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

166 lines
5.1 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import type { V1Pod } from "@kubernetes/client-node";
import type { ComposeSpecification } from "../../schema/docker.d";
import {
getComposeArch,
getComposeArchPlacement,
resolveComposeArch,
withComposeArch,
} from "../../lib/arch";
import { createCachedGetter, ctx } from "../../lib/context";
import { toEnvVars, toTable } from "../../lib/format";
import { deepMerge, getPodContainerName, throwWhen } from "../../lib/shared";
describe("architecture selection", () => {
test("defaults to arm64 and recognizes amd64", () => {
expect(resolveComposeArch({} as ComposeSpecification)).toBe("arm64");
expect(
resolveComposeArch({ "x-arch": "arm64" } as ComposeSpecification),
).toBe("arm64");
expect(
resolveComposeArch({ "x-arch": "amd64" } as ComposeSpecification),
).toBe("amd64");
});
test("provides amd64 placement and no arm64 constraints", () => {
expect(getComposeArchPlacement({} as ComposeSpecification)).toEqual({});
expect(
getComposeArchPlacement({ "x-arch": "amd64" } as ComposeSpecification),
).toEqual({
nodeSelector: { "kubernetes.io/arch": "amd64" },
tolerations: [
{
key: "arch",
operator: "Equal",
value: "amd64",
effect: "NoExecute",
},
],
});
});
test("scopes architecture through synchronous and asynchronous work", async () => {
expect(getComposeArch()).toBe("arm64");
expect(
withComposeArch({ "x-arch": "amd64" } as ComposeSpecification, () =>
getComposeArch(),
),
).toBe("amd64");
await withComposeArch(
{ "x-arch": "amd64" } as ComposeSpecification,
async () => {
await Promise.resolve();
expect(getComposeArch()).toBe("amd64");
},
);
expect(getComposeArch()).toBe("arm64");
});
});
describe("format helpers", () => {
test("converts array environments and preserves embedded equals", () => {
expect(toEnvVars(["EMPTY", "A=one", "TOKEN=a=b=c"])).toEqual([
{ name: "EMPTY", value: undefined },
{ name: "A", value: "one" },
{ name: "TOKEN", value: "a=b=c" },
]);
});
test("converts object environment scalar values", () => {
expect(
toEnvVars({ STRING: "yes", NUMBER: 2, BOOL: false, NIL: null }),
).toEqual([
{ name: "STRING", value: "yes" },
{ name: "NUMBER", value: "2" },
{ name: "BOOL", value: "false" },
{ name: "NIL", value: undefined },
]);
expect(toEnvVars(undefined)).toBeUndefined();
});
test("renders uppercase table headers and rows", () => {
const table = toTable([{ name: "app", replicas: 2 }]);
expect(table).toContain("NAME");
expect(table).toContain("REPLICAS");
expect(table).toContain("app");
expect(table).toContain("2");
});
});
describe("shared helpers", () => {
test("deep merges objects and concatenates arrays without mutation", () => {
const target = { nested: { left: 1 }, list: [1], untouched: true };
const source = { nested: { right: 2 }, list: [2], added: "yes" };
expect(deepMerge(target, source)).toEqual({
nested: { left: 1, right: 2 },
list: [1, 2],
untouched: true,
added: "yes",
});
expect(target).toEqual({ nested: { left: 1 }, list: [1], untouched: true });
});
test.each([
[1, 2, 2],
[{ value: 1 }, null, null],
[null, { value: 2 }, { value: 2 }],
])("replaces non-mergeable values", (target, source, expected) => {
expect(deepMerge(target, source)).toEqual(expected);
});
test("throwWhen passes unmatched values and throws matched values", () => {
const rejectEmpty = throwWhen(
(value: string) => value.length === 0,
"empty",
);
expect(rejectEmpty("value")).toBe("value");
expect(() => rejectEmpty("")).toThrow("empty");
});
test("gets the first pod container or gives a useful error", () => {
expect(
getPodContainerName({
metadata: { name: "app-1" },
spec: { containers: [{ name: "app" }] },
} as V1Pod),
).toBe("app");
expect(() =>
getPodContainerName({ metadata: { name: "empty" } } as V1Pod),
).toThrow("No container found in pod empty");
});
});
describe("context helpers", () => {
test("rejects access outside an application context", () => {
expect(() => ctx()).toThrow("Not in context");
});
test("cached getters execute once and share an in-flight result", async () => {
let calls = 0;
const getter = createCachedGetter(
`test-${crypto.randomUUID()}`,
async () => {
calls += 1;
return "value";
},
);
expect(await Promise.all([getter(), getter()])).toEqual(["value", "value"]);
expect(await getter()).toBe("value");
expect(calls).toBe(1);
});
test("failed getter calls can retry", async () => {
let calls = 0;
const getter = createCachedGetter(
`test-${crypto.randomUUID()}`,
async () => {
calls += 1;
if (calls === 1) throw new Error("temporary");
return "recovered";
},
);
await expect(getter()).rejects.toThrow("temporary");
expect(await getter()).toBe("recovered");
});
});