/** * all-integration.test.ts — `/pygienium-all` end-to-end (task 14). * * Mirrors the spec scenario: run every registered check in sequence under one * resumable run-state and confirm the run completes with every check marked * `complete`. Uses the injectable fake agent runner (no model needed) and stub * checks whose `!write`/`!echo` task protocol produces deterministic artifacts. */ import { describe, expect, it, beforeEach, afterEach } from "bun:test"; import { mkdtemp, readFile, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { clearChecks, registerCheck, type CheckDefinition, } from "../src/checks/registry.js"; import { setAgentRunner, resetAgentRunner, fakeAgentRunner, type AgentRunner, } from "../src/agent-runner.js"; import { handleAllCommand, type PygieniumCtx } from "../src/commands.js"; import { loadRunState } from "../src/run-state.js"; import { canonicalChecksRoot } from "../src/export.js"; /** Stub check whose fake-runner task writes artifacts + echoes a line. */ function fakeCheck(name: string): CheckDefinition { return { name, label: name, description: `${name} check`, agentName: "scanner", fixAgentName: "fixer", phaseId: "scan", buildScanTask: (_cwd, scope) => `!write pygienium/checks/${name}/findings.md # ${name} findings\nscan-target:${scope.target}\n!echo ${name}-scan`, buildFixTask: (_cwd, _scope, findings) => `!write pygienium/checks/${name}/changes.md # ${name} changes\nbased-on:${findings.split("\n")[0] ?? ""}\n!echo ${name}-fix`, gate: () => undefined, }; } function stubCtx(cwd: string): PygieniumCtx { return { cwd, mode: "print", hasUI: false, ui: undefined } as PygieniumCtx; } /** Capture process.stdout.write lines for the duration of `fn`. */ async function captureStdout(fn: () => Promise): Promise { const out: string[] = []; const write = process.stdout.write.bind(process.stdout); (process.stdout as { write: (chunk: unknown) => boolean }).write = ( chunk: unknown, ) => { out.push(String(chunk).replace(/\r?\n$/, "")); return true; }; try { await fn(); } finally { (process.stdout as { write: (chunk: unknown) => boolean }).write = write; } return out; } describe("/pygienium-all end-to-end (task 14)", () => { let cwd: string; let dispatched: string[]; let runner: AgentRunner; beforeEach(async () => { clearChecks(); dispatched = []; runner = async (opts) => { const m = /pygienium\/checks\/([^/]+)\//.exec(opts.task); if (m) dispatched.push(m[1] as string); return fakeAgentRunner(opts); }; setAgentRunner(runner); cwd = await mkdtemp(join(tmpdir(), "pygienium-all-")); }); afterEach(async () => { resetAgentRunner(); await rm(cwd, { recursive: true, force: true }); }); it("runs every registered check in sequence and marks the run complete", async () => { registerCheck(fakeCheck("alpha")); registerCheck(fakeCheck("beta")); registerCheck(fakeCheck("gamma")); const out = await captureStdout(() => handleAllCommand("--fix", stubCtx(cwd)), ); // Every check was dispatched (scan + fix each, in registration order). expect(dispatched.filter((n) => n === "alpha").length).toBeGreaterThan(0); expect(dispatched.filter((n) => n === "beta").length).toBeGreaterThan(0); expect(dispatched.filter((n) => n === "gamma").length).toBeGreaterThan(0); // Final run-state: complete, every check complete, recon shared once. const state = await loadRunState(cwd); expect(state).toBeDefined(); expect(state!.status).toBe("complete"); expect(state!.recon.complete).toBe(true); for (const name of ["alpha", "beta", "gamma"]) { expect(state!.checks[name]?.status).toBe("complete"); } // Per-check artifacts landed on disk under the canonical root. const alphaFindings = await readFile( join(canonicalChecksRoot(cwd), "alpha", "findings.md"), "utf8", ); expect(alphaFindings).toContain("alpha findings"); const gammaChanges = await readFile( join(canonicalChecksRoot(cwd), "gamma", "changes.md"), "utf8", ); expect(gammaChanges).toContain("gamma changes"); // The summary line reports completion and the run-state path. const text = out.join("\n"); expect(text).toContain("pygienium: all-run complete"); }); it("completes cleanly in scan-only mode (no --fix)", async () => { registerCheck(fakeCheck("solo")); const out = await captureStdout(() => handleAllCommand("", stubCtx(cwd))); const state = await loadRunState(cwd); expect(state!.status).toBe("complete"); expect(state!.checks["solo"]?.status).toBe("complete"); expect(state!.checks["solo"]?.fix).toBe(false); // Scan-only still writes findings but not changes. const findings = await readFile( join(canonicalChecksRoot(cwd), "solo", "findings.md"), "utf8", ); expect(findings).toContain("solo findings"); expect(out.join("\n")).toContain("pygienium: all-run complete"); }); it("reports no checks when the registry is empty", async () => { const out = await captureStdout(() => handleAllCommand("", stubCtx(cwd))); expect(out.join("\n")).toContain("no checks registered"); }); it("is resumable: a second call reuses the existing run-state", async () => { registerCheck(fakeCheck("alpha")); await captureStdout(() => handleAllCommand("--fix", stubCtx(cwd))); const first = await loadRunState(cwd); const firstStarted = first!.startedAt; // Second run reloads the existing run-state (same startedAt). await captureStdout(() => handleAllCommand("--fix", stubCtx(cwd))); const second = await loadRunState(cwd); expect(second!.startedAt).toBe(firstStarted); expect(second!.status).toBe("complete"); }); });