Files
kuber/tests/command/init.test.ts
T
2026-10-06 15:31:51 +00:00

431 lines
14 KiB
TypeScript

import { afterEach, describe, expect, test, spyOn } from "bun:test";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { YAML } from "bun";
import { initializeProject } from "../../command/init";
import { managedHeader } from "../../lib/scaffold";
import { readTrust, resolveTrustIdentity, updateTrust } from "../../lib/trust";
import { KuberApiError } from "../../lib/api";
const roots: string[] = [];
const originalConfig = process.env.XDG_CONFIG_HOME;
afterEach(async () => {
for (const root of roots.splice(0))
await rm(root, { recursive: true, force: true });
if (originalConfig === undefined) delete process.env.XDG_CONFIG_HOME;
else process.env.XDG_CONFIG_HOME = originalConfig;
});
async function root() {
const path = await mkdtemp(join(tmpdir(), "kuber-init-test-"));
roots.push(path);
process.env.XDG_CONFIG_HOME = join(path, "config");
return path;
}
describe("kuber init", () => {
test("uses an existing configured project for Compose and trust", async () => {
const cwd = await root();
await writeFile(
join(cwd, ".kuberrc.ts"),
'export default { project: "configured" };\n',
);
const paths: string[] = [];
const log = spyOn(console, "log").mockImplementation(() => {});
try {
await initializeProject(
cwd,
{ nonInteractive: true },
{
session: async () => ({
token: "test",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
}),
request: async (path, init) => {
paths.push(path);
return (
init?.method === "POST" ? undefined : { fingerprints: [] }
) as never;
},
},
);
expect(
YAML.parse(await readFile(join(cwd, "compose.yml"), "utf8")),
).toMatchObject({ name: "configured" });
expect(paths).toEqual([
"/workspaces/configured/trust",
"/workspaces/configured/trust",
]);
} finally {
log.mockRestore();
}
});
test("empty directory creates documented image-only example and trusts selected project", async () => {
const cwd = await root();
const calls: string[] = [];
const log = spyOn(console, "log").mockImplementation(() => {});
try {
await initializeProject(
cwd,
{ nonInteractive: true, project: "demo" },
{
session: async () => ({
token: "test",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
}),
request: async (path, init) => {
calls.push(path);
return (
init?.method === "POST" ? undefined : { fingerprints: [] }
) as never;
},
},
);
const content = await readFile(join(cwd, "compose.yml"), "utf8");
expect(content.split("\n")[0]).toBe(managedHeader);
const compose = YAML.parse(content);
expect(compose).toMatchObject({
managedBy: "kuber",
name: "demo",
services: {
[join(cwd).split("/").at(-1)!.toLowerCase()]: {
image: "nginx:stable",
},
},
});
expect(calls).toEqual([
"/workspaces/demo/trust",
"/workspaces/demo/trust",
]);
expect(await readTrust()).toHaveLength(1);
expect(await Bun.file(join(cwd, "Dockerfile")).exists()).toBe(false);
} finally {
log.mockRestore();
}
});
test("multiple manifests require explicit selection in noninteractive mode", async () => {
const cwd = await root();
for (const name of ["one", "two"]) {
await mkdir(join(cwd, name));
await writeFile(join(cwd, name, "package.json"), "{}");
}
await expect(
initializeProject(
cwd,
{ nonInteractive: true },
{ session: async () => undefined },
),
).rejects.toThrow("specify --path");
expect(await Bun.file(join(cwd, "compose.yml")).exists()).toBe(false);
});
test("revoked session retries once without duplicating trust; failed grant never writes local trust", async () => {
const cwd = await root();
let attempts = 0;
let logins = 0;
const log = spyOn(console, "log").mockImplementation(() => {});
try {
await initializeProject(
cwd,
{ nonInteractive: true, project: "demo" },
{
session: async () => ({
token: "old",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
}),
login: async () => {
logins++;
return {
token: "new",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
};
},
request: async (_path, init) => {
if (++attempts === 1) throw new KuberApiError("revoked", 401);
return (
init?.method === "POST" ? undefined : { fingerprints: [] }
) as never;
},
},
);
expect([attempts, logins]).toEqual([3, 1]);
expect(await readTrust()).toHaveLength(1);
} finally {
log.mockRestore();
}
const other = await root();
await expect(
initializeProject(
other,
{ nonInteractive: true, project: "bad" },
{
session: async () => undefined,
login: async () => ({
token: "new",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
}),
request: async () => {
throw new KuberApiError("failed", 503);
},
},
),
).rejects.toThrow("failed");
expect(await readTrust()).toEqual([]);
expect(await Bun.file(join(other, "compose.yml")).exists()).toBe(false);
});
test("successful grant with failed local persistence keeps registration and generated files", async () => {
const cwd = await root();
const identity = await resolveTrustIdentity("demo", cwd);
const trusted = new Set<string>();
const calls: string[] = [];
const log = spyOn(console, "log").mockImplementation(() => {});
try {
const failure = await initializeProject(
cwd,
{
nonInteractive: true,
project: "demo",
source: "template",
template: "static-nginx",
},
{
session: async () => ({
token: "test",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
}),
request: async (path, init) => {
calls.push(init?.method ?? "GET");
if (init?.method === "POST")
trusted.add((init.json as { fingerprint: string }).fingerprint);
expect(path).toBe("/workspaces/demo/trust");
return (
init?.method ? undefined : { fingerprints: [...trusted] }
) as never;
},
persistTrust: async () => {
throw new Error("disk full: secret-token");
},
},
).catch((error: unknown) => error);
expect(failure).toBeInstanceOf(Error);
expect((failure as Error).message).toMatch(
/Server registration exists.*kuber trust/,
);
expect((failure as Error).message).not.toContain("secret-token");
expect((failure as Error).cause).toBeUndefined();
expect(calls).toEqual(["GET", "POST"]);
expect(trusted.has(identity.fingerprint)).toBe(true);
expect(await readTrust()).toEqual([]);
expect(await Bun.file(join(cwd, "compose.yml")).exists()).toBe(true);
expect(await Bun.file(join(cwd, "Dockerfile")).exists()).toBe(true);
expect(await Bun.file(join(cwd, ".dockerignore")).exists()).toBe(true);
expect(log).not.toHaveBeenCalled();
} finally {
log.mockRestore();
}
});
test("pre-existing server grant and local trust survive local persistence failure", async () => {
const cwd = await root();
const identity = await resolveTrustIdentity("demo", cwd);
const existing = await resolveTrustIdentity("other", cwd);
await updateTrust(() => [existing]);
const calls: string[] = [];
await expect(
initializeProject(
cwd,
{ nonInteractive: true, project: "demo" },
{
session: async () => ({
token: "test",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
}),
request: async (_path, init) => {
calls.push(init?.method ?? "GET");
return { fingerprints: [identity.fingerprint] } as never;
},
persistTrust: async () => {
throw new Error("disk full");
},
},
),
).rejects.toThrow(/Server registration exists.*kuber trust/);
expect(calls).toEqual(["GET"]);
expect(await readTrust()).toEqual([existing]);
expect(await Bun.file(join(cwd, "compose.yml")).exists()).toBe(true);
});
test("concurrent grant between GET and POST is never revoked on local failure", async () => {
const cwd = await root();
const identity = await resolveTrustIdentity("demo", cwd);
const calls: string[] = [];
const trusted = new Set<string>();
await expect(
initializeProject(
cwd,
{ nonInteractive: true, project: "demo" },
{
session: async () => ({
token: "test",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
}),
request: async (_path, init) => {
calls.push(init?.method ?? "GET");
if (!init?.method) {
trusted.add(identity.fingerprint); // Another client grants after our GET snapshot.
return { fingerprints: [] } as never;
}
if (init.method === "POST") {
expect(trusted.has(identity.fingerprint)).toBe(true);
return undefined as never; // Idempotent POST did not create this grant.
}
throw new Error(`Unexpected ${init.method}`);
},
persistTrust: async () => {
throw new Error("disk full");
},
},
),
).rejects.toThrow(/Server registration exists.*kuber trust/);
expect(calls).toEqual(["GET", "POST"]);
expect(trusted.has(identity.fingerprint)).toBe(true);
expect(await Bun.file(join(cwd, "compose.yml")).exists()).toBe(true);
});
test("partial local write still reports failure without revoking remote trust", async () => {
const cwd = await root();
const identity = await resolveTrustIdentity("demo", cwd);
const calls: string[] = [];
const log = spyOn(console, "log").mockImplementation(() => {});
try {
await expect(
initializeProject(
cwd,
{ nonInteractive: true, project: "demo" },
{
session: async () => ({
token: "test",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
}),
request: async (_path, init) => {
calls.push(init?.method ?? "GET");
return (init?.method ? undefined : { fingerprints: [] }) as never;
},
persistTrust: async (update) => {
await updateTrust(update); // Simulates rename succeeding before chmod fails.
throw new Error("chmod failed");
},
},
),
).rejects.toThrow(/Server registration exists.*kuber trust/);
expect(calls).toEqual(["GET", "POST"]);
expect(await readTrust()).toEqual([identity]);
expect(await Bun.file(join(cwd, "compose.yml")).exists()).toBe(true);
expect(log).not.toHaveBeenCalled();
} finally {
log.mockRestore();
}
});
test("failed POST rolls back generated files without altering existing trust", async () => {
const cwd = await root();
const existing = await resolveTrustIdentity("other", cwd);
await updateTrust(() => [existing]);
const calls: string[] = [];
await expect(
initializeProject(
cwd,
{
nonInteractive: true,
project: "demo",
source: "template",
template: "static-nginx",
},
{
session: async () => ({
token: "test",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
}),
request: async (_path, init) => {
calls.push(init?.method ?? "GET");
if (init?.method === "POST")
throw new KuberApiError("grant failed", 503);
return { fingerprints: [] } as never;
},
},
),
).rejects.toThrow("grant failed");
expect(calls).toEqual(["GET", "POST"]);
expect(await readTrust()).toEqual([existing]);
expect(await Bun.file(join(cwd, "compose.yml")).exists()).toBe(false);
expect(await Bun.file(join(cwd, "Dockerfile")).exists()).toBe(false);
expect(await Bun.file(join(cwd, ".dockerignore")).exists()).toBe(false);
});
test("existing Compose file is not overwritten and no grant is attempted", async () => {
const cwd = await root();
const composePath = join(cwd, "compose.yml");
const existing = "name: existing\nservices: {}\n";
await writeFile(composePath, existing);
let requested = false;
await expect(
initializeProject(
cwd,
{ nonInteractive: true, project: "demo" },
{
request: async () => {
requested = true;
return undefined as never;
},
},
),
).rejects.toThrow("A Compose file already exists");
expect(await readFile(composePath, "utf8")).toBe(existing);
expect(requested).toBe(false);
});
test("existing Dockerfile is not overwritten by template init", async () => {
const cwd = await root();
const dockerfile = join(cwd, "Dockerfile");
await writeFile(dockerfile, "FROM private-image\n");
let requested = false;
await expect(
initializeProject(
cwd,
{
nonInteractive: true,
project: "demo",
source: "template",
template: "static-nginx",
},
{
session: async () => ({
token: "test",
expiresAt: "2099-01-01",
user: { username: "a", roles: [] },
}),
request: async () => {
requested = true;
return undefined as never;
},
},
),
).rejects.toThrow();
expect(await readFile(dockerfile, "utf8")).toBe("FROM private-image\n");
expect(await Bun.file(join(cwd, "compose.yml")).exists()).toBe(false);
expect(requested).toBe(false);
});
});