import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, expect, it } from "vitest"; import { loadCiGateContract } from "../../scripts/contracts/ci-gates.ts"; import { assertNoRuntimeImports, pruneRemovalFixtureCiContract, removeRuntimeDependentTests, runtimeImportGraph, } from "../../scripts/lib/removal-fixture.ts"; const temporaryRoots: string[] = []; afterEach(async () => { await Promise.all( temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })), ); }); it("rejects an empty scanned module universe", async () => { const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-empty-modules-")); temporaryRoots.push(root); await expect(runtimeImportGraph(root, ["src/runtime"])).rejects.toThrow( /scanned module universe is empty/i, ); }); it("rejects a runtime removal scan that discovers no dependent tests", async () => { const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-zero-dependent-tests-")); temporaryRoots.push(root); await mkdir(path.join(root, "src/runtime"), { recursive: true }); await mkdir(path.join(root, "tests"), { recursive: true }); await writeFile(path.join(root, "src/runtime/index.ts"), "export const runtime = true;\n"); await writeFile(path.join(root, "tests/unrelated.test.ts"), "export const unrelated = true;\n"); await expect(removeRuntimeDependentTests(root, ["src/runtime"])).rejects.toThrow( /no runtime-dependent tests discovered/i, ); }); it("allows zero runtime imports after the runtime has been removed", async () => { const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-post-removal-")); temporaryRoots.push(root); await mkdir(path.join(root, "tests"), { recursive: true }); await writeFile(path.join(root, "tests/unrelated.test.ts"), "export const unrelated = true;\n"); await expect( assertNoRuntimeImports(root, ["src/runtime"], "fixture runtime"), ).resolves.toBeUndefined(); }); it("prunes removed command and evidence references from a reduced CI contract", async () => { const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-contract-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); await writeFile(path.join(root, "config/ci/gates.json"), await readFile("config/ci/gates.json")); await writeFile(path.join(root, "package.json"), await readFile("package.json")); await pruneRemovalFixtureCiContract({ root, removedScripts: new Set(["test:reference-feature"]), removedEvidencePathFragments: ["reference-feature.xml"], }); const contract = await loadCiGateContract(root, { mode: "removal-fixture" }); expect(contract.commands.some(({ script }) => script === "test:reference-feature")).toBe(false); expect(contract.artifacts.some(({ path }) => path.includes("reference-feature.xml"))).toBe(false); expect(contract.gates.some(({ commandIds }) => commandIds.includes("test-reference-feature"))).toBe(false); expect(contract.gates.some(({ evidenceArtifactIds }) => evidenceArtifactIds.includes("artifact-artifacts-tests-reference-feature-xml") )).toBe(false); }); it("rejects pruning that leaves a reduced gate without commands", async () => { const root = await mkdtemp(path.join(tmpdir(), "removal-fixture-empty-gate-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); await writeFile(path.join(root, "config/ci/gates.json"), await readFile("config/ci/gates.json")); await writeFile(path.join(root, "package.json"), await readFile("package.json")); await expect(pruneRemovalFixtureCiContract({ root, removedScripts: new Set(["test:runtime-schema"]), removedEvidencePathFragments: ["runtime-schema.xml"], })).rejects.toThrow(/commandIds|too small|at least 1/i); });