import { constants } from "node:fs"; import { mkdir, mkdtemp, open, readFile, readdir, rename, rm, symlink, writeFile, } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { readRiskCoverageInput, resolveRiskCoverageArtifactPath, writeRiskCoverageArtifactAtomic, } from "../../scripts/lib/risk-coverage-files.ts"; const roots: string[] = []; async function fixture(): Promise { const root = await mkdtemp(path.join(tmpdir(), "risk-coverage-files-")); roots.push(root); await mkdir(path.join(root, "config/testing"), { recursive: true }); await mkdir(path.join(root, "artifacts/tests/coverage"), { recursive: true }); await writeFile(path.join(root, "config/testing/policy.json"), "{\"policy\":true}\n"); await writeFile(path.join(root, "artifacts/tests/coverage/summary.json"), "{\"total\":{}}\n"); return root; } afterEach(async () => { await Promise.all( roots.splice(0).map((root) => rm(root, { recursive: true, force: true })), ); }); describe("risk coverage CLI files", () => { it("reads only exact contained regular input files", async () => { const repositoryRoot = await fixture(); await expect( readRiskCoverageInput({ repositoryRoot, relativePath: "config/testing/policy.json", label: "policy", }), ).resolves.toMatchObject({ relativePath: "config/testing/policy.json", text: "{\"policy\":true}\n", }); await expect( readRiskCoverageInput({ repositoryRoot, relativePath: path.join(repositoryRoot, "config/testing/policy.json"), label: "policy", }), ).rejects.toThrow(/repository-relative POSIX/u); await expect( readRiskCoverageInput({ repositoryRoot, relativePath: "config\\testing\\policy.json", label: "policy", }), ).rejects.toThrow(/repository-relative POSIX/u); }); it("rejects final and ancestor input symlinks", async () => { const repositoryRoot = await fixture(); const outside = await mkdtemp(path.join(tmpdir(), "risk-coverage-input-outside-")); roots.push(outside); await writeFile(path.join(outside, "outside.json"), "{}\n"); await symlink( path.join(outside, "outside.json"), path.join(repositoryRoot, "config/testing/link.json"), ); await symlink(outside, path.join(repositoryRoot, "linked-config"), "dir"); await expect( readRiskCoverageInput({ repositoryRoot, relativePath: "config/testing/link.json", label: "policy", }), ).rejects.toThrow(/symlink/u); await expect( readRiskCoverageInput({ repositoryRoot, relativePath: "linked-config/outside.json", label: "policy", }), ).rejects.toThrow(/outside repository|symlink/u); }); it("confines artifact output and rejects input overwrite or symlink ancestors", async () => { const repositoryRoot = await fixture(); await expect( resolveRiskCoverageArtifactPath({ repositoryRoot, relativePath: "artifacts/quality/risk-coverage.json", inputPaths: ["config/testing/policy.json", "artifacts/tests/coverage/summary.json"], }), ).resolves.toBe(path.join(repositoryRoot, "artifacts/quality/risk-coverage.json")); await expect( resolveRiskCoverageArtifactPath({ repositoryRoot, relativePath: "config/testing/result.json", inputPaths: [], }), ).rejects.toThrow(/artifacts\/quality/u); await expect( resolveRiskCoverageArtifactPath({ repositoryRoot, relativePath: "artifacts/quality/risk-coverage.json", inputPaths: ["artifacts/quality/risk-coverage.json"], }), ).rejects.toThrow(/must not overwrite an input/u); const outside = await mkdtemp(path.join(tmpdir(), "risk-coverage-output-outside-")); roots.push(outside); await rm(path.join(repositoryRoot, "artifacts/quality"), { recursive: true, force: true }); await symlink(outside, path.join(repositoryRoot, "artifacts/quality"), "dir"); await expect( resolveRiskCoverageArtifactPath({ repositoryRoot, relativePath: "artifacts/quality/risk-coverage.json", inputPaths: [], }), ).rejects.toThrow(/symlink/u); }); it("syncs an exclusive sibling temp before atomic rename", async () => { const repositoryRoot = await fixture(); const observed: string[] = []; let observedFlags = 0; await writeRiskCoverageArtifactAtomic( { repositoryRoot, relativePath: "artifacts/quality/risk-coverage.json", inputPaths: ["config/testing/policy.json", "artifacts/tests/coverage/summary.json"], value: { schemaVersion: 2, status: "PASS" }, }, { createNonce: () => "owned", fileSystem: { openFile: async (target, flags, mode) => { observedFlags = flags; const handle = await open(target, flags, mode); return { writeFile: async (data) => handle.writeFile(data, "utf8"), sync: async () => { observed.push("file-sync"); await handle.sync(); }, close: async () => handle.close(), }; }, openDirectory: async (target) => { const handle = await open(target, constants.O_RDONLY); return { sync: async () => { observed.push("directory-sync"); await handle.sync(); }, close: async () => handle.close(), }; }, rename: async (source, destination) => { observed.push("rename"); await rename(source, destination); }, rm, }, }, ); expect(observedFlags & constants.O_EXCL).toBe(constants.O_EXCL); expect(observedFlags & constants.O_NOFOLLOW).toBe(constants.O_NOFOLLOW); expect(observed).toEqual(["file-sync", "rename", "directory-sync"]); expect( JSON.parse( await readFile( path.join(repositoryRoot, "artifacts/quality/risk-coverage.json"), "utf8", ), ), ).toEqual({ schemaVersion: 2, status: "PASS" }); }); it("cleans its owned temp and preserves destination when publication fails", async () => { const repositoryRoot = await fixture(); const outputDirectory = path.join(repositoryRoot, "artifacts/quality"); await mkdir(outputDirectory, { recursive: true }); const destination = path.join(outputDirectory, "risk-coverage.json"); await writeFile(destination, "previous\n"); await expect( writeRiskCoverageArtifactAtomic( { repositoryRoot, relativePath: "artifacts/quality/risk-coverage.json", inputPaths: [], value: { schemaVersion: 2 }, }, { createNonce: () => "owned", fileSystem: { openFile: async (target, flags, mode) => { const handle = await open(target, flags, mode); return { writeFile: async (data) => handle.writeFile(data, "utf8"), sync: async () => handle.sync(), close: async () => handle.close(), }; }, openDirectory: async (target) => { const handle = await open(target, constants.O_RDONLY); return { sync: async () => handle.sync(), close: async () => handle.close() }; }, rename: async () => { throw new Error("injected rename failure"); }, rm, }, }, ), ).rejects.toThrow(/injected rename failure/u); await expect(readFile(destination, "utf8")).resolves.toBe("previous\n"); expect(await readdir(outputDirectory)).toEqual(["risk-coverage.json"]); }); it("has no changed-files gate in the executable", async () => { const source = await readFile("scripts/check-risk-coverage.ts", "utf8"); expect(source).not.toMatch(/changedFiles|changed-files/u); }); });