/* Unit tests for fleet sites generate covering fleet.sites.generate; validateFleetSitesDrift. It also guards the documented public contract and regression-sensitive edge cases. Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. RFC-1311: Compass header sweep — declares the fleet suite's contract for test-file scaffold coverage. */ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { stringify as yamlStringify } from "yaml"; import { runFleetSitesGenerate, validateFleetSitesDrift } from "./fleet-sites-generate.ts"; import type { KernelRuntimeContext } from "@warpgogol/werkstatt-shared/kernel"; import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io"; function makeContext(workspaceRoot: string): KernelRuntimeContext { return { workspaceRoot, logger: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {}, event: () => {}, getEvents: () => [], }, siteName: undefined, siteExplicit: false, dryRun: false, outputFormat: "text", io: createDefaultIO().io, actualState: undefined, } as unknown as KernelRuntimeContext; } function writeSystemConfig( cacheRoot: string, id: string, opts: { canary?: boolean; status?: string; mirrors?: number } = {}, ) { const dir = join(cacheRoot, id); mkdirSync(dir, { recursive: true }); const config: Record = { schemaVersion: "1", id, cosmicStar: "Sirius", mirrors: Array.from({ length: opts.mirrors ?? 1 }, (_, i) => ({ path: i === 0 ? `../systems-cache/${id}` : `git@github.com:example/${id}.git`, storageType: i === 0 ? "non-bare" : "bare", })), pinnedPlatform: "6.0.0", status: opts.status ?? "active", registeredAt: "2026-01-01T00:00:00Z", }; if (opts.canary) { config.fleet = { canary: true }; } writeFileSync(join(dir, "system-config.yaml"), yamlStringify(config)); } describe("fleet.sites.generate", () => { let workspaceRoot: string; let cacheRoot: string; // workspaceRoot sits inside a unique sandbox so "../systems-cache" resolves // per-test — a sibling directly under tmpdir() would be the shared // /tmp/systems-cache pool whose parallel afterEach deletions race writes. let sandbox: string; beforeEach(() => { sandbox = mkdtempSync(join(tmpdir(), "fleet-gen-")); workspaceRoot = join(sandbox, "werkstatt"); mkdirSync(workspaceRoot, { recursive: true }); cacheRoot = join(sandbox, "systems-cache"); mkdirSync(cacheRoot, { recursive: true }); }); afterEach(() => { rmSync(sandbox, { recursive: true, force: true }); }); it("generates fleet.sites.yaml from discovered systems", async () => { writeSystemConfig(cacheRoot, "alpha"); writeSystemConfig(cacheRoot, "beta"); const result = await runFleetSitesGenerate({ flags: {}, argv: [] }, makeContext(workspaceRoot)); expect(result.exitCode).toBe(0); const data = result.data as { sites: unknown[] }; expect(data.sites).toHaveLength(2); expect(data.sites[0]).toHaveProperty("id"); expect(data.sites[0]).toHaveProperty("path"); expect(data.sites[0]).toHaveProperty("platformVersion"); expect(data.sites[0]).toHaveProperty("channels"); expect(data.sites[0]).toHaveProperty("mirrors"); expect(data.sites[0]).toHaveProperty("activeMission"); expect(data.sites[0]).toHaveProperty("canary"); }); it("sorts sites by id", async () => { writeSystemConfig(cacheRoot, "zeta"); writeSystemConfig(cacheRoot, "alpha"); const result = await runFleetSitesGenerate({ flags: {}, argv: [] }, makeContext(workspaceRoot)); const data = result.data as { sites: Array<{ id: string }> }; expect(data.sites[0].id).toBe("alpha"); expect(data.sites[1].id).toBe("zeta"); }); it("sets canary=true when fleet.canary is set in system-config.yaml", async () => { writeSystemConfig(cacheRoot, "canary-site", { canary: true }); writeSystemConfig(cacheRoot, "normal-site", { canary: false }); const result = await runFleetSitesGenerate({ flags: {}, argv: [] }, makeContext(workspaceRoot)); const data = result.data as { sites: Array<{ id: string; canary: boolean }> }; const canary = data.sites.find((s) => s.id === "canary-site"); const normal = data.sites.find((s) => s.id === "normal-site"); expect(canary?.canary).toBe(true); expect(normal?.canary).toBe(false); }); it("defaults canary to false when fleet field is absent", async () => { writeSystemConfig(cacheRoot, "site-a"); const result = await runFleetSitesGenerate({ flags: {}, argv: [] }, makeContext(workspaceRoot)); const data = result.data as { sites: Array<{ id: string; canary: boolean }> }; expect(data.sites[0].canary).toBe(false); }); it("writes the output file with GENERATED header", async () => { writeSystemConfig(cacheRoot, "alpha"); await runFleetSitesGenerate({ flags: {}, argv: [] }, makeContext(workspaceRoot)); const outputPath = join(workspaceRoot, "fleet", "fleet.sites.yaml"); expect(existsSync(outputPath)).toBe(true); const raw = readFileSync(outputPath, "utf8"); expect(raw).toContain("GENERATED"); }); it("handles empty fleet gracefully", async () => { const result = await runFleetSitesGenerate({ flags: {}, argv: [] }, makeContext(workspaceRoot)); expect(result.exitCode).toBe(0); const data = result.data as { sites: unknown[] }; expect(data.sites).toHaveLength(0); }); it("maps SCP-style git mirrors (git@host:path) to ssh protocol without crashing", async () => { // Regression: extractMirrorProtocols called new URL() on any path starting // with "git" — SCP-style git@github.com:org/repo.git threw Invalid URL and // killed workspace.surface.validate on real fleet configs. writeSystemConfig(cacheRoot, "scp-site", { mirrors: 2 }); const result = await runFleetSitesGenerate({ flags: {}, argv: [] }, makeContext(workspaceRoot)); expect(result.exitCode).toBe(0); const data = result.data as { sites: Array<{ id: string; mirrors: { count: number; protocols: string[] } }>; }; const site = data.sites.find((s) => s.id === "scp-site"); expect(site?.mirrors.count).toBe(2); expect(site?.mirrors.protocols).toEqual(["file", "ssh"]); }); }); describe("validateFleetSitesDrift", () => { let workspaceRoot: string; let cacheRoot: string; // workspaceRoot sits inside a unique sandbox so "../systems-cache" resolves // per-test — a sibling directly under tmpdir() would be the shared // /tmp/systems-cache pool whose parallel afterEach deletions race writes. let sandbox: string; beforeEach(() => { sandbox = mkdtempSync(join(tmpdir(), "fleet-drift-")); workspaceRoot = join(sandbox, "werkstatt"); mkdirSync(workspaceRoot, { recursive: true }); cacheRoot = join(sandbox, "systems-cache"); mkdirSync(cacheRoot, { recursive: true }); }); afterEach(() => { rmSync(sandbox, { recursive: true, force: true }); }); it("reports drift when fleet.sites.yaml does not exist", async () => { writeSystemConfig(cacheRoot, "alpha"); const result = await validateFleetSitesDrift(workspaceRoot); expect(result.drifted).toBe(true); }); it("reports no drift after generate", async () => { writeSystemConfig(cacheRoot, "alpha"); await runFleetSitesGenerate({ flags: {}, argv: [] }, makeContext(workspaceRoot)); const result = await validateFleetSitesDrift(workspaceRoot); expect(result.drifted).toBe(false); }); });