mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
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:
@@ -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: () => ({}),
|
||||
|
||||
@@ -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,
|
||||
}))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user