import { constants } from "node:fs"; import { mkdir, link, 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 mkdir(path.join(repositoryRoot, "real-config")); await writeFile(path.join(repositoryRoot, "real-config/inside.json"), "{}\n"); await symlink( path.join(repositoryRoot, "real-config"), path.join(repositoryRoot, "inside-alias"), "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); await expect( readRiskCoverageInput({ repositoryRoot, relativePath: "inside-alias/inside.json", label: "policy", }), ).rejects.toThrow(/ancestor is a symlink/u); }); it("rejects an input identity swap between lstat and open", async () => { const repositoryRoot = await fixture(); const outside = await mkdtemp(path.join(tmpdir(), "risk-coverage-input-race-")); roots.push(outside); const replacement = path.join(outside, "replacement.json"); await writeFile(replacement, "{\"replacement\":true}\n"); await expect( readRiskCoverageInput( { repositoryRoot, relativePath: "config/testing/policy.json", label: "policy", }, { openFile: async (_target, flags) => open(replacement, flags), }, ), ).rejects.toThrow(/changed during validation/u); }); it("fails closed when an opened input has no stable file identity", async () => { const repositoryRoot = await fixture(); await expect( readRiskCoverageInput( { repositoryRoot, relativePath: "config/testing/policy.json", label: "policy", }, { openFile: async (target, flags) => { const handle = await open(target, flags); return { stat: async () => { const metadata = await handle.stat(); Object.defineProperties(metadata, { dev: { value: 0 }, ino: { value: 0 }, }); return metadata; }, readFile: async (encoding) => handle.readFile(encoding), close: async () => handle.close(), }; }, }, ), ).rejects.toThrow(/stable file identity unavailable/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("rejects input overwrite through a realpath or hard-link alias", 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, "{}\n"); const hardLinkInput = path.join(repositoryRoot, "config/testing/output-alias.json"); await link(destination, hardLinkInput); await expect( resolveRiskCoverageArtifactPath({ repositoryRoot, relativePath: "artifacts/quality/risk-coverage.json", inputPaths: ["config/testing/output-alias.json"], }), ).rejects.toThrow(/same file as an input/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.each([ ["EINVAL", true], ["ENOTSUP", true], ["EIO", false], ] as const)( "handles directory sync error %s with an explicit portability fallback", async (code, accepted) => { const repositoryRoot = await fixture(); const operation = writeRiskCoverageArtifactAtomic( { repositoryRoot, relativePath: `artifacts/quality/sync-${code}.json`, inputPaths: [], value: { schemaVersion: 2 }, }, { createNonce: () => code, 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 () => { throw Object.assign(new Error(`sync ${code}`), { code }); }, close: async () => handle.close(), }; }, rename, rm, }, }, ); if (accepted) { await expect(operation).resolves.toBeUndefined(); } else { await expect(operation).rejects.toMatchObject({ code }); } }, ); 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); }); });