From 98854d12fdebd0f6da9a0030f7c85f1951a38ca3 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 2 Sep 2026 16:56:47 +0000 Subject: [PATCH] fix(tests): Improve network mocking to compensate for libraries with frozen imports * move msw setup into global server that is required before any tests start so primitives are patched before libraries import them * refactor withRequestInterception to only add/reset handlers instead of server setup/teardown * run syncBuiltinESMExports to sync patched CJS objects with ESM imports Primarily fixed mocking lastfm requests but will be useful for future testing. --- .mocharc.json | 1 + src/backend/tests/utils/mswGlobalServer.ts | 21 +++++++++++++++++++++ src/backend/tests/utils/networking.ts | 11 +++++++---- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 src/backend/tests/utils/mswGlobalServer.ts 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(); }); }; -- 2.51.2