test(engine): fix ModelFallbackExhaustedError pi mock + FN-7360 worktree exec counts + sync conflict mapping

This commit is contained in:
gsxdsm
2026-07-07 09:37:18 -07:00
parent b6a8f6430f
commit f93ce52689
4 changed files with 45 additions and 20 deletions

View File

@@ -18,11 +18,20 @@ vi.mock("../reviewer.js", () => ({
reviewStep: mockReviewStep,
}));
vi.mock("../pi.js", () => ({
createFnAgent: mockCreateFnAgent,
describeModel: vi.fn().mockReturnValue("mock-model"),
promptWithFallback: vi.fn().mockResolvedValue(undefined),
}));
vi.mock("../pi.js", () => {
/*
FNXC:EngineTests 2026-07-07-09:10:
triage.ts specifyTask catch checks `err instanceof ModelFallbackExhaustedError` (FN-7559 planner fallback exhaustion handling) and agentWork calls `formatModelMarkerDetails`. The pi mock must expose both so the instanceof guard is callable and model-marker formatting resolves, instead of crashing happy-path specifyTask runs.
*/
class ModelFallbackExhaustedError extends Error {}
return {
ModelFallbackExhaustedError,
createFnAgent: mockCreateFnAgent,
describeModel: vi.fn().mockReturnValue("mock-model"),
formatModelMarkerDetails: vi.fn((model: string) => model),
promptWithFallback: vi.fn().mockResolvedValue(undefined),
};
});
vi.mock("@fusion/core", async (importOriginal) => {
const { createEngineCoreMock } = await import("../test/mockCore.js");

View File

@@ -134,14 +134,18 @@ describe("acquireTaskWorktree backend wiring", () => {
).rejects.toMatchObject({ name: "WorktrunkOperationError", code: "worktrunk_binary_missing" });
/*
* FNXC:WorktreeIsolation 2026-07-02-07:40:
* The integration-branch resolution (`git symbolic-ref`) runs before the worktrunk binary check, so one exec call is expected. No worktrunk `switch` command should be attempted when the binary is missing.
* FNXC:WorktreeIsolation 2026-07-02-07:40 (updated 2026-07-07-09:15 for FN-7438):
* The integration-branch resolution runs before the worktrunk binary check. With an empty symbolic-ref result, FN-7438 (aa8f1f32e) adds a `git remote` discovery call before the "main" fallback, so two exec calls happen. No worktrunk `switch` command should be attempted when the binary is missing.
*/
expect(execMock).toHaveBeenCalledTimes(1);
expect(execMock).toHaveBeenCalledTimes(2);
expect(execMock).toHaveBeenCalledWith(
"git symbolic-ref --short refs/remotes/origin/HEAD",
expect.objectContaining({ cwd: "/repo" }),
);
expect(execMock).toHaveBeenCalledWith(
"git remote",
expect.objectContaining({ cwd: "/repo" }),
);
expect(execMock.mock.calls.some((call) => String(call[0]).includes('"switch"'))).toBe(false);
});
@@ -188,13 +192,17 @@ describe("acquireTaskWorktree backend wiring", () => {
expect(result.branch).toBe("fusion/fn-backend");
expect(create).toHaveBeenCalledTimes(1);
/*
* FNXC:WorktreeIsolation 2026-07-02-07:40:
* The integration-branch resolution runs before the explicit backend's create is invoked, so the only exec call is the `git symbolic-ref` lookup. The custom backend's create mock performs no exec.
* FNXC:WorktreeIsolation 2026-07-02-07:40 (updated 2026-07-07-09:15 for FN-7438):
* The integration-branch resolution runs before the explicit backend's create. With an empty symbolic-ref result, FN-7438 (aa8f1f32e) adds a `git remote` discovery call before the "main" fallback, so two exec calls happen: symbolic-ref + git remote. The custom backend's create mock performs no exec.
*/
expect(execMock).toHaveBeenCalledTimes(1);
expect(execMock).toHaveBeenCalledTimes(2);
expect(execMock).toHaveBeenCalledWith(
"git symbolic-ref --short refs/remotes/origin/HEAD",
expect.objectContaining({ cwd: "/repo" }),
);
expect(execMock).toHaveBeenCalledWith(
"git remote",
expect.objectContaining({ cwd: "/repo" }),
);
});
});

