mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
177 lines
6.5 KiB
TypeScript
177 lines
6.5 KiB
TypeScript
import * as path from "path";
|
|
import { fileURLToPath } from "node:url";
|
|
import type { StartedNetwork } from "testcontainers";
|
|
import { GenericContainer, Network, Wait, type StartedTestContainer } from "testcontainers";
|
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
|
|
const TEST_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
const LOGIN_APP_DIR = path.join(TEST_DIR, "../..");
|
|
const LOGIN_IMAGE_TAG = `zitadel-login-test:${process.env.GITHUB_SHA || "local"}`;
|
|
|
|
const DOCKER_TIMEOUT = 180_000;
|
|
|
|
/**
|
|
* Integration tests for ZITADEL_API_AWAITINITIALCONN support.
|
|
*
|
|
* When this environment variable is set (e.g. "30s", "5m"), the login
|
|
* container polls the ZITADEL API's /debug/ready endpoint before starting
|
|
* the Next.js server. This replaces the wait4x init container that was
|
|
* previously used in the Helm chart.
|
|
*
|
|
* The test suite uses a mock Zitadel server that returns 503 on /debug/ready
|
|
* for a configurable duration before switching to 200. Three scenarios are
|
|
* verified: the API becomes ready within the timeout, the timeout expires
|
|
* before the API is ready, and the variable is not set at all.
|
|
*/
|
|
describe("ZITADEL_API_AWAITINITIALCONN", () => {
|
|
beforeAll(async () => {
|
|
await GenericContainer.fromDockerfile(LOGIN_APP_DIR).build(LOGIN_IMAGE_TAG);
|
|
}, DOCKER_TIMEOUT);
|
|
|
|
describe("when the API becomes ready before the timeout", () => {
|
|
let network: StartedNetwork;
|
|
let mockZitadel: StartedTestContainer;
|
|
let container: StartedTestContainer;
|
|
let appUrl: string;
|
|
|
|
beforeAll(async () => {
|
|
network = await new Network().start();
|
|
|
|
mockZitadel = await new GenericContainer("node:20-alpine")
|
|
.withNetwork(network)
|
|
.withNetworkAliases("mock-zitadel")
|
|
.withCopyFilesToContainer([
|
|
{ source: path.join(TEST_DIR, "mock-zitadel-server.js"), target: "/app/server.js" },
|
|
])
|
|
.withEnvironment({ PORT: "8080", READY_AFTER_MS: "5000" })
|
|
.withCommand(["node", "/app/server.js"])
|
|
.withExposedPorts(8080)
|
|
.withWaitStrategy(Wait.forLogMessage("Mock Zitadel server listening"))
|
|
.start();
|
|
|
|
container = await new GenericContainer(LOGIN_IMAGE_TAG)
|
|
.withNetwork(network)
|
|
.withExposedPorts(3000)
|
|
.withEnvironment({
|
|
ZITADEL_API_URL: "http://mock-zitadel:8080",
|
|
ZITADEL_SERVICE_USER_TOKEN: "test-token",
|
|
ZITADEL_API_AWAITINITIALCONN: "60s",
|
|
})
|
|
.withStartupTimeout(120_000)
|
|
.withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000))
|
|
.start();
|
|
|
|
appUrl = `http://${container.getHost()}:${container.getMappedPort(3000)}`;
|
|
}, DOCKER_TIMEOUT);
|
|
|
|
afterAll(async () => {
|
|
await container?.stop().catch(() => {});
|
|
await mockZitadel?.stop().catch(() => {});
|
|
await network?.stop().catch(() => {});
|
|
}, DOCKER_TIMEOUT);
|
|
|
|
it("starts successfully after the API becomes ready", async () => {
|
|
const response = await fetch(`${appUrl}/ui/v2/login/healthy`);
|
|
expect(response.status).toBe(200);
|
|
});
|
|
});
|
|
|
|
describe("when the API does not become ready before the timeout", () => {
|
|
let network: StartedNetwork;
|
|
let mockZitadel: StartedTestContainer;
|
|
let container: StartedTestContainer;
|
|
|
|
beforeAll(async () => {
|
|
network = await new Network().start();
|
|
|
|
mockZitadel = await new GenericContainer("node:20-alpine")
|
|
.withNetwork(network)
|
|
.withNetworkAliases("mock-zitadel")
|
|
.withCopyFilesToContainer([
|
|
{ source: path.join(TEST_DIR, "mock-zitadel-server.js"), target: "/app/server.js" },
|
|
])
|
|
.withEnvironment({ PORT: "8080", READY_AFTER_MS: "600000" })
|
|
.withCommand(["node", "/app/server.js"])
|
|
.withExposedPorts(8080)
|
|
.withWaitStrategy(Wait.forLogMessage("Mock Zitadel server listening"))
|
|
.start();
|
|
|
|
container = await new GenericContainer(LOGIN_IMAGE_TAG)
|
|
.withNetwork(network)
|
|
.withExposedPorts(3000)
|
|
.withEnvironment({
|
|
ZITADEL_API_URL: "http://mock-zitadel:8080",
|
|
ZITADEL_SERVICE_USER_TOKEN: "test-token",
|
|
ZITADEL_API_AWAITINITIALCONN: "5s",
|
|
})
|
|
.withStartupTimeout(30_000)
|
|
.withWaitStrategy(Wait.forLogMessage(/Timed out after 5s/))
|
|
.start();
|
|
}, DOCKER_TIMEOUT);
|
|
|
|
afterAll(async () => {
|
|
await container?.stop().catch(() => {});
|
|
await mockZitadel?.stop().catch(() => {});
|
|
await network?.stop().catch(() => {});
|
|
}, DOCKER_TIMEOUT);
|
|
|
|
it("logs the timeout error before exiting", async () => {
|
|
const stream = await container.logs();
|
|
const logs = await new Promise<string>((resolve) => {
|
|
let output = "";
|
|
stream.on("data", (chunk: Buffer) => { output += chunk.toString(); });
|
|
stream.on("end", () => resolve(output));
|
|
setTimeout(() => { stream.destroy(); resolve(output); }, 5_000);
|
|
});
|
|
expect(logs).toContain("Timed out after 5s waiting for API readiness");
|
|
});
|
|
});
|
|
|
|
describe("when the variable is not set", () => {
|
|
let network: StartedNetwork;
|
|
let mockZitadel: StartedTestContainer;
|
|
let container: StartedTestContainer;
|
|
let appUrl: string;
|
|
|
|
beforeAll(async () => {
|
|
network = await new Network().start();
|
|
|
|
mockZitadel = await new GenericContainer("node:20-alpine")
|
|
.withNetwork(network)
|
|
.withNetworkAliases("mock-zitadel")
|
|
.withCopyFilesToContainer([
|
|
{ source: path.join(TEST_DIR, "mock-zitadel-server.js"), target: "/app/server.js" },
|
|
])
|
|
.withEnvironment({ PORT: "8080", READY_AFTER_MS: "0" })
|
|
.withCommand(["node", "/app/server.js"])
|
|
.withExposedPorts(8080)
|
|
.withWaitStrategy(Wait.forLogMessage("Mock Zitadel server listening"))
|
|
.start();
|
|
|
|
container = await new GenericContainer(LOGIN_IMAGE_TAG)
|
|
.withNetwork(network)
|
|
.withExposedPorts(3000)
|
|
.withEnvironment({
|
|
ZITADEL_API_URL: "http://mock-zitadel:8080",
|
|
ZITADEL_SERVICE_USER_TOKEN: "test-token",
|
|
})
|
|
.withStartupTimeout(120_000)
|
|
.withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000))
|
|
.start();
|
|
|
|
appUrl = `http://${container.getHost()}:${container.getMappedPort(3000)}`;
|
|
}, DOCKER_TIMEOUT);
|
|
|
|
afterAll(async () => {
|
|
await container?.stop().catch(() => {});
|
|
await mockZitadel?.stop().catch(() => {});
|
|
await network?.stop().catch(() => {});
|
|
}, DOCKER_TIMEOUT);
|
|
|
|
it("starts without waiting", async () => {
|
|
const response = await fetch(`${appUrl}/ui/v2/login/healthy`);
|
|
expect(response.status).toBe(200);
|
|
});
|
|
});
|
|
});
|