Files
kuber/tests/lib/config.test.ts
T

152 lines
4.5 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_BUILDERS,
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,
builders: DEFAULT_BUILDERS,
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/",
builders: { amd64: "user@amd", remoteRoot: "build-root" },
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",
builders: {
amd64: "user@amd",
arm64: DEFAULT_BUILDERS.arm64,
remoteRoot: "build-root",
},
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(),
{},
"registry.example.com/team",
);
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",
);
});
});
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");
});
});