Files
pygienium/tests/run-state.test.ts
Michael Freno c605a709fb feat(run): resume-aware per-check runs, verify hooks, run-state hardening
/pygienium-<check> is now resume-aware (terminal checks skipped unless
--fresh) and shares run-state with all/resume; every check gets a verify
hook that fails loudly when a sub-agent returns ok with no artifact;
run-state clears stale errors on retry success and reconciles a run as
failed only when every check failed. Drops the superseded
hygiene-state.ts model.
2026-08-09 16:45:30 -04:00

138 lines
4.3 KiB
TypeScript

/**
* run-state.test.ts — run-state reconciliation, error hygiene, and the
* .gitignore guard (issues surfaced by the MagnaFluo all-run: "partial" for
* all-failed runs, stale errors on completed checks, staged artifacts).
*/
import { describe, expect, it, beforeEach, afterEach } from "bun:test";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
ensureRunStateIgnored,
initRunState,
markCheckStatus,
reconcileRunStatus,
} from "../src/run-state.js";
/** Build a run state whose checks carry the given statuses. */
function stateWith(
...statuses: Array<[name: string, status: string]>
): ReturnType<typeof initRunState> {
const state = initRunState(
"/virtual/cwd",
statuses.map(([name]) => ({ name, label: name })),
);
for (const [name, status] of statuses) {
markCheckStatus(
state,
name,
status as "complete" | "failed" | "skipped",
status === "failed" ? "boom" : undefined,
);
}
return state;
}
describe("reconcileRunStatus", () => {
it("is in_progress while nothing is terminal", () => {
expect(reconcileRunStatus(stateWith())).toBe("in_progress");
expect(
reconcileRunStatus(initRunState("/virt", [{ name: "a", label: "a" }])),
).toBe("in_progress");
});
it("is complete only when every check is complete", () => {
expect(
reconcileRunStatus(stateWith(["a", "complete"], ["b", "complete"])),
).toBe("complete");
});
it("is partial when some checks failed and others completed", () => {
expect(
reconcileRunStatus(stateWith(["a", "complete"], ["b", "failed"])),
).toBe("partial");
});
it("is partial when checks were skipped", () => {
expect(
reconcileRunStatus(stateWith(["a", "complete"], ["b", "skipped"])),
).toBe("partial");
});
it("is failed when every check failed (not partial)", () => {
expect(
reconcileRunStatus(stateWith(["a", "failed"], ["b", "failed"])),
).toBe("failed");
expect(reconcileRunStatus(stateWith(["a", "failed"]))).toBe("failed");
});
it("is partial for a mixed failed/skipped run (some degraded, none ok)", () => {
expect(
reconcileRunStatus(stateWith(["a", "failed"], ["b", "skipped"])),
).toBe("partial");
});
});
describe("check error hygiene", () => {
it("a failed check records its error", () => {
const s = stateWith(["a", "failed"]);
expect(s.checks.a?.error).toBe("boom");
});
it("a later success clears the stale error (resume-complete invariant)", () => {
const s = stateWith(["a", "failed"]);
expect(s.checks.a?.error).toBe("boom");
markCheckStatus(s, "a", "complete");
expect(s.checks.a?.error).toBeUndefined();
expect(s.checks.a?.status).toBe("complete");
});
});
describe("ensureRunStateIgnored", () => {
let cwd: string;
beforeEach(async () => {
cwd = await mkdtemp(join(tmpdir(), "pygium-git-"));
await mkdir(join(cwd, ".git"), { recursive: true }); // pretend it's a work tree
});
afterEach(async () => {
await rm(cwd, { recursive: true, force: true });
});
it("creates .gitignore with .pygienium/ when absent", async () => {
expect(await ensureRunStateIgnored(cwd)).toBe(true);
const content = await readFile(join(cwd, ".gitignore"), "utf8");
expect(content).toContain(".pygienium/");
});
it("appends to an existing .gitignore without the marker", async () => {
await writeFile(join(cwd, ".gitignore"), "node_modules/\n", "utf8");
expect(await ensureRunStateIgnored(cwd)).toBe(true);
const content = await readFile(join(cwd, ".gitignore"), "utf8");
expect(content).toContain(".pygienium/");
expect(content).toContain("node_modules/");
});
it("leaves an existing marker untouched and reports no change", async () => {
await writeFile(
join(cwd, ".gitignore"),
".pygienium/\nnode_modules/\n",
"utf8",
);
expect(await ensureRunStateIgnored(cwd)).toBe(false);
const content = await readFile(join(cwd, ".gitignore"), "utf8");
expect(content).toBe(".pygienium/\nnode_modules/\n");
});
it("is a no-op outside a git work tree", async () => {
const plain = await mkdtemp(join(tmpdir(), "pygium-nogit-"));
try {
expect(await ensureRunStateIgnored(plain)).toBe(false);
await expect(
readFile(join(plain, ".gitignore"), "utf8"),
).rejects.toThrow();
} finally {
await rm(plain, { recursive: true, force: true });
}
});
});