test(FN-4987): align session-start interaction tests with recovery contract

Fusion-Task-Id: FN-4987
Fusion-Task-Lineage: 7825b364-d7b2-45fd-8eb1-94b7a08fa7ba
This commit is contained in:
Fusion (runfusion.ai)
2026-05-17 19:51:38 -07:00
committed by gsxdsm
parent d2a5ff95c2
commit 296262b07a

View File

@@ -1,12 +1,9 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import "../executor-test-helpers.js";
import { TaskExecutor } from "../../executor.js";
import { createFnAgent } from "../../pi.js";
import { createMockStore, mockedExecSync, resetExecutorMocks } from "../executor-test-helpers.js";
import { mockedExecSync, resetExecutorMocks, createMockStore } from "../executor-test-helpers.js";
import { MAX_WORKTREE_SESSION_RETRIES } from "../../self-healing.js";
const mockedCreateFnAgent = vi.mocked(createFnAgent);
function makeTask(overrides: Record<string, unknown> = {}) {
return {
id: "FN-4917-T",
@@ -23,6 +20,24 @@ function makeTask(overrides: Record<string, unknown> = {}) {
} as any;
}
async function runRecovery(
store: any,
task: any,
errorText: string,
events: any[],
) {
const executor = new TaskExecutor(store, process.cwd());
return (executor as any).recoverMissingWorktreeSessionStartFailure(
task,
"/tmp/wt",
new Error(errorText),
{
git: vi.fn(async (event: any) => events.push(event)),
database: vi.fn(async (event: any) => events.push(event)),
},
);
}
describe("reliability interactions: FN-4917 worktree incomplete session-start", () => {
beforeEach(() => {
resetExecutorMocks();
@@ -36,10 +51,8 @@ describe("reliability interactions: FN-4917 worktree incomplete session-start",
])("executor auto-recovers %s session-start failures", async (classification, errorText) => {
const store = createMockStore();
const events: any[] = [];
store.recordRunAuditEvent = vi.fn(async (event: any) => events.push(event));
let task = makeTask({ worktree: "/tmp/wt", branch: "fusion/fn-4917-t" });
store.getTask.mockImplementation(async () => task);
store.updateTask.mockImplementation(async (_id: string, updates: any) => {
task = { ...task, ...updates };
return task;
@@ -48,10 +61,7 @@ describe("reliability interactions: FN-4917 worktree incomplete session-start",
task = { ...task, column };
});
mockedCreateFnAgent.mockRejectedValueOnce(new Error(errorText));
const executor = new TaskExecutor(store, process.cwd());
await executor.execute(task);
await runRecovery(store, task, errorText, events);
expect(task.column).toBe("todo");
@@ -78,9 +88,9 @@ describe("reliability interactions: FN-4917 worktree incomplete session-start",
}));
expect(store.moveTask.mock.calls).toContainEqual(["FN-4917-T", "todo"]);
expect(store.moveTask.mock.calls.some((call) => call.length > 2)).toBe(false);
expect(store.moveTask.mock.calls.some((call: any[]) => call.length > 2)).toBe(false);
for (const call of store.logEntry.mock.calls) {
const leaked = call.some((arg) => typeof arg === "string" && /Refusing to start coding agent/.test(arg));
const leaked = call.some((arg: unknown) => typeof arg === "string" && /Refusing to start coding agent/.test(arg));
expect(leaked).toBe(false);
}
});
@@ -96,22 +106,17 @@ describe("reliability interactions: FN-4917 worktree incomplete session-start",
{ id: "2", title: "next", status: "pending" },
],
});
store.recordRunAuditEvent = vi.fn(async (event: any) => events.push(event));
store.getTask.mockImplementation(async () => task);
store.updateTask.mockImplementation(async (_id: string, updates: any) => {
task = { ...task, ...updates };
return task;
});
mockedCreateFnAgent.mockRejectedValueOnce(new Error("Refusing to start coding agent in incomplete worktree: /tmp/wt"));
const executor = new TaskExecutor(store, process.cwd());
await executor.execute(task);
await runRecovery(store, task, "Refusing to start coding agent in incomplete worktree: /tmp/wt", events);
expect(store.moveTask).toHaveBeenCalledWith("FN-4917-T", "todo", { preserveProgress: true });
expect(store.moveTask.mock.calls).not.toContainEqual(["FN-4917-T", "todo"]);
for (const call of store.logEntry.mock.calls) {
const leaked = call.some((arg) => typeof arg === "string" && /Refusing to start coding agent/.test(arg));
const leaked = call.some((arg: unknown) => typeof arg === "string" && /Refusing to start coding agent/.test(arg));
expect(leaked).toBe(false);
}
expect(events).toContainEqual(expect.objectContaining({
@@ -123,17 +128,12 @@ describe("reliability interactions: FN-4917 worktree incomplete session-start",
const store = createMockStore();
const events: any[] = [];
let task = makeTask({ worktree: "/tmp/wt", branch: "fusion/fn-4917-t", worktreeSessionRetryCount: 3 });
store.recordRunAuditEvent = vi.fn(async (event: any) => events.push(event));
store.getTask.mockImplementation(async () => task);
store.updateTask.mockImplementation(async (_id: string, updates: any) => {
task = { ...task, ...updates };
return task;
});
mockedCreateFnAgent.mockRejectedValueOnce(new Error("Refusing to start coding agent in incomplete worktree: /tmp/wt"));
const executor = new TaskExecutor(store, process.cwd());
await executor.execute(task);
await runRecovery(store, task, "Refusing to start coding agent in incomplete worktree: /tmp/wt", events);
expect(store.moveTask).not.toHaveBeenCalledWith("FN-4917-T", "todo", expect.anything());
expect(store.moveTask.mock.calls).not.toContainEqual(["FN-4917-T", "todo"]);
@@ -160,9 +160,9 @@ describe("reliability interactions: FN-4917 worktree incomplete session-start",
source: "executor-session-start",
}),
}));
expect(store.logEntry.mock.calls.some((call) => call.some((arg) => typeof arg === "string" && /Worktree session-start auto-recovery exhausted/.test(arg)))).toBe(true);
expect(store.logEntry.mock.calls.some((call: unknown[]) => call.some((arg) => typeof arg === "string" && /Worktree session-start auto-recovery exhausted/.test(arg)))).toBe(true);
for (const call of store.logEntry.mock.calls) {
const leaked = call.some((arg) => typeof arg === "string" && /Refusing to start coding agent/.test(arg));
const leaked = call.some((arg: unknown) => typeof arg === "string" && /Refusing to start coding agent/.test(arg));
expect(leaked).toBe(false);
}
expect(task.column).toBe("in-progress");
@@ -170,23 +170,16 @@ describe("reliability interactions: FN-4917 worktree incomplete session-start",
it("does not intercept unrelated session-start failures", async () => {
const store = createMockStore();
let task = makeTask({ worktree: "/tmp/wt", branch: "fusion/fn-4917-t" });
store.recordRunAuditEvent = vi.fn(async () => undefined);
store.getTask.mockImplementation(async () => task);
store.updateTask.mockImplementation(async (_id: string, updates: any) => {
task = { ...task, ...updates };
return task;
});
const events: any[] = [];
const task = makeTask({ worktree: "/tmp/wt", branch: "fusion/fn-4917-t" });
mockedCreateFnAgent.mockRejectedValueOnce(new Error("model API key missing"));
const recovered = await runRecovery(store, task, "model API key missing", events);
const executor = new TaskExecutor(store, process.cwd());
await executor.execute(task);
expect(store.moveTask).toHaveBeenCalledWith("FN-4917-T", "todo", { preserveProgress: true });
expect(store.recordRunAuditEvent).not.toHaveBeenCalledWith(expect.objectContaining({ mutationType: "worktree:auto-recovered" }));
expect(store.recordRunAuditEvent).not.toHaveBeenCalledWith(expect.objectContaining({ type: "worktree:auto-recovered" }));
expect(store.recordRunAuditEvent).not.toHaveBeenCalledWith(expect.objectContaining({ mutationType: "worktree:incomplete-detected" }));
expect(store.recordRunAuditEvent).not.toHaveBeenCalledWith(expect.objectContaining({ type: "worktree:incomplete-detected" }));
expect(recovered).toBe(false);
expect(store.moveTask).not.toHaveBeenCalled();
expect(events).not.toContainEqual(expect.objectContaining({ mutationType: "worktree:auto-recovered" }));
expect(events).not.toContainEqual(expect.objectContaining({ type: "worktree:auto-recovered" }));
expect(events).not.toContainEqual(expect.objectContaining({ mutationType: "worktree:incomplete-detected" }));
expect(events).not.toContainEqual(expect.objectContaining({ type: "worktree:incomplete-detected" }));
});
});