/**
*
* Unit tests for the RFC-1327 run-twice harness: convergence acceptance, status drift detection, state drift detection, and the snapshotGitTree probe.
*
*
* - RFC-1327: harness self-tests.
*
*/
import { mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { execSync } from "node:child_process";
import { describe, expect, it } from "vitest";
import { expectRerunSafe, snapshotGitTree } from "./run-twice.ts";
describe("expectRerunSafe", () => {
it("accepts a converging command (idempotent counter)", async () => {
let count = 0;
const { first, second } = await expectRerunSafe({
run: async () => ({ status: "ok", exitCode: 0, ...(count++ === 0 ? {} : {}) }),
snapshot: () => "stable",
});
expect(first.status).toBe("ok");
expect(second.status).toBe("ok");
});
it("rejects a second run that reports a different status", async () => {
let run = 0;
await expect(
expectRerunSafe({
run: async () => ({ status: run++ === 0 ? "ok" : "failed", exitCode: run === 1 ? 0 : 1 }),
snapshot: () => "stable",
}),
).rejects.toThrow();
});
it("rejects a second run that mutates owned state", async () => {
let state = "a";
await expect(
expectRerunSafe({
run: async () => {
const cur = state;
state = state + "x";
return { status: "ok", exitCode: 0, _cur: cur } as never;
},
snapshot: () => state,
}),
).rejects.toThrow(/not rerun-safe/);
});
it("honours expectSecondRun overrides", async () => {
let run = 0;
const { second } = await expectRerunSafe({
run: async () => ({ status: run++ === 0 ? "ok" : "already-done", exitCode: 0 }),
snapshot: () => "stable",
expectSecondRun: (_f, s) => expect(s.status).toBe("already-done"),
});
expect(second.status).toBe("already-done");
});
});
describe("snapshotGitTree", () => {
it("captures HEAD + porcelain status of a repo", () => {
const dir = mkdtempSync(join(tmpdir(), "run-twice-"));
execSync("git init -q && git config user.email t@t && git config user.name t", { cwd: dir });
writeFileSync(join(dir, "a.txt"), "one");
execSync("git add a.txt && git commit -qm init", { cwd: dir });
const clean = snapshotGitTree(dir);
expect(clean).toMatch(/^[0-9a-f]{40}\n$/);
writeFileSync(join(dir, "b.txt"), "two");
const dirty = snapshotGitTree(dir);
expect(dirty).toContain("?? b.txt");
expect(dirty).not.toBe(clean);
});
});