138 lines
4.3 KiB
TypeScript
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 });
|
|
}
|
|
});
|
|
});
|