test(marketplace): import real marketplaceManager in full suite via cache-busted URL

CodeRabbit round 8 follow-up + jatmn round 8 P2: the test file's static
`import { _test } from './marketplaceManager.js'` resolves to whichever
mock was registered first for the bare path. Two other test files
register partial mocks at module top-level:

  - lspRecommendation.test.ts:32 mocks the path with 9 exports (no _test)
  - officialMarketplaceStartupCheck.test.ts:96 mocks it with 9 exports
    (no _test)

Whichever runs first wins, and downstream test files re-importing the
same bare path inherit that partial mock — yielding
"Export named '_test' not found in module './marketplaceManager.js'"
and 4 failing tests in the full suite.

The fix uses three techniques:

1. The static import at the top of marketplaceManager.test.ts is changed
   to `'./marketplaceManager.js?bust=this-test-needs-the-real-module'`.
   Bun treats the query-string suffix as a distinct module id, so the
   import bypasses the bare-path mock.module() registration in
   lspRecommendation.test.ts and officialMarketplaceStartupCheck.test.ts.
   The constant suffix is sufficient because we only need the import to
   happen once at module top-level — no per-test re-evaluation is
   required for the cache-sharing test block.

2. The EXDEV describe block's dynamic re-import (line 263) was already
   using a non-busted URL `'./marketplaceManager.ts'` to pick up the
   mocked axios. It gets the partial mock from the other test files
   instead, with the same `_test` is undefined failure. Apply the
   same `?bust=exdev-test-reimport` suffix so the re-import is treated
   as a fresh module id and still picks up the axios mock (because
   `'axios'` is a different module id than the busted marketplace URL).

3. The two upstream test files (lspRecommendation.test.ts and
   officialMarketplaceStartupCheck.test.ts) had stale manual mock
   lists that were missing recent exports (e.g.
   `normalizeMaxMessagesCompactionThreshold` added in PR #1605,
   `logAntError`, `logMCPError`, `isSourceInBlocklist`, etc.). These
   missing entries broke transitive imports of marketplaceManager →
   config / debug / log / marketplaceHelpers. Switch their mocks to the
   `...await import('...real=...')` spread pattern already used for
   growthbook (line 41). This preserves all real exports while
   overriding the test-specific ones — robust to future additions.

The same comment in the static-import block documents the technique
so future readers understand why a query string is part of the URL.

