144 lines
4.4 KiB
TypeScript
144 lines
4.4 KiB
TypeScript
import { afterEach, describe, expect, test } from "bun:test";
|
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import type { V1Deployment } from "@kubernetes/client-node";
|
|
import {
|
|
DEFAULT_REGISTRY,
|
|
DEFAULT_ROLLOUT_TIMEOUT_MS,
|
|
extractConfigArgument,
|
|
loadConfig,
|
|
resolveConfigArgument,
|
|
} from "../../lib/config";
|
|
import { composeToKubernetes } from "../../lib/convert";
|
|
|
|
const directories: string[] = [];
|
|
|
|
async function temporaryDirectory() {
|
|
const directory = await mkdtemp(join(tmpdir(), "kuber-config-"));
|
|
directories.push(directory);
|
|
return directory;
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
directories
|
|
.splice(0)
|
|
.map((directory) => rm(directory, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
describe("kuber config", () => {
|
|
test("uses operational defaults when .kuberrc.ts is absent", async () => {
|
|
const cwd = await temporaryDirectory();
|
|
expect(await loadConfig(cwd)).toMatchObject({
|
|
project: cwd.split("/").at(-1),
|
|
registry: DEFAULT_REGISTRY,
|
|
rolloutTimeoutMs: DEFAULT_ROLLOUT_TIMEOUT_MS,
|
|
configFile: undefined,
|
|
});
|
|
});
|
|
|
|
test("loads TypeScript from a custom config path", async () => {
|
|
const cwd = await temporaryDirectory();
|
|
const configFile = join(cwd, "production.kuberrc.ts");
|
|
await Bun.write(
|
|
configFile,
|
|
`export default {
|
|
project: "configured-project",
|
|
composeFile: "deploy/compose.yml",
|
|
registry: "registry.example.com/",
|
|
rolloutTimeoutMs: 42_000,
|
|
async preBuild() {},
|
|
};`,
|
|
);
|
|
|
|
const config = await loadConfig(cwd, "production.kuberrc.ts");
|
|
expect(config).toMatchObject({
|
|
project: "configured-project",
|
|
composeFile: join(cwd, "deploy", "compose.yml"),
|
|
registry: "registry.example.com",
|
|
rolloutTimeoutMs: 42_000,
|
|
configFile,
|
|
});
|
|
expect(config.preBuild).toBeFunction();
|
|
});
|
|
|
|
test("requires explicit config paths to exist", async () => {
|
|
const cwd = await temporaryDirectory();
|
|
await expect(loadConfig(cwd, "missing.ts")).rejects.toThrow(
|
|
"Config file cannot be found",
|
|
);
|
|
});
|
|
|
|
test("requires a default object export", async () => {
|
|
const cwd = await temporaryDirectory();
|
|
await Bun.write(join(cwd, ".kuberrc.ts"), "export const project = 'bad';");
|
|
await expect(loadConfig(cwd)).rejects.toThrow(
|
|
"must default export a configuration object",
|
|
);
|
|
});
|
|
|
|
test("validates hook and operational values", async () => {
|
|
const cwd = await temporaryDirectory();
|
|
await Bun.write(
|
|
join(cwd, ".kuberrc.ts"),
|
|
"export default { preBuild: true, rolloutTimeoutMs: -1 };",
|
|
);
|
|
await expect(loadConfig(cwd)).rejects.toThrow("preBuild");
|
|
});
|
|
|
|
test("uses the configured registry in rendered build images", async () => {
|
|
const resources = await composeToKubernetes(
|
|
"project",
|
|
{ services: { app: { build: "." } } },
|
|
process.cwd(),
|
|
{},
|
|
{ app: `registry.example.com/team/kuber/project-app:latest@sha256:${"a".repeat(64)}` },
|
|
);
|
|
const deployment = resources.find(
|
|
(resource) => resource.kind === "Deployment",
|
|
) as V1Deployment | undefined;
|
|
expect(deployment?.spec?.template.spec?.containers[0]?.image).toBe(
|
|
`registry.example.com/team/kuber/project-app:latest@sha256:${"a".repeat(64)}`,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("global config argument", () => {
|
|
test.each([
|
|
[["--config", "one.ts", "up"], "one.ts"],
|
|
[["up", "--config", "two.ts"], "two.ts"],
|
|
[["--config=three.ts", "up"], "three.ts"],
|
|
])("reads a config path from %j", (args, expected) => {
|
|
expect(resolveConfigArgument(args)).toBe(expected);
|
|
});
|
|
|
|
test("rejects a config option without a path", () => {
|
|
expect(() => resolveConfigArgument(["up", "--config"])).toThrow(
|
|
"requires a path",
|
|
);
|
|
});
|
|
|
|
test("does not parse config arguments after --", () => {
|
|
expect(
|
|
resolveConfigArgument(["exec", "app", "--", "--config", "x.ts"]),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
test("removes the global option before subcommand dispatch", () => {
|
|
expect(
|
|
extractConfigArgument(["logs", "--config", "production.ts"]),
|
|
).toEqual({
|
|
configPath: "production.ts",
|
|
rawArgs: ["logs"],
|
|
});
|
|
});
|
|
|
|
test("rejects duplicate config options", () => {
|
|
expect(() =>
|
|
extractConfigArgument(["--config=one.ts", "--config=two.ts"]),
|
|
).toThrow("only be specified once");
|
|
});
|
|
});
|