test(FN-4433): complete Step 3 — cover file-scope violation audit emission

Fusion-Task-Id: FN-4433
Fusion-Task-Lineage: 63ee20ca-1e04-4b42-b386-a1e586aac889
This commit is contained in:
Fusion
2026-05-14 04:27:12 -07:00
committed by gsxdsm
parent 2c5d0aba4c
commit 880f755787
2 changed files with 109 additions and 3 deletions

View File

@@ -6,6 +6,7 @@ import {
assertSquashOverlapsFileScope,
attemptWithSideStrategy,
commitOrAmendMergeWithFixes,
enforceSquashFileScopeInvariant,
executeMergeAttempt,
FileScopeViolationError,
} from "../merger.js";
@@ -195,6 +196,108 @@ describe("assertSquashOverlapsFileScope", () => {
});
});
describe("enforceSquashFileScopeInvariant audit emission", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("emits run_audit event on file-scope violation", async () => {
const store = createInvariantStore(["packages/engine/src/merger.ts"]);
const auditor = { git: vi.fn().mockResolvedValue(undefined) };
mockedExecSync.mockImplementation((cmd: any) => {
const cmdStr = String(cmd);
if (cmdStr === "git diff --cached --name-only") return "packages/core/src/store.ts";
if (cmdStr === "git reset --merge") return "";
return "";
});
await expect(enforceSquashFileScopeInvariant({
store: store as never,
taskId: "FN-4073",
rootDir: "/tmp/root",
task: await (store as any).getTask("FN-4073"),
resetLabel: "file-scope invariant violation",
auditor: auditor as any,
})).rejects.toBeInstanceOf(FileScopeViolationError);
expect(auditor.git).toHaveBeenCalledTimes(1);
expect(auditor.git).toHaveBeenCalledWith({
type: "merge:file-scope-violation",
target: "FN-4073",
metadata: {
resetLabel: "file-scope invariant violation",
stagedFiles: ["packages/core/src/store.ts"],
declaredScope: ["packages/engine/src/merger.ts"],
stagedFileCount: 1,
declaredScopeCount: 1,
},
});
});
it("does not emit when scopeOverride bypasses invariant", async () => {
const store = createInvariantStore(["packages/engine/src/merger.ts"], { scopeOverride: true });
const auditor = { git: vi.fn().mockResolvedValue(undefined) };
mockedExecSync.mockImplementation(() => "packages/core/src/store.ts");
await expect(enforceSquashFileScopeInvariant({
store: store as never,
taskId: "FN-4073",
rootDir: "/tmp/root",
task: await (store as any).getTask("FN-4073"),
resetLabel: "file-scope invariant violation",
auditor: auditor as any,
})).resolves.toBeUndefined();
expect(auditor.git).not.toHaveBeenCalled();
});
it("does not mask original violation when audit emission fails", async () => {
const store = createInvariantStore(["packages/engine/src/merger.ts"]);
const auditor = { git: vi.fn().mockRejectedValue(new Error("audit boom")) };
mockedExecSync.mockImplementation((cmd: any) => {
const cmdStr = String(cmd);
if (cmdStr === "git diff --cached --name-only") return "packages/core/src/store.ts";
if (cmdStr === "git reset --merge") return "";
return "";
});
await expect(enforceSquashFileScopeInvariant({
store: store as never,
taskId: "FN-4073",
rootDir: "/tmp/root",
task: await (store as any).getTask("FN-4073"),
resetLabel: "file-scope invariant violation",
auditor: auditor as any,
})).rejects.toBeInstanceOf(FileScopeViolationError);
expect(store.appendAgentLog).toHaveBeenCalledWith(
"FN-4073",
expect.stringContaining("File-scope invariant violation"),
"tool_error",
expect.stringContaining("declaredScope:"),
"merger",
);
});
it("keeps backward compatibility when auditor is omitted", async () => {
const store = createInvariantStore(["packages/engine/src/merger.ts"]);
mockedExecSync.mockImplementation((cmd: any) => {
const cmdStr = String(cmd);
if (cmdStr === "git diff --cached --name-only") return "packages/core/src/store.ts";
if (cmdStr === "git reset --merge") return "";
return "";
});
await expect(enforceSquashFileScopeInvariant({
store: store as never,
taskId: "FN-4073",
rootDir: "/tmp/root",
task: await (store as any).getTask("FN-4073"),
resetLabel: "file-scope invariant violation",
})).rejects.toBeInstanceOf(FileScopeViolationError);
});
});
describe("file-scope invariant wiring", () => {
beforeEach(() => {
vi.clearAllMocks();