150 lines
4.9 KiB
TypeScript
150 lines
4.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { createMockStore, mockedExecSync } from "./merger-test-helpers.js";
|
|
import { applyLayer3ConflictScopePartition, partitionConflictsByFileScope } from "../merger.js";
|
|
|
|
describe("partitionConflictsByFileScope", () => {
|
|
it("treats empty declared scope as no enforcement", () => {
|
|
const result = partitionConflictsByFileScope({
|
|
conflictFiles: ["AGENTS.md", "packages/core/src/store.ts"],
|
|
declaredScope: [],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
inScope: ["AGENTS.md", "packages/core/src/store.ts"],
|
|
outOfScope: [],
|
|
});
|
|
});
|
|
|
|
it("returns all in-scope files", () => {
|
|
const result = partitionConflictsByFileScope({
|
|
conflictFiles: ["packages/engine/src/merger.ts", "packages/engine/src/store.ts"],
|
|
declaredScope: ["packages/engine/src/**"],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
inScope: ["packages/engine/src/merger.ts", "packages/engine/src/store.ts"],
|
|
outOfScope: [],
|
|
});
|
|
});
|
|
|
|
it("returns all out-of-scope files", () => {
|
|
const result = partitionConflictsByFileScope({
|
|
conflictFiles: ["AGENTS.md", "packages/core/src/store.ts"],
|
|
declaredScope: ["packages/desktop/src/**"],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
inScope: [],
|
|
outOfScope: ["AGENTS.md", "packages/core/src/store.ts"],
|
|
});
|
|
});
|
|
|
|
it("partitions mixed files", () => {
|
|
const result = partitionConflictsByFileScope({
|
|
conflictFiles: ["packages/desktop/src/foo.ts", "packages/core/src/store.ts"],
|
|
declaredScope: ["packages/desktop/src/**"],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
inScope: ["packages/desktop/src/foo.ts"],
|
|
outOfScope: ["packages/core/src/store.ts"],
|
|
});
|
|
});
|
|
|
|
it("matches glob scope entries", () => {
|
|
const result = partitionConflictsByFileScope({
|
|
conflictFiles: ["packages/core/src/store.ts", "packages/core/test/store.test.ts"],
|
|
declaredScope: ["packages/core/src/**"],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
inScope: ["packages/core/src/store.ts"],
|
|
outOfScope: ["packages/core/test/store.test.ts"],
|
|
});
|
|
});
|
|
|
|
it("supports .changeset patterns", () => {
|
|
const result = partitionConflictsByFileScope({
|
|
conflictFiles: [".changeset/fn-4956.md", "AGENTS.md"],
|
|
declaredScope: [".changeset/*"],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
inScope: [".changeset/fn-4956.md"],
|
|
outOfScope: ["AGENTS.md"],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("applyLayer3ConflictScopePartition", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("resolves out-of-scope conflicts to ours and emits skip audit", async () => {
|
|
const store = createMockStore() as any;
|
|
store.parseFileScopeFromPrompt = vi.fn().mockResolvedValue(["packages/desktop/src/**"]);
|
|
const task = await store.getTask("FN-050");
|
|
const gitCalls: string[] = [];
|
|
|
|
mockedExecSync.mockImplementation((cmd: any) => {
|
|
const c = String(cmd);
|
|
gitCalls.push(c);
|
|
if (c.includes("git diff --cached --name-only")) {
|
|
return "packages/desktop/src/foo.ts\n";
|
|
}
|
|
return "";
|
|
});
|
|
|
|
const audit = { git: vi.fn().mockResolvedValue(undefined) } as any;
|
|
const result = await applyLayer3ConflictScopePartition({
|
|
store,
|
|
task,
|
|
taskId: "FN-050",
|
|
rootDir: "/tmp/root",
|
|
branch: "fusion/fn-050",
|
|
conflictFiles: ["packages/desktop/src/foo.ts", "AGENTS.md"],
|
|
auditor: audit,
|
|
});
|
|
|
|
expect(result.inScopeConflicts).toEqual(["packages/desktop/src/foo.ts"]);
|
|
expect(result.skippedFiles).toEqual(["AGENTS.md"]);
|
|
expect(gitCalls.some((call) => call.includes("git checkout --ours -- AGENTS.md"))).toBe(true);
|
|
expect(audit.git).toHaveBeenCalledWith(expect.objectContaining({
|
|
type: "merge:layer3:foreign-file-skipped",
|
|
target: "fusion/fn-050",
|
|
metadata: expect.objectContaining({
|
|
taskId: "FN-050",
|
|
skippedFiles: ["AGENTS.md"],
|
|
inScopeCount: 1,
|
|
viaScopeOverride: false,
|
|
}),
|
|
}));
|
|
});
|
|
|
|
it("emits scope override bypass audit and skips partition", async () => {
|
|
const store = createMockStore({ scopeOverride: true, scopeOverrideReason: "hotfix" }) as any;
|
|
store.parseFileScopeFromPrompt = vi.fn().mockResolvedValue(["packages/desktop/src/**"]);
|
|
const task = await store.getTask("FN-050");
|
|
const audit = { git: vi.fn().mockResolvedValue(undefined) } as any;
|
|
|
|
const result = await applyLayer3ConflictScopePartition({
|
|
store,
|
|
task,
|
|
taskId: "FN-050",
|
|
rootDir: "/tmp/root",
|
|
branch: "fusion/fn-050",
|
|
conflictFiles: ["packages/desktop/src/foo.ts", "AGENTS.md"],
|
|
auditor: audit,
|
|
});
|
|
|
|
expect(result.inScopeConflicts).toEqual(["packages/desktop/src/foo.ts", "AGENTS.md"]);
|
|
expect(result.skippedFiles).toEqual([]);
|
|
expect(audit.git).toHaveBeenCalledWith(expect.objectContaining({
|
|
type: "merge:layer3:scope-override-bypass",
|
|
metadata: expect.objectContaining({ viaScopeOverride: true }),
|
|
}));
|
|
});
|
|
});
|