test(FN-5168): complete Step 6 — verification gates green

Fusion-Task-Id: FN-5168
Fusion-Task-Lineage: 0c865d3e-0886-4e51-a7ff-cb4c713dcc54
This commit is contained in:
Fusion (runfusion.ai)
2026-05-19 15:38:59 -07:00
committed by gsxdsm
parent 7ac8a73f1b
commit 23dae4b495
2 changed files with 61 additions and 36 deletions

View File

@@ -1231,22 +1231,23 @@ describe("runDashboard — WorktreePool wiring", () => {
it("passes a WorktreePool instance to TaskExecutor", async () => { it("passes a WorktreePool instance to TaskExecutor", async () => {
await runDashboard(0, { open: false }); await runDashboard(0, { open: false });
expect(capturedExecutorOpts).toBeDefined(); await waitForAsyncExpectation(() => {
expect(capturedExecutorOpts!.pool).toBeInstanceOf(WorktreePool); expect(capturedExecutorOpts).toBeDefined();
expect(capturedExecutorOpts!.pool).toBeInstanceOf(WorktreePool);
});
}); });
it("passes a WorktreePool instance to aiMergeTask via rawMerge", async () => { it("passes a WorktreePool instance to aiMergeTask via rawMerge", async () => {
const { aiMergeTask } = await import("@fusion/engine"); const { aiMergeTask, ProjectEngineManager } = await import("@fusion/engine");
const { createServer } = await import("@fusion/dashboard");
await runDashboard(0, { open: false }); await runDashboard(0, { open: false });
// rawMerge is exposed as the onMerge callback wired into createServer. const managerInstance = (ProjectEngineManager as unknown as ReturnType<typeof vi.fn>).mock.results[0]?.value;
const createServerCall = (createServer as ReturnType<typeof vi.fn>).mock.calls[0]; await waitForAsyncExpectation(() => {
const serverOpts = createServerCall[1] as { onMerge: (taskId: string) => Promise<unknown> }; expect(managerInstance?.getEngine("project-1")).toBeDefined();
});
// Invoke the merge handler await managerInstance.getEngine("project-1").onMerge("FN-TEST");
await serverOpts.onMerge("FN-TEST");
expect(aiMergeTask).toHaveBeenCalled(); expect(aiMergeTask).toHaveBeenCalled();
const mergeCallOpts = (aiMergeTask as ReturnType<typeof vi.fn>).mock.calls[0][3]; const mergeCallOpts = (aiMergeTask as ReturnType<typeof vi.fn>).mock.calls[0][3];
@@ -1254,15 +1255,17 @@ describe("runDashboard — WorktreePool wiring", () => {
}); });
it("shares the same WorktreePool instance between executor and merger", async () => { it("shares the same WorktreePool instance between executor and merger", async () => {
const { aiMergeTask } = await import("@fusion/engine"); const { aiMergeTask, ProjectEngineManager } = await import("@fusion/engine");
const { createServer } = await import("@fusion/dashboard");
await runDashboard(0, { open: false }); await runDashboard(0, { open: false });
// Trigger merger via onMerge const managerInstance = (ProjectEngineManager as unknown as ReturnType<typeof vi.fn>).mock.results[0]?.value;
const createServerCall = (createServer as ReturnType<typeof vi.fn>).mock.calls[0]; await waitForAsyncExpectation(() => {
const serverOpts = createServerCall[1] as { onMerge: (taskId: string) => Promise<unknown> }; expect(capturedExecutorOpts).toBeDefined();
await serverOpts.onMerge("FN-TEST"); expect(managerInstance?.getEngine("project-1")).toBeDefined();
});
await managerInstance.getEngine("project-1").onMerge("FN-TEST");
const executorPool = capturedExecutorOpts!.pool; const executorPool = capturedExecutorOpts!.pool;
const mergerPool = (aiMergeTask as ReturnType<typeof vi.fn>).mock.calls[0][3].pool; const mergerPool = (aiMergeTask as ReturnType<typeof vi.fn>).mock.calls[0][3].pool;
@@ -1496,12 +1499,12 @@ describe("runDashboard — immediate resume on unpause", () => {
it("registers a settings:updated listener on the store", async () => { it("registers a settings:updated listener on the store", async () => {
await runDashboard(0, { open: false }); await runDashboard(0, { open: false });
// The store.on should have been called with "settings:updated" at least once await waitForAsyncExpectation(() => {
const settingsUpdatedCalls = mockStore.on.mock.calls.filter( const settingsUpdatedCalls = mockStore.on.mock.calls.filter(
(call: any[]) => call[0] === "settings:updated", (call: any[]) => call[0] === "settings:updated",
); );
// At least 2 listeners: one for pause→true (merge kill), one for unpause expect(settingsUpdatedCalls.length).toBeGreaterThanOrEqual(2);
expect(settingsUpdatedCalls.length).toBeGreaterThanOrEqual(2); });
}); });
it("calls executor.resumeOrphaned() when globalPause transitions true → false", async () => { it("calls executor.resumeOrphaned() when globalPause transitions true → false", async () => {
@@ -1533,12 +1536,14 @@ describe("runDashboard — immediate resume on unpause", () => {
it("passes executor recovery callbacks into SelfHealingManager", async () => { it("passes executor recovery callbacks into SelfHealingManager", async () => {
await runDashboard(0, { open: false }); await runDashboard(0, { open: false });
expect(capturedSelfHealingOpts).toMatchObject({ await waitForAsyncExpectation(() => {
rootDir: process.cwd(), expect(capturedSelfHealingOpts).toMatchObject({
recoverCompletedTask: expect.any(Function), rootDir: process.cwd(),
getExecutingTaskIds: expect.any(Function), recoverCompletedTask: expect.any(Function),
getExecutingTaskIds: expect.any(Function),
});
expect(mockSelfHealingStart).toHaveBeenCalled();
}); });
expect(mockSelfHealingStart).toHaveBeenCalled();
}); });
it("sweeps merge queue on unpause when autoMerge is enabled", async () => { it("sweeps merge queue on unpause when autoMerge is enabled", async () => {
@@ -1658,6 +1663,13 @@ describe("runDashboard — stuck task timeout listener guards", () => {
try { try {
await runDashboard(0, { open: false }); await runDashboard(0, { open: false });
await waitForAsyncExpectation(() => {
const settingsUpdatedCalls = mockStore.on.mock.calls.filter(
(call: any[]) => call[0] === "settings:updated",
);
expect(settingsUpdatedCalls.length).toBeGreaterThanOrEqual(1);
});
mockStore.emit("settings:updated", { mockStore.emit("settings:updated", {
settings: { taskStuckTimeoutMs: 600_000 }, settings: { taskStuckTimeoutMs: 600_000 },
previous: { taskStuckTimeoutMs: 1_200_000 }, previous: { taskStuckTimeoutMs: 1_200_000 },
@@ -1708,9 +1720,13 @@ describe("runDashboard — port fallback on EADDRINUSE", () => {
expect(mockListen).toHaveBeenCalledWith(4040, "127.0.0.1"); expect(mockListen).toHaveBeenCalledWith(4040, "127.0.0.1");
// Banner should show the resolved localhost URL from the bound server. // Banner should show the resolved localhost URL from the bound server.
expect(consoleSpy).toHaveBeenCalledWith( await waitForAsyncExpectation(() => {
expect.stringContaining("http://localhost:"), expect(
); consoleSpy.mock.calls.some(
(call) => typeof call[0] === "string" && call[0].includes("http://localhost:"),
),
).toBe(true);
});
// No warning should be printed // No warning should be printed
const warningCalls = consoleSpy.mock.calls.filter( const warningCalls = consoleSpy.mock.calls.filter(
@@ -2132,9 +2148,11 @@ describe("runDashboard — --dev mode", () => {
const { TriageProcessor, TaskExecutor, Scheduler } = await import("@fusion/engine"); const { TriageProcessor, TaskExecutor, Scheduler } = await import("@fusion/engine");
await runDashboard(0, { open: false }); await runDashboard(0, { open: false });
expect(TriageProcessor).toHaveBeenCalled(); await waitForAsyncExpectation(() => {
expect(TaskExecutor).toHaveBeenCalled(); expect(TriageProcessor).toHaveBeenCalled();
expect(Scheduler).toHaveBeenCalled(); expect(TaskExecutor).toHaveBeenCalled();
expect(Scheduler).toHaveBeenCalled();
});
}); });
it("shows 'AI engine: ✓ active' when not in dev mode", async () => { it("shows 'AI engine: ✓ active' when not in dev mode", async () => {
@@ -2494,8 +2512,9 @@ describe("runDashboard — PR feedback follow-up wiring", () => {
await runDashboard(0, { open: false }); await runDashboard(0, { open: false });
// Verify the callback was passed to the scheduler await waitForAsyncExpectation(() => {
expect(capturedOnClosedPrFeedback).toBeDefined(); expect(capturedOnClosedPrFeedback).toBeDefined();
});
// Invoke it to verify it reaches createFollowUpTask // Invoke it to verify it reaches createFollowUpTask
const mockPrInfo = { status: "merged", number: 42 }; const mockPrInfo = { status: "merged", number: 42 };
@@ -2526,8 +2545,9 @@ describe("runDashboard — PR feedback follow-up wiring", () => {
await runDashboard(0, { open: false }); await runDashboard(0, { open: false });
// The onNewComments callback should still be wired to handleNewComments await waitForAsyncExpectation(() => {
expect(capturedOnNewComments).toBeDefined(); expect(capturedOnNewComments).toBeDefined();
});
const handlerInstance = (PrCommentHandler as unknown as ReturnType<typeof vi.fn>).mock.results[0].value; const handlerInstance = (PrCommentHandler as unknown as ReturnType<typeof vi.fn>).mock.results[0].value;
const mockComments = [ const mockComments = [
{ id: 1, body: "Fix this", user: { login: "reviewer" }, created_at: "2024-01-01", updated_at: "2024-01-01", html_url: "" }, { id: 1, body: "Fix this", user: { login: "reviewer" }, created_at: "2024-01-01", updated_at: "2024-01-01", html_url: "" },

View File

@@ -3309,6 +3309,7 @@ describe("TaskExecutor loop recovery", () => {
noProgressMs: 600000, noProgressMs: 600000,
inactivityMs: 0, inactivityMs: 0,
activitySinceProgress: 100, activitySinceProgress: 100,
ignoredStepUpdateCount: 0,
shouldRequeue: true, shouldRequeue: true,
}); });
@@ -3332,6 +3333,7 @@ describe("TaskExecutor loop recovery", () => {
noProgressMs: 600000, noProgressMs: 600000,
inactivityMs: 0, inactivityMs: 0,
activitySinceProgress: 100, activitySinceProgress: 100,
ignoredStepUpdateCount: 0,
shouldRequeue: true, shouldRequeue: true,
}); });
@@ -3349,6 +3351,7 @@ describe("TaskExecutor loop recovery", () => {
noProgressMs: 600000, noProgressMs: 600000,
inactivityMs: 0, inactivityMs: 0,
activitySinceProgress: 100, activitySinceProgress: 100,
ignoredStepUpdateCount: 0,
shouldRequeue: true, shouldRequeue: true,
}); });
expect(result1).toBe(true); expect(result1).toBe(true);
@@ -3360,6 +3363,7 @@ describe("TaskExecutor loop recovery", () => {
noProgressMs: 600000, noProgressMs: 600000,
inactivityMs: 0, inactivityMs: 0,
activitySinceProgress: 200, activitySinceProgress: 200,
ignoredStepUpdateCount: 0,
shouldRequeue: true, shouldRequeue: true,
}); });
expect(result2).toBe(false); expect(result2).toBe(false);
@@ -3375,6 +3379,7 @@ describe("TaskExecutor loop recovery", () => {
noProgressMs: 600000, noProgressMs: 600000,
inactivityMs: 0, inactivityMs: 0,
activitySinceProgress: 100, activitySinceProgress: 100,
ignoredStepUpdateCount: 0,
shouldRequeue: true, shouldRequeue: true,
}); });