View File

@@ -66,13 +66,14 @@ describe("acquireTaskWorktree worktrunk wiring", () => {
expect(result).toMatchObject({ source: "fresh", branch: "fusion/fn-1" });
/*
* FNXC:WorktreeIsolation 2026-07-02-07:40:
* acquireTaskWorktree now resolves the integration branch via `git symbolic-ref` (returning empty here, so it falls back to "main") and pins the fresh worktree to that start point. Two exec calls happen: the symbolic-ref lookup, then the native `git worktree add -b ... "main"` create.
* FNXC:WorktreeIsolation 2026-07-02-07:40 (updated 2026-07-07-09:15 for FN-7438):
* acquireTaskWorktree resolves the integration branch via `git symbolic-ref`. When that returns empty (as the mock does here), FN-7438 (aa8f1f32e) added a `git remote` discovery call before falling back to "main". So three exec calls happen: symbolic-ref, git remote, then the native `git worktree add -b ... "main"` create.
*/
expect(execMock).toHaveBeenCalledTimes(2);
expect(execMock).toHaveBeenCalledTimes(3);
expect(execMock.mock.calls[0]?.[0]).toBe("git symbolic-ref --short refs/remotes/origin/HEAD");
expect(execMock.mock.calls[1]?.[0]).toContain('git worktree add -b "fusion/fn-1"');
expect(execMock.mock.calls[1]?.[0]).toContain('"main"');
expect(execMock.mock.calls[1]?.[0]).toBe("git remote");
expect(execMock.mock.calls[2]?.[0]).toContain('git worktree add -b "fusion/fn-1"');
expect(execMock.mock.calls[2]?.[0]).toContain('"main"');
});
it("prefers explicit createWorktree override", async () => {
@@ -230,13 +231,17 @@ describe("acquireTaskWorktree worktrunk wiring", () => {
expect(result.branch).toBe("fusion/fn-1-custom");
expect(create).toHaveBeenCalledTimes(1);
/*
* FNXC:WorktreeIsolation 2026-07-02-07:40:
* The integration-branch resolution runs before the custom backend's create, so the only exec call is the `git symbolic-ref` lookup. The backend's create mock performs no exec.
* FNXC:WorktreeIsolation 2026-07-02-07:40 (updated 2026-07-07-09:15 for FN-7438):
* The integration-branch resolution runs before the custom backend's create. With an empty symbolic-ref result, FN-7438 (aa8f1f32e) adds a `git remote` discovery call before the "main" fallback, so two exec calls happen: symbolic-ref + git remote. The backend's create mock performs no exec.
*/
expect(execMock).toHaveBeenCalledTimes(1);
expect(execMock).toHaveBeenCalledTimes(2);
expect(execMock).toHaveBeenCalledWith(
"git symbolic-ref --short refs/remotes/origin/HEAD",
expect.objectContaining({ cwd: "/repo" }),
);
expect(execMock).toHaveBeenCalledWith(
"git remote",
expect.objectContaining({ cwd: "/repo" }),
);
});
});

View File

@@ -821,11 +821,14 @@ describe("WorktrunkWorktreeBackend", () => {
});
it("maps rebase conflicts to worktrunk_sync_conflict", async () => {
// FN-7438 (aa8f1f32e): resolveIntegrationBranch now does symbolic-ref + `git remote`
// before fetch+rebase when no trunk is given, which would consume this mock queue.
// Pass an explicit trunk to isolate the rebase-conflict mapping path under test.
execMock.mockResolvedValueOnce({ stdout: "", stderr: "" }).mockRejectedValueOnce({ stderr: "CONFLICT" });
const backend = new WorktrunkWorktreeBackend({ binaryPath: "worktrunk" });
await expect(
backend.sync({ rootDir: "/repo", worktreePath: "/repo/.worktrees/fn-1", branch: "main" }),
backend.sync({ rootDir: "/repo", worktreePath: "/repo/.worktrees/fn-1", branch: "main", trunk: "main" }),
).rejects.toMatchObject({ code: "worktrunk_sync_conflict", operation: "sync" });
});