diff --git a/packages/dashboard/src/__tests__/session-reconnect.test.ts b/packages/dashboard/src/__tests__/session-reconnect.test.ts index a5a0dbde4a..6aaeb9fc1e 100644 --- a/packages/dashboard/src/__tests__/session-reconnect.test.ts +++ b/packages/dashboard/src/__tests__/session-reconnect.test.ts @@ -9,7 +9,6 @@ import express from "express"; import { mkdtempSync } from "node:fs"; import { rm } from "node:fs/promises"; import { tmpdir } from "node:os"; -import { setImmediate } from "node:timers"; import { join } from "node:path"; import { beforeEach, afterEach, describe, expect, it, vi } from "vitest"; import { Database, TaskStore } from "@fusion/core"; @@ -105,6 +104,7 @@ describe("session reconnect + replay", () => { let db: Database; let aiSessionStore: AiSessionStore; let app: express.Express; + let apiRouter: express.Router & { dispose?: () => void }; beforeEach(async () => { vi.clearAllMocks(); @@ -129,7 +129,16 @@ describe("session reconnect + replay", () => { app = express(); app.use(express.json()); - app.use("/api", createApiRoutes(store, { aiSessionStore })); + /* + FNXC:DashboardSessionTests 2026-06-14-12:05: + These SSE replay tests exercise planning/subtask/mission routes, not the EventEmitter-driven GitHub tracking services that createApiRoutes starts for a full TaskStore. Hide on/off for this focused harness so unrelated startup reconcile work cannot touch the temp .fusion tree after the test-owned store closes. + */ + Object.defineProperties(store, { + on: { value: undefined, configurable: true }, + off: { value: undefined, configurable: true }, + }); + apiRouter = createApiRoutes(store, { aiSessionStore }) as express.Router & { dispose?: () => void }; + app.use("/api", apiRouter); }); afterEach(async () => { @@ -138,6 +147,12 @@ describe("session reconnect + replay", () => { __resetSubtaskBreakdownState(); __resetMissionInterviewState(); + try { + apiRouter.dispose?.(); + } catch { + // no-op + } + aiSessionStore.stopScheduledCleanup(); try { store.close(); } catch { @@ -148,8 +163,7 @@ describe("session reconnect + replay", () => { } catch { // no-op } - // FNXC:DashboardSessionTests 2026-06-14-09:20: TaskStore.close() closes watcher/database handles synchronously but their filesystem close callbacks settle on the next event-loop turn; drain that turn before deleting .fusion. - await new Promise((resolve) => setImmediate(resolve)); + // FNXC:DashboardSessionTests 2026-06-14-12:07: FN-6447 requires teardown to remove tmpRoot only after route-owned background workers are prevented/disposed and both TaskStore/AiSession DB handles are closed; do not use retry-rm loops that can mask a live writer. await rm(tmpRoot, { recursive: true, force: true }); }); diff --git a/packages/dashboard/vitest.config.ts b/packages/dashboard/vitest.config.ts index 50d9c060d5..86828e92c4 100644 --- a/packages/dashboard/vitest.config.ts +++ b/packages/dashboard/vitest.config.ts @@ -246,7 +246,10 @@ const quarantinedDashboardTests: string[] = [ FN-6441 removed the dashboard component orphan batch from the curated skip-list so passing rescues run in backfill and still-failing tests are excluded only through the dated quarantine ledger. Keep these one-line excludes mirrored with scripts/lib/test-quarantine.json until each file is rescued or deleted under the deletion ratchet. FNXC:DashboardTests 2026-06-14-09:58: - FN-6444 applies the same no-silent-orphan invariant to dashboard src route/API tests: rescued files run in backfill, while broad stale mission/planning suites plus the newly observed FN-6447 reconnect temp-cleanup flake are represented only by the dated quarantine ledger. + FN-6444 applies the same no-silent-orphan invariant to dashboard src route/API tests: rescued files run in backfill, while broad stale mission/planning suites are represented only by the dated quarantine ledger. + + FNXC:DashboardSessionTests 2026-06-14-12:10: + FN-6447 rescued session-reconnect by isolating the SSE harness from unrelated route background workers, so it must stay out of this quarantine list and run in dashboard-api-quality-backfill. */ "app/components/__tests__/ChatView.regular-composer-no-right-line.test.tsx", "app/components/__tests__/MissionManager.test.tsx", @@ -261,7 +264,6 @@ const quarantinedDashboardTests: string[] = [ "app/components/__tests__/mobile-css.test.tsx", "src/__tests__/mission-e2e.test.ts", "src/__tests__/planning.test.ts", - "src/__tests__/session-reconnect.test.ts", ]; const qualityApiTests = [ diff --git a/scripts/lib/test-quarantine.json b/scripts/lib/test-quarantine.json index ce13444a44..3d68d2f90a 100644 --- a/scripts/lib/test-quarantine.json +++ b/scripts/lib/test-quarantine.json @@ -65,11 +65,6 @@ "file": "packages/dashboard/src/__tests__/planning.test.ts", "reason": "FN-6444: orphaned dashboard planning route/API test is slow and fails standalone across stale agent/session mocks plus temp cleanup leakage. Quarantined instead of widening waits/timeouts or weakening assertions.", "quarantinedAt": "2026-06-14" - }, - { - "file": "packages/dashboard/src/__tests__/session-reconnect.test.ts", - "reason": "FN-6444/FN-6447: dashboard API backfill shard 1 observed ENOTEMPTY temp cleanup leakage while replaying planning buffered events. Quarantined on sight for focused rescue/delete review instead of adding waits or weakening assertions.", - "quarantinedAt": "2026-06-14" } ] }