Skip to content

test(web): fix flaky CI from api/client mock.module pollution#297

Merged
mgoldsborough merged 1 commit into
mainfrom
fix/web-client-mock-pollution
May 27, 2026
Merged

test(web): fix flaky CI from api/client mock.module pollution#297
mgoldsborough merged 1 commit into
mainfrom
fix/web-client-mock-pollution

Conversation

@mgoldsborough
Copy link
Copy Markdown
Contributor

Problem

Web unit tests fail intermittently on CI (never reproducibly local) with:

SyntaxError: Export named 'getActiveWorkspaceId' not found in module .../web/src/api/client.ts

Surfaced on #291 CI, but it is pre-existing and unrelated to that PR — the failing commit there was an empty commit, byte-identical to a commit that had passed CI three minutes earlier.

Root cause

Not a circular import, and not a bun-version regression (CI's bun-version: latest = 1.3.14 runs the suite 30/30 green locally).

Several web test files replace the entire ../api/client module with a partial mock.module stub containing only the exports that file needs:

  • InstallConnectorDialog{ installConnector }
  • connector-sections → 5 functions
  • ResourceLinkView{ readResource, ApiClientError }
  • workspace-section{ setActiveWorkspaceId, getActiveWorkspaceId, callTool }

Bun's mock.module registry is process-global. Under CI's parallel execution, one partial stub can be the active version of ../api/client at the moment a bridge test's module graph first evaluates bridge.ts / mcp-bridge-client.ts, which statically import getActiveWorkspaceId — an export those partial stubs omit → the crash. Timing/concurrency dependent, hence CI-only and non-deterministic. The codebase already half-knew this (the a-/b- filename prefixes and the comment in client.ts).

Fix

Make every whole-module ../api/client mock complete: spread the real module, then override only the functions the test stubs.

const actualClient = await import("../api/client");
mock.module("../api/client", () => ({ ...actualClient, installConnector }));

A complete mock is inert when it leaks — no export can go missing regardless of load order. The overrides the components actually call still win; the existing passing tests prove the components never invoke the spread-in real functions.

Because completeness removes the load-order dependency, the a-/b- filename-ordering hacks are no longer needed (and were ineffective under concurrent execution anyway). Renamed:

  • a-t009-acceptance.test.tst009-acceptance.test.ts
  • b-workspace-section.test.tsxworkspace-section.test.tsx

Verification

  • format:check, lint, web tsc: clean
  • Web suite: 385 pass / 0 fail
  • Stress-run 20× on bun 1.3.14 (CI) and 1.3.12 (local): 0 failures each

Note: the original flake does not reproduce locally even pre-fix (the race needs CI's concurrency), so the guarantee here is logical — a complete mock cannot drop an export — not a failing-test-now-passing demonstration.

…r hacks

Several web test files replaced the entire `../api/client` module with a
partial `mock.module` stub containing only the exports that file needed.
Bun's `mock.module` registry is process-global, so under CI's parallel test
execution one of those partial stubs could be the active version of
`../api/client` at the moment a bridge test's module graph first evaluated —
and the bridge imports `getActiveWorkspaceId`, which the partial stubs omit.
The result was the intermittent, CI-only failure:

    SyntaxError: Export named 'getActiveWorkspaceId' not found in module
    .../web/src/api/client.ts

Not a circular import and not a bun-version regression (CI's bun 1.3.14 runs
the suite green locally). The fix is to make every whole-module client mock
complete: spread the real module, then override only the handful of functions
the test stubs. A complete mock is inert when it leaks across files, so no
export can ever go missing regardless of load order.

That also removes the reason `a-t009-acceptance` and `b-workspace-section`
carried filename prefixes to win alphabetical load order — an ineffective hack
under concurrent execution anyway. Renamed both to drop the prefixes.
@mgoldsborough mgoldsborough added the qa-reviewed QA review completed with no critical issues label May 27, 2026
@mgoldsborough mgoldsborough merged commit c6e1e78 into main May 27, 2026
4 checks passed
@mgoldsborough mgoldsborough deleted the fix/web-client-mock-pollution branch May 27, 2026 09:16
mgoldsborough added a commit that referenced this pull request May 27, 2026
…ke) (#298)

* test(web): snapshot real api/client in preload instead of racy await import

PR #297's whole-module api/client mocks spread `await import("../api/client")`
to stay complete. That dynamic import reads the same process-global mock
registry it's defending against, so under CI's file-scheduling it can resolve
to an incomplete mock and propagate the gap — the flaky "Export named
'getActiveWorkspaceId' / 'ApiClientError' not found" link errors still fired
on main (intermittently; passed on re-run).

Capture the real module once in the test preload (web/test/setup.ts), before
any test file can register a mock.module replacement, and freeze it as a plain
object. Each whole-module mock spreads that snapshot instead. Because the
snapshot is taken before any mock exists and is immune to later swaps, every
registered mock is complete by construction — there is no longer a code path
that registers an incomplete api/client.

Web suite: 385 pass / 0 fail, 10x stable on bun 1.3.14. tsc + format clean.

* test(web): migrate remaining api/client mocks to preload snapshot

Spread realClient (the web/test/setup.ts snapshot) into the five test/*
sites that still registered incomplete or registry-derived api/client
mocks: streamingState, inlineError, useWorkspaceBriefing, useShell, and
WorkspaceOverviewPage (dropping its racy `import * as actualClient`).

All 10 mock.module("api/client") sites now register complete mocks, so
no code path can leak an incomplete replacement into the process-global
registry and crash bridge.ts/format-error.ts at link time. Completes the
guarantee the snapshot was introduced to provide. 385/0, tsc clean.

---------

Co-authored-by: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

qa-reviewed QA review completed with no critical issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant