diff --git a/.mocharc.json b/.mocharc.json index db4429b9..efe65a8c 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -3,6 +3,7 @@ "extension": "ts", "spec": "./src/backend/tests/**/*.test.ts", "file": [ + "./src/backend/tests/utils/mswGlobalServer.ts", "./src/backend/tests/setup.ts" ], "exit": true diff --git a/src/backend/tests/utils/mswGlobalServer.ts b/src/backend/tests/utils/mswGlobalServer.ts new file mode 100644 index 00000000..c223353b --- /dev/null +++ b/src/backend/tests/utils/mswGlobalServer.ts @@ -0,0 +1,21 @@ +import { setupServer, type SetupServer } from 'msw/node'; + +/** + * Must be loaded (via .mocharc.json "file") before any spec file, and must have no + * other imports, so this listen() call wins the race against any library (like + * lastfm-ts-api) that freezes a reference to `https.request` at its own module load. + * @see https://github.com/nock/nock/issues/2397#issuecomment-1591090893 + */ +import { syncBuiltinESMExports } from 'node:module'; + +export const server: SetupServer = setupServer(); +server.listen({ onUnhandledRequest: 'bypass' }); +// Reassigning https.request/http.request only patches the CJS exports object. +// Named ESM imports of those exports (e.g. `import { request } from 'node:https'`, +// used by lastfm-ts-api) resolve to a snapshot taken independently of that +// reassignment, so they never see MSW's patched version without this call. +// @see https://nodejs.org/api/module.html#modulesyncbuiltinesmexports +syncBuiltinESMExports(); +server.events.on('request:unhandled', ({ request }) => { + console.log('MSW unhandled:', request.method, request.url) +}); diff --git a/src/backend/tests/utils/networking.ts b/src/backend/tests/utils/networking.ts index bc2036ac..abddc743 100644 --- a/src/backend/tests/utils/networking.ts +++ b/src/backend/tests/utils/networking.ts @@ -1,5 +1,6 @@ -import { setupServer, type SetupServer } from 'msw/node'; +import type { setupServer, SetupServer } from 'msw/node'; import type {NodeNetworkErrorCode, NodeNetworkException} from "../../common/errors/NodeErrors.ts"; +import { server } from './mswGlobalServer.ts'; export class MockNetworkError extends Error implements NodeNetworkException { @@ -15,17 +16,19 @@ export class MockNetworkError extends Error implements NodeNetworkException { /** * Adapted from https://github.com/nock/nock/issues/2397#issuecomment-1591090893 + * + * Reuses the single process-wide server from mswGlobalServer.ts instead of + * listen()/close() per test, since some HTTP clients (e.g. lastfm-ts-api) capture a + * reference to https.request at module load time and never see a re-patched one. * */ export type ServerOptions = Parameters; export const withRequestInterception = (handlers: ServerOptions, test: (server: SetupServer) => any) => async () => { - const server = setupServer(...handlers); - server.listen(); + server.use(...handlers); return Promise.resolve(test(server)).finally(() => { server.resetHandlers(); - server.close(); }); };