/** * check-runner.test.ts — integration test for the orchestration keystone. * * Registers a throwaway "smoke" check whose fake sub-agent writes a marker * file, then invokes the per-check command handler with a stub context and * asserts the run-state marks the check complete and the marker exists. * Also asserts `--fix` runs the fix phase and records changes, while * scan-only does not. */ 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, } from "../src/agent-runner.js"; import { handleCheckCommand, type PygieniumCtx } from "../src/commands.js"; import { loadRunState, runStatePath } from "../src/run-state.js"; function smokeCheck(): CheckDefinition { return { name: "smoke", label: "Smoke", description: "Throwaway smoke check for tests", agentName: "scanner", fixAgentName: "fixer", phaseId: "scan", buildScanTask: (_cwd, scope) => `!write .pygienium/smoke.marker smoke-complete\n!echo smoke-findings for ${scope.target}`, buildFixTask: (_cwd, _scope, findings) => `!echo applied-fixes based on: ${findings.split("\n")[0] ?? ""}`, gate: () => undefined, }; } function stubCtx(cwd: string): PygieniumCtx { return { cwd, hasUI: false, ui: undefined } as PygieniumCtx; } describe("check-runner integration", () => { let cwd: string; beforeEach(async () => { clearChecks(); setAgentRunner(fakeAgentRunner); cwd = await mkdtemp(join(tmpdir(), "pygienium-smoke-")); }); afterEach(async () => { resetAgentRunner(); await rm(cwd, { recursive: true, force: true }); }); it("runs a smoke check, writes a marker, and marks the check complete", async () => { registerCheck(smokeCheck()); const check = smokeCheck(); await handleCheckCommand(check, "", stubCtx(cwd)); const state = await loadRunState(cwd); expect(state).toBeDefined(); expect(state?.checks.smoke.status).toBe("complete"); expect(state?.status).toBe("complete"); const marker = await readFile( join(cwd, ".pygienium", "smoke.marker"), "utf8", ); expect(marker.trim()).toBe("smoke-complete"); expect(state?.checks.smoke.findings).toContain("smoke-findings"); expect( state?.checks.smoke.phases.map((p) => `${p.id}=${p.status}`).join(","), ).toContain("analysis=complete"); expect( state?.checks.smoke.phases.find((p) => p.id === "fix"), ).toBeUndefined(); }); it("scan-only does not produce changes and skips the fix phase", async () => { const check = smokeCheck(); await handleCheckCommand(check, "", stubCtx(cwd)); const state = await loadRunState(cwd); expect(state?.checks.smoke.fix).toBe(false); expect(state?.checks.smoke.changes).toBeUndefined(); const fixPhase = state?.checks.smoke.phases.find((p) => p.id === "fix"); expect(fixPhase).toBeUndefined(); }); it("--fix runs the fix phase and records changes", async () => { const check = smokeCheck(); await handleCheckCommand(check, "--fix", stubCtx(cwd)); const state = await loadRunState(cwd); expect(state?.checks.smoke.status).toBe("complete"); expect(state?.checks.smoke.fix).toBe(true); expect(state?.checks.smoke.changes).toContain("applied-fixes"); expect(state?.checks.smoke.phases.find((p) => p.id === "fix")?.status).toBe( "complete", ); }); it("persists run-state.json at the expected path", async () => { const check = smokeCheck(); await handleCheckCommand(check, "", stubCtx(cwd)); expect(runStatePath(cwd)).toBe(join(cwd, ".pygienium", "run-state.json")); const raw = await readFile(runStatePath(cwd), "utf8"); expect(JSON.parse(raw).checks.smoke.status).toBe("complete"); }); it("marks a check skipped when the gate returns an error", async () => { const gated: CheckDefinition = { ...smokeCheck(), name: "gated", label: "Gated", gate: () => "no source files matched", }; await handleCheckCommand(gated, "", stubCtx(cwd)); const state = await loadRunState(cwd); expect(state?.checks.gated.status).toBe("skipped"); expect(state?.checks.gated.error).toBe("no source files matched"); }); });