From c25b57b92260f1ca3751af710fec0e4d51cbf73c Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 23 Mar 2026 11:51:05 -0400 Subject: [PATCH] test(web): add regression tests for readdirSync in boot payload path (#2050) Fixes #1936 The /api/boot endpoint relies on bridge-service.ts importing readdirSync from node:fs to list session files. Without this import, listProjectSessions throws ReferenceError and the route returns HTTP 500 on every request. Add two guard tests: - Source-level check that bridge-service.ts imports readdirSync - Integration test that exercises the real filesystem session listing (no listSessions mock) to catch the 500 at runtime Co-authored-by: Claude Opus 4.6 (1M context) --- src/tests/web-boot-node24.test.ts | 23 +++++++++ src/tests/web-bridge-contract.test.ts | 74 +++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/src/tests/web-boot-node24.test.ts b/src/tests/web-boot-node24.test.ts index f103070cf..dd587aefa 100644 --- a/src/tests/web-boot-node24.test.ts +++ b/src/tests/web-boot-node24.test.ts @@ -151,3 +151,26 @@ test("boot route returns { error } JSON on handler failure", async () => { "boot route must return status 500 on error", ) }) + +// --------------------------------------------------------------------------- +// Bug 4 — bridge-service must import readdirSync for session listing (#1936) +// --------------------------------------------------------------------------- + +test("bridge-service imports readdirSync from node:fs (#1936)", async () => { + // The boot payload calls listProjectSessions which uses readdirSync. + // A missing import causes ReferenceError → HTTP 500 on /api/boot. + const { readFileSync } = await import("node:fs") + const { join } = await import("node:path") + + const bridgeSource = readFileSync( + join(process.cwd(), "src", "web", "bridge-service.ts"), + "utf-8", + ) + + assert.match( + bridgeSource, + /import\s*\{[^}]*readdirSync[^}]*\}\s*from\s*["']node:fs["']/, + "bridge-service.ts must import readdirSync from node:fs — " + + "removing it breaks /api/boot with ReferenceError (see #1936)", + ) +}) diff --git a/src/tests/web-bridge-contract.test.ts b/src/tests/web-bridge-contract.test.ts index 1f29ad4ab..cf85c2d85 100644 --- a/src/tests/web-bridge-contract.test.ts +++ b/src/tests/web-bridge-contract.test.ts @@ -659,3 +659,77 @@ test("bridge command/runtime failures are inspectable and redact secret material fixture.cleanup(); } }); + +// --------------------------------------------------------------------------- +// Bug — readdirSync must be available in bridge-service for session listing +// (Fixes #1936: /api/boot returns 500 when readdirSync is missing) +// --------------------------------------------------------------------------- + +test("/api/boot lists sessions from the real filesystem via readdirSync (#1936)", async () => { + const fixture = makeWorkspaceFixture(); + const sessionPath = createSessionFile(fixture.projectCwd, fixture.sessionsDir, "sess-fs", "FS Session"); + const harness = createHarness((command, current) => { + if (command.type === "get_state") { + current.emit({ + id: command.id, + type: "response", + command: "get_state", + success: true, + data: { + sessionId: "sess-fs", + sessionFile: sessionPath, + thinkingLevel: "off", + isStreaming: false, + isCompacting: false, + steeringMode: "all", + followUpMode: "all", + autoCompactionEnabled: false, + autoRetryEnabled: false, + retryInProgress: false, + retryAttempt: 0, + messageCount: 0, + pendingMessageCount: 0, + }, + }); + return; + } + assert.fail(`unexpected command during boot: ${command.type}`); + }); + + // Deliberately omit listSessions so the real listProjectSessions (which + // calls readdirSync) is exercised. If readdirSync is missing from the + // bridge-service node:fs import, this test will throw ReferenceError. + bridge.configureBridgeServiceForTests({ + env: { + ...process.env, + GSD_WEB_PROJECT_CWD: fixture.projectCwd, + GSD_WEB_PROJECT_SESSIONS_DIR: fixture.sessionsDir, + GSD_WEB_PACKAGE_ROOT: repoRoot, + }, + spawn: harness.spawn, + indexWorkspace: async () => fakeWorkspaceIndex(), + getAutoDashboardData: () => fakeAutoDashboardData(), + getOnboardingNeeded: () => false, + }); + + try { + const response = await bootRoute.GET(); + assert.equal(response.status, 200, "/api/boot must not return 500 — readdirSync must be available"); + const payload = await response.json() as any; + + // The real listProjectSessions should have found the session file via readdirSync + assert.ok( + Array.isArray(payload.resumableSessions), + "boot payload must include resumableSessions array", + ); + assert.equal( + payload.resumableSessions.length, + 1, + "readdirSync-based session listing must find the test session file", + ); + assert.equal(payload.resumableSessions[0].id, "sess-fs"); + } finally { + await bridge.resetBridgeServiceForTests(); + fixture.cleanup(); + } +});