import { spawnSync } from "node:child_process"; import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { loadCiGateContract } from "../../../scripts/contracts/ci-gates.ts"; const temporaryRoots: string[] = []; afterEach(async () => { await Promise.all( temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true }), ), ); }); describe("CI-runner command-generated evidence freshness", () => { it("rejects stale command-generated evidence from a successful no-op producer", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-gate-stale-evidence-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); await mkdir(path.join(root, "artifacts/tests"), { recursive: true }); const contract = JSON.parse( JSON.stringify(await loadCiGateContract(process.cwd())), ) as Record; const command = contract.commands.find( (entry: Record) => entry.id === "test-runtime-schema", ); command.script = "test:stale-evidence-noop"; const evidence = contract.artifacts.find( (entry: Record) => entry.path === "artifacts/tests/runtime-schema.xml", ); const packageDocument = JSON.parse( await readFile("package.json", "utf8"), ) as { scripts: Record }; packageDocument.scripts[command.script] = "true"; await writeFile( path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`, ); await writeFile( path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`, ); await writeFile( path.join(root, evidence.path), '\n', ); const result = spawnSync( process.execPath, [path.resolve("scripts/run-ci-gate.ts"), "FE-GATE-004"], { cwd: root, encoding: "utf8", env: { ...process.env, CI: "false" } }, ); expect(result.status).toBe(1); expect( await readFile( path.join(root, "artifacts/quality/gates/FE-GATE-004.txt"), "utf8", ), ).toMatch(/not freshly produced/i); }); it("accepts a fresh deterministic rewrite with identical evidence bytes", async () => { const root = await mkdtemp( path.join(tmpdir(), "ci-gate-identical-rewrite-"), ); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); await mkdir(path.join(root, "artifacts/tests"), { recursive: true }); const contract = JSON.parse( JSON.stringify(await loadCiGateContract(process.cwd())), ) as Record; const command = contract.commands.find( (entry: Record) => entry.id === "test-runtime-schema", ); command.script = "test:identical-evidence-rewrite"; const evidence = contract.artifacts.find( (entry: Record) => entry.path === "artifacts/tests/runtime-schema.xml", ); const evidenceBytes = '\n'; const packageDocument = JSON.parse( await readFile("package.json", "utf8"), ) as { scripts: Record }; packageDocument.scripts[command.script] = `node -e 'require("node:fs").writeFileSync("${evidence.path}", Buffer.from("${Buffer.from(evidenceBytes).toString("base64")}", "base64"))'`; await writeFile( path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`, ); await writeFile( path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`, ); await writeFile(path.join(root, evidence.path), evidenceBytes); const result = spawnSync( process.execPath, [path.resolve("scripts/run-ci-gate.ts"), "FE-GATE-004"], { cwd: root, encoding: "utf8", env: { ...process.env, CI: "false" } }, ); expect(result.status).toBe(0); expect(result.stdout).toMatch(/FE-GATE-004 runtime-schema: PASS/); }); }); describe("CI-runner gate output budget", () => { it("caps aggregate gate output at the log schema before later commands can accumulate", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-gate-output-budget-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); const contract = JSON.parse( JSON.stringify(await loadCiGateContract(process.cwd())), ) as Record; const gate = contract.gates.find( (entry: Record) => entry.id === "FE-GATE-001", ); const command = contract.commands.find( (entry: Record) => entry.id === gate.commandIds[0], ); command.script = "test:huge-output"; const logArtifact = contract.artifacts.find( (entry: Record) => entry.id === gate.logArtifactId, ); const logSchema = contract.artifactSchemas.find( (entry: Record) => entry.id === logArtifact.schemaId, ); logSchema.maxBytes = 8_192; const packageDocument = JSON.parse( await readFile("package.json", "utf8"), ) as { scripts: Record }; packageDocument.scripts["test:huge-output"] = "node -e \"process.stdout.write('x'.repeat(20000))\""; await writeFile( path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`, ); await writeFile( path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`, ); const result = spawnSync( process.execPath, [path.resolve("scripts/run-ci-gate.ts"), "FE-GATE-001"], { cwd: root, encoding: "utf8", env: { ...process.env, CI: "false" }, timeout: 15_000, }, ); expect(result.status).toBe(1); const log = await readFile(path.join(root, logArtifact.path)); expect(log.byteLength).toBeLessThanOrEqual(8_192); expect(log.toString("utf8")).toMatch( /aggregate output|INFRASTRUCTURE_FAILURE/i, ); }, 20_000); });