const { execFile } = require("node:child_process"); const { mkdtemp, rm, writeFile } = require("node:fs/promises"); const os = require("node:os"); const path = require("node:path"); const { promisify } = require("node:util"); const { pathToFileURL } = require("node:url"); const execFileAsync = promisify(execFile); const ROOT_DIR = path.resolve(__dirname, "../../../.."); let provenance; let repoDir; async function git(args, cwd = repoDir) { return execFileAsync("git", args, { cwd }); } async function createCommit(message = "initial") { await git(["add", "tracked.txt"]); await git(["commit", "-m", message]); } describe("deployment source provenance", () => { beforeAll(async () => { const scriptPath = path.join(ROOT_DIR, "deploy/trialrun/check-source-provenance.mjs"); provenance = await import(pathToFileURL(scriptPath).href); }); beforeEach(async () => { repoDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-provenance-test-")); await git(["init", "--initial-branch=main"]); await git(["config", "user.name", "Test User"]); await git(["config", "user.email", "test@example.invalid"]); await writeFile(path.join(repoDir, "tracked.txt"), "initial\n", "utf8"); await createCommit(); }); afterEach(async () => { await rm(repoDir, { recursive: true, force: true }); }); it("requires an upstream by default and permits an explicit unpublished override", () => { expect(() => provenance.collectSourceProvenance({ rootDir: repoDir })).toThrowError( expect.objectContaining({ code: "upstream_missing" }), ); expect(provenance.collectSourceProvenance({ rootDir: repoDir, allowUnpublished: true })).toMatchObject({ status: "PASS", published: false, overrideUsed: true, branch: "main", }); }); it("rejects tracked and untracked working-tree changes before any deployment", async () => { await writeFile(path.join(repoDir, "tracked.txt"), "changed\n", "utf8"); await writeFile(path.join(repoDir, "untracked.txt"), "untracked\n", "utf8"); expect(() => provenance.collectSourceProvenance({ rootDir: repoDir, allowUnpublished: true })).toThrowError( expect.objectContaining({ code: "dirty_worktree" }), ); }); it("accepts a clean commit that is contained in its upstream", async () => { const remoteDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-provenance-remote-")); try { await execFileAsync("git", ["init", "--bare", remoteDir]); await git(["remote", "add", "origin", remoteDir]); await git(["push", "--set-upstream", "origin", "main"]); expect(provenance.collectSourceProvenance({ rootDir: repoDir })).toMatchObject({ status: "PASS", published: true, overrideUsed: false, upstream: "origin/main", }); } finally { await rm(remoteDir, { recursive: true, force: true }); } }); it("rejects a clean local commit that has not reached upstream", async () => { const remoteDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-provenance-remote-")); try { await execFileAsync("git", ["init", "--bare", remoteDir]); await git(["remote", "add", "origin", remoteDir]); await git(["push", "--set-upstream", "origin", "main"]); await writeFile(path.join(repoDir, "tracked.txt"), "ahead\n", "utf8"); await createCommit("ahead"); expect(() => provenance.collectSourceProvenance({ rootDir: repoDir })).toThrowError( expect.objectContaining({ code: "commit_not_in_upstream" }), ); expect(provenance.collectSourceProvenance({ rootDir: repoDir, allowUnpublished: true })).toMatchObject({ published: false, overrideUsed: true, }); } finally { await rm(remoteDir, { recursive: true, force: true }); } }); it("refreshes the remote-tracking ref before deciding whether a commit is still published", async () => { const remoteDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-provenance-remote-")); const replacementDir = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-provenance-replacement-")); try { await execFileAsync("git", ["init", "--bare", remoteDir]); await git(["remote", "add", "origin", remoteDir]); await git(["push", "--set-upstream", "origin", "main"]); expect(provenance.collectSourceProvenance({ rootDir: repoDir })).toMatchObject({ published: true }); await execFileAsync("git", ["init", "--initial-branch=main"], { cwd: replacementDir }); await execFileAsync("git", ["config", "user.name", "Replacement"], { cwd: replacementDir }); await execFileAsync("git", ["config", "user.email", "replacement@example.invalid"], { cwd: replacementDir }); await writeFile(path.join(replacementDir, "replacement.txt"), "replacement\n", "utf8"); await execFileAsync("git", ["add", "replacement.txt"], { cwd: replacementDir }); await execFileAsync("git", ["commit", "-m", "replacement"], { cwd: replacementDir }); await execFileAsync("git", ["remote", "add", "origin", remoteDir], { cwd: replacementDir }); await execFileAsync("git", ["push", "--force", "origin", "main"], { cwd: replacementDir }); expect(provenance.collectSourceProvenance({ rootDir: repoDir })).toMatchObject({ published: true }); expect(provenance.refreshUpstreamRef({ rootDir: repoDir })).toMatchObject({ refreshed: true, upstream: "origin/main", remote: "origin", }); expect(() => provenance.collectSourceProvenance({ rootDir: repoDir })).toThrowError( expect.objectContaining({ code: "commit_not_in_upstream" }), ); } finally { await rm(remoteDir, { recursive: true, force: true }); await rm(replacementDir, { recursive: true, force: true }); } }); it("requires an allowlisted audit reason for the unpublished override", async () => { const script = path.join(ROOT_DIR, "deploy/trialrun/check-source-provenance.mjs"); await expect(execFileAsync("node", [script, `--root=${repoDir}`], { env: { ...process.env, DEPLOY_ALLOW_UNPUBLISHED_COMMIT: "1" }, })).rejects.toMatchObject({ code: 1 }); const { stdout } = await execFileAsync("node", [script, `--root=${repoDir}`], { env: { ...process.env, DEPLOY_ALLOW_UNPUBLISHED_COMMIT: "1", DEPLOY_UNPUBLISHED_REASON: "offline-disaster-recovery", }, }); expect(JSON.parse(stdout)).toMatchObject({ published: false, remoteVerified: false, overrideUsed: true, overrideRequested: true, overrideReason: "offline-disaster-recovery", }); }); });