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:
committed by
gsxdsm
parent
7ac8a73f1b
commit
23dae4b495
@@ -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: "" },
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user