Verified locally: `bun test --max-concurrency=1 ./src/utils/plugins/`
runs all 37 tests across 5 files (lspRecommendation,
marketplaceManager, officialMarketplaceStartupCheck, pluginLoader,
gitAvailability) with 0 failures, regardless of file order. Each
test file in isolation also passes.
This commit is contained in:
adityachaudhary99
2026-06-13 02:54:37 +05:30
parent bd6387c87c
commit cc6c184492
3 changed files with 76 additions and 2 deletions
@@ -83,7 +83,17 @@ mock.module('./installedPluginsManager.js', () => ({
updateInstallationPathOnDisk: mock(() => {}),
}))
// Spread the real config module to preserve all exports. The marketplace
// module body transitively imports many of these; missing entries
// (e.g. `normalizeMaxMessagesCompactionThreshold` added in PR #1605) break
// any downstream test that re-imports the mocked path in the same Bun
// process. The `?real=...` cache-bust ensures the import bypasses this
// file's own mock.module() registration for the same path.
const realConfig = await import(
`../config.js?real=${Date.now()}-${Math.random()}`
)
mock.module('../config.js', () => ({
...realConfig,
checkHasTrustDialogAccepted: () => true,
enableConfigs: mock(() => {}),
getCurrentProjectConfig: () => ({}),
+28 -2
View File
@@ -19,11 +19,31 @@ import {
type FsOperations,
} from '../fsOperations.js'
import { _test } from './marketplaceManager.js'
import { _test } from './marketplaceManager.js?bust=this-test-needs-the-real-module'
import type { MarketplaceSource } from './schemas.js'
const { loadAndCacheMarketplace } = _test
/**
* The static import above uses a query-string suffix to bypass Bun's
* mock.module() registry under the bare `./marketplaceManager.js` path.
*
* Why: in the full test suite, `lspRecommendation.test.ts` (line 32) and
* `officialMarketplaceStartupCheck.test.ts` (line 96) both call
* `mock.module('./marketplaceManager.js', () => ({...}))` at module
* top-level to stub out `addMarketplaceSource`, `getMarketplace`, etc.
* Neither stub exports `_test`, so a static import under the bare path
* would resolve to a mock without `_test` and every test below would fail
* with "Export named '_test' not found" when those test files run first.
*
* The `?bust=...` suffix makes Bun treat this URL as a distinct module id
* and skip the mock.module() registration for the bare path. The same trick
* is used in src/utils/betas.test.ts (per-test, with `?ts=${Date.now()}-`)
* to force fresh imports that re-evaluate memoized provider detection. We
* use a constant suffix here because we only need the import to happen
* once at module top-level — no per-test re-evaluation.
*/
/**
* Regression test for issue #1500 / PR #1531.
*
@@ -240,7 +260,13 @@ describe('loadAndCacheMarketplace — rename failure fallback (EXDEV)', () => {
}))
// Re-import with mocked axios so the module under test picks up the mock.
const mod = await import('./marketplaceManager.ts')
// The `?bust=` suffix is the same trick the static import at the top of
// this file uses — it gives Bun a unique module id that bypasses any
// mock.module('./marketplaceManager.js', ...) registration made by
// other test files (lspRecommendation, officialMarketplaceStartupCheck).
// Without it, when those test files run first their partial mock is
// picked up here and `_test` is undefined.
const mod = await import('./marketplaceManager.ts?bust=exdev-test-reimport')
loadAndCacheWithMockedAxios = mod._test.loadAndCacheMarketplace
})
@@ -50,7 +50,17 @@ mock.module('../../services/analytics/index.js', () => ({
logEvent: mock(() => {}),
}))
// Spread the real config module so all exports are present. The marketplace
// module body transitively imports many config.js exports; missing entries
// (e.g. `normalizeMaxMessagesCompactionThreshold` added in PR #1605) break
// any downstream test that re-imports the mocked path in the same Bun
// process. The `?real=...` cache-bust ensures the import bypasses this
// file's own mock.module() registration for the same path.
const realConfig = await import(
`../config.js?real=${Date.now()}-${Math.random()}`
)
mock.module('../config.js', () => ({
...realConfig,
checkHasTrustDialogAccepted: () => true,
enableConfigs: mock(() => {}),
getCurrentProjectConfig: () => ({}),
@@ -76,11 +86,28 @@ mock.module('../config.js', () => ({
saveCurrentProjectConfig: mock(() => {}),
}))
// Spread the real debug module to preserve all exports (isDebugToStdErr,
// logAntError, getDebugLogPath, etc.). The marketplace module body
// transitively imports many of these; missing entries break any
// downstream test that re-imports the mocked path in the same Bun
// process. Pre-imported here (not inline) because mock.module's factory
// is sync — see the growthbook pattern at line 41 above.
const realDebug = await import(
`../debug.js?real=${Date.now()}-${Math.random()}`
)
mock.module('../debug.js', () => ({
...realDebug,
logForDebugging: mock(() => {}),
}))
// Spread the real log module to preserve all exports. The marketplace
// module body transitively imports many of these; missing entries break
// any downstream test that re-imports the mocked path.
const realLog = await import(
`../log.js?real=${Date.now()}-${Math.random()}`
)
mock.module('../log.js', () => ({
...realLog,
logError: mock(() => {}),
}))
@@ -89,7 +116,18 @@ mock.module('./gitAvailability.js', () => ({
markGitUnavailable: mock(() => {}),
}))
// Spread the real module so all exports (`isSourceInBlocklist`,
// `formatFailureDetails`, etc.) are present. The market manager module
// body transitively imports many of these, and missing entries break any
// downstream test that re-imports the mocked path in the same Bun
// process (e.g. marketplaceManager.test.ts running after this file).
// Using `?real=...` cache-bust ensures the import bypasses this file's
// own mock.module() registration for the same path.
const realMarketplaceHelpers = await import(
`./marketplaceHelpers.js?real=${Date.now()}-${Math.random()}`
)
mock.module('./marketplaceHelpers.js', () => ({
...realMarketplaceHelpers,
isSourceAllowedByPolicy: () => true,
}))