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(); 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(); 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); }); });