test(engine): fix ModelFallbackExhaustedError pi mock + FN-7360 worktree exec counts + sync conflict mapping
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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" }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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" }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user