import assert from "node:assert/strict"; import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; import { CHECK_IDS, canonicalIntegrationBase, cleanupOwnedRun, createOwnedRun, readAndValidateOwnership, runIntegration, validateReport, validateRunRoot, } from "./p5-acceptance.mjs"; const roots = []; async function fakeRepository() { const root = await mkdtemp(join(tmpdir(), "p5-acceptance-repo-")); roots.push(root); await mkdir(join(root, ".artifacts", "p5-integration"), { recursive: true }); await mkdir(join(root, ".artifacts", "p2-integration"), { recursive: true }); await mkdir(join(root, ".artifacts", "p1-integration"), { recursive: true }); await mkdir(join(root, ".artifacts", "manual-acceptance", "p11"), { recursive: true }); return root; } test.afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); test("run roots are only canonical direct p5 integration children", async () => { const repositoryRoot = await fakeRepository(); const base = canonicalIntegrationBase(repositoryRoot); const id = `p5-${"a".repeat(32)}`; assert.equal(validateRunRoot(repositoryRoot, join(base, id), id), join(base, id)); for (const candidate of [ base, join(repositoryRoot, ".artifacts", "manual-acceptance", "p11"), join(repositoryRoot, ".artifacts", "p1-integration", id), join(repositoryRoot, ".artifacts", "p2-integration", id), join(base, id, "nested"), join(base, "foreign"), ]) { assert.throws(() => validateRunRoot(repositoryRoot, candidate, id)); } assert.throws(() => validateRunRoot(repositoryRoot, join(base, `p5-${"A".repeat(32)}`), `p5-${"A".repeat(32)}`)); }); test("cleanup refuses p1, p2, p11, manual, sibling, and wrong-nonce roots", async () => { const repositoryRoot = await fakeRepository(); const run = await createOwnedRun({ repositoryRoot }); await readAndValidateOwnership({ repositoryRoot, runRoot: run.root, expectedNonce: run.nonce }); for (const bad of [ join(repositoryRoot, ".artifacts", "p1-integration", `p1-${"b".repeat(32)}`), join(repositoryRoot, ".artifacts", "p2-integration", `p2-${"c".repeat(32)}`), join(repositoryRoot, ".artifacts", "manual-acceptance", "p11"), join(canonicalIntegrationBase(repositoryRoot), `p5-${"d".repeat(32)}`), ]) { await assert.rejects(cleanupOwnedRun({ repositoryRoot, runRoot: bad, expectedNonce: run.nonce })); } await assert.rejects(cleanupOwnedRun({ repositoryRoot, runRoot: run.root, expectedNonce: "0".repeat(64) })); }); test("cleanup removes exactly one owned p5 root", async () => { const repositoryRoot = await fakeRepository(); const run = await createOwnedRun({ repositoryRoot }); const sibling = join(canonicalIntegrationBase(repositoryRoot), `p5-${"e".repeat(32)}`); await mkdir(sibling); await writeFile(join(sibling, "sentinel"), "foreign"); await cleanupOwnedRun({ repositoryRoot, runRoot: run.root, expectedNonce: run.nonce }); await assert.rejects(readFile(join(run.root, "ownership.json"))); assert.equal(await readFile(join(sibling, "sentinel"), "utf8"), "foreign"); }); function resultFor(id) { return { id, status: "PASS", startedAt: "2026-08-12T00:00:00.000Z", finishedAt: "2026-08-12T00:00:01.000Z", commands: ["node"], artifacts: [{ path: `logs/${id}.json`, sha256: "a".repeat(64) }], }; } test("report validation requires exact p5 identity, check order, and unique artifacts", () => { const report = { schemaVersion: 1, runId: `p5-${"f".repeat(32)}`, startedAt: "2026-08-12T00:00:00.000Z", finishedAt: "2026-08-12T00:00:10.000Z", command: "p5-acceptance integration --keep", overall: "PASS", checks: CHECK_IDS.map(resultFor), }; assert.doesNotThrow(() => validateReport(report)); const invalid = structuredClone(report); invalid.runId = `p2-${"f".repeat(32)}`; assert.throws(() => validateReport(invalid)); const duplicate = structuredClone(report); duplicate.checks[1].artifacts[0].path = duplicate.checks[0].artifacts[0].path; assert.throws(() => validateReport(duplicate), /duplicated/); const reordered = structuredClone(report); reordered.checks.reverse(); reordered.overall = "FAIL"; assert.throws(() => validateReport(reordered)); }); test("public wrapper uses a strict empty environment", async () => { const wrapper = await readFile(join(dirname(fileURLToPath(import.meta.url)), "..", "..", "scripts", "p5-acceptance.sh"), "utf8"); assert.match(wrapper, /safe_env=\(\/usr\/bin\/env -i/); assert.doesNotMatch(wrapper, /LANG|LC_ALL|TZ/); assert.doesNotMatch(wrapper, /P5_ACCEPTANCE_FAIL_AT/); }); test("synthetic integration cleans up successful non-kept runs", async () => { const repositoryRoot = await fakeRepository(); const result = await runIntegration({ repositoryRoot, keep: false, env: { P5_ACCEPTANCE_SYNTHETIC: "1" } }); assert.equal(result.exitCode, 0); assert.equal(result.retained, false); await assert.rejects(readFile(join(result.runRoot, "ownership.json"))); }); test("synthetic integration retains kept runs with bounded reports", async () => { const repositoryRoot = await fakeRepository(); const result = await runIntegration({ repositoryRoot, keep: true, env: { P5_ACCEPTANCE_SYNTHETIC: "1" } }); assert.equal(result.exitCode, 0); assert.equal(result.retained, true); const report = JSON.parse(await readFile(join(result.runRoot, "report.json"), "utf8")); assert.equal(report.overall, "PASS"); const reportMd = await readFile(join(result.runRoot, "report.md"), "utf8"); assert.match(reportMd, /P5 automated integration: PASS/); assert.match(reportMd, /P5 manual acceptance: PENDING/); const reportJsonStat = await stat(join(result.runRoot, "report.json")); const reportMdStat = await stat(join(result.runRoot, "report.md")); assert.ok(reportJsonStat.size <= 64 * 1024, `report.json too large: ${reportJsonStat.size}`); assert.ok(reportMdStat.size <= 32 * 1024, `report.md too large: ${reportMdStat.size}`); }); test("synthetic injected failure retains the owned run and records a single failed report", async () => { const repositoryRoot = await fakeRepository(); const result = await runIntegration({ repositoryRoot, keep: false, env: { P5_ACCEPTANCE_SYNTHETIC: "1", P5_ACCEPTANCE_FAIL_AT: CHECK_IDS[2] }, }); assert.equal(result.exitCode, 1); assert.equal(result.retained, true); const report = JSON.parse(await readFile(join(result.runRoot, "report.json"), "utf8")); assert.equal(report.overall, "FAIL"); const failed = report.checks.find((check) => check.id === CHECK_IDS[2]); assert.equal(failed.status, "FAIL"); const roots = await readFile(join(result.runRoot, "ownership.json"), "utf8"); assert.match(roots, /p5-acceptance/); });