/* Unit tests for signed commit covering its documented public contract and regression-sensitive edge cases. Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. RFC-0560: initial unit tests for createSignedCommit. */ import { test, expect, beforeEach, afterEach } from "vitest"; import { execSync } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; import os from "node:os"; import { createSignedCommit } from "./signed-commit.ts"; import { generateKeypair } from "@warpgogol/werkstatt-shared/passport/sign"; function git(cwd: string, args: string): string { return execSync(`git ${args}`, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }).trim(); } let tmpDir: string; let workpieceDir: string; beforeEach(async () => { tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0560-test-")); workpieceDir = path.join(tmpDir, "workpiece"); await fs.mkdir(workpieceDir, { recursive: true }); git(workpieceDir, "init"); git(workpieceDir, 'config user.email "test@example.com"'); git(workpieceDir, 'config user.name "Test"'); // Create initial commit await fs.writeFile(path.join(workpieceDir, "README.md"), "# Initial\n"); git(workpieceDir, "add -A"); git(workpieceDir, 'commit -m "initial"'); }); afterEach(async () => { await fs.rm(tmpDir, { recursive: true, force: true }); }); test("createSignedCommit produces signed commit with Werkstatt-Actor and Werkstatt-Signature trailers", async () => { const keyPair = await generateKeypair(); await fs.writeFile(path.join(workpieceDir, "file.txt"), "content\n"); const result = await createSignedCommit( workpieceDir, "test commit", "did:key:z6Mktest", keyPair.privateKeyHex, ); expect(result.signed).toBe(true); expect(result.actorId).toBe("did:key:z6Mktest"); expect(result.signature).toBeTruthy(); expect(result.commitSha).toBeTruthy(); const commitMessage = git(workpieceDir, "log -1 --format=%B"); expect(commitMessage).toContain("Werkstatt-Actor: did:key:z6Mktest"); expect(commitMessage).toContain("Werkstatt-Signature:"); }); test("createSignedCommit returns signed=false when there are no changes", async () => { const keyPair = await generateKeypair(); const headBefore = git(workpieceDir, "rev-parse HEAD"); const result = await createSignedCommit( workpieceDir, "no changes", "did:key:z6Mktest", keyPair.privateKeyHex, ); expect(result.signed).toBe(false); expect(result.actorId).toBeNull(); expect(result.signature).toBeNull(); expect(result.commitSha).toBe(headBefore); }); test("ADR-0104: scopeFiles commits only named paths — staged residue stays staged, never committed", async () => { const keyPair = await generateKeypair(); // The bookkeeping target plus unrelated staged residue (the m000164 sweep // scenario: an earlier `git add -A` left operator content in the index). await fs.writeFile(path.join(workpieceDir, "snapshot.yaml"), "generated: true\n"); await fs.writeFile(path.join(workpieceDir, "residue.md"), "operator content\n"); git(workpieceDir, "add residue.md"); // residue staged BEFORE the scoped commit const result = await createSignedCommit( workpieceDir, "chore: bootstrap snapshot", "did:key:z6Mktest", keyPair.privateKeyHex, ["snapshot.yaml"], ); expect(result.signed).toBe(true); // The commit contains exactly the scoped file — including after --amend. expect(git(workpieceDir, "show --name-only --format= HEAD")).toBe("snapshot.yaml"); // Residue remains staged for the operator to deal with. expect(git(workpieceDir, "status --porcelain")).toBe("A residue.md"); const body = git(workpieceDir, "log -1 --format=%B"); expect(body).toContain("Werkstatt-Actor: did:key:z6Mktest"); }); test("ADR-0104: scopeFiles with only residue staged returns no-changes (signed=false)", async () => { const keyPair = await generateKeypair(); const headBefore = git(workpieceDir, "rev-parse HEAD"); // Stage residue outside the scope; the scoped path itself is clean. await fs.writeFile(path.join(workpieceDir, "residue.md"), "x\n"); git(workpieceDir, "add residue.md"); const result = await createSignedCommit( workpieceDir, "noop", "did:key:z6Mktest", keyPair.privateKeyHex, ["README.md"], // unchanged file that exists ); expect(result.signed).toBe(false); expect(result.commitSha).toBe(headBefore); // Residue must still be staged — never swept, never unstaged. expect(git(workpieceDir, "status --porcelain")).toBe("A residue.md"); }); test("createSignedCommit: post-amend SHA differs from pre-amend SHA", async () => { const keyPair = await generateKeypair(); await fs.writeFile(path.join(workpieceDir, "file2.txt"), "content2\n"); const result = await createSignedCommit( workpieceDir, "test commit 2", "did:key:z6Mktest", keyPair.privateKeyHex, ); // The commit SHA after amend should be valid and the commit should exist const headSha = git(workpieceDir, "rev-parse HEAD"); expect(result.commitSha).toBe(headSha); expect(result.signed).toBe(true); });