import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { access, lstat, readFile, rm } from "node:fs/promises"; import { join } from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; import { dirname, resolve } from "node:path"; import { cleanupManual, prepareManual, readManualOwnership, serveManual, stopManual, } from "./p11-manual-acceptance.mjs"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); const fixedRoot = join(repoRoot, ".artifacts", "manual-acceptance", "p11"); async function safeCleanup() { try { const owned = await readManualOwnership({ repositoryRoot: repoRoot }); if (owned.status === "RUNNING") await stopManual({ repositoryRoot: repoRoot }).catch(() => {}); await cleanupManual({ repositoryRoot: repoRoot }).catch(() => {}); } catch { await rm(fixedRoot, { recursive: true, force: true }).catch(() => {}); } } test.beforeEach(async () => { await safeCleanup(); }); test.afterEach(async () => { await safeCleanup(); }); test("prepare creates an independent pending lab without verdict", { concurrency: false }, async () => { const root = await prepareManual({ repositoryRoot: repoRoot }); assert.equal(root, fixedRoot); const owned = await readManualOwnership({ repositoryRoot: repoRoot }); assert.equal(owned.kind, "p11-manual-acceptance"); assert.equal(owned.status, "PENDING"); await access(join(root, "GUIDE.md")); await access(join(root, "author", "thoth-workspaces.yaml")); await access(join(root, "author", "p11-filesystem", "evidence", "guide.md")); await access(join(root, "requests", "validate-p11-filesystem.json")); await access(join(root, "commands", "http-01-status.sh")); await access(join(root, "commands", "render-1.sh")); await assert.rejects(access(join(root, "VERDICT.md"))); const guide = await readFile(join(root, "GUIDE.md"), "utf8"); assert.match(guide, /VERDICT\.md/); assert.match(guide, /read-only/); }); test("serve, stop, and cleanup manage the owned backend and frontend listeners", { concurrency: false }, async () => { await prepareManual({ repositoryRoot: repoRoot }); const running = await serveManual({ repositoryRoot: repoRoot }); assert.equal(running.status, "RUNNING"); assert.equal(typeof running.backend.pid, "number"); assert.equal(typeof running.frontend.pid, "number"); const status = await fetch("http://127.0.0.1:8791/workspace-registry/status"); assert.equal(status.status, 200); const frontend = await fetch("http://127.0.0.1:8792/"); assert.equal(frontend.status, 200); await assert.rejects(cleanupManual({ repositoryRoot: repoRoot }), /still live/); const stopped = await stopManual({ repositoryRoot: repoRoot }); assert.equal(stopped.status, "STOPPED"); await cleanupManual({ repositoryRoot: repoRoot }); await assert.rejects(lstat(fixedRoot)); }); test("stop fails closed when ownership is tampered", { concurrency: false }, async () => { await prepareManual({ repositoryRoot: repoRoot }); const running = await serveManual({ repositoryRoot: repoRoot }); const ownershipPath = join(fixedRoot, "ownership.json"); const digestPath = join(fixedRoot, "ownership.sha256"); const original = JSON.parse(await readFile(ownershipPath, "utf8")); const tampered = { ...original, backend: { ...original.backend, pid: original.backend.pid + 1 } }; await rm(ownershipPath); await readFile(join(fixedRoot, "logs", "backend.log")); await import("node:fs/promises").then(({ writeFile }) => writeFile(ownershipPath, `${JSON.stringify(tampered, null, 2)} `)); await assert.rejects(stopManual({ repositoryRoot: repoRoot }), /manual ownership digest mismatch/); const restored = `${JSON.stringify(running, null, 2)} `; const restoredDigest = `${createHash("sha256").update(restored).digest("hex")} `; await import("node:fs/promises").then(({ writeFile }) => Promise.all([writeFile(ownershipPath, restored), writeFile(digestPath, restoredDigest)])); await stopManual({ repositoryRoot: repoRoot }); });