From 7ccba4f1de0203586e99cdeb3c9b8cf962d2e9fb Mon Sep 17 00:00:00 2001 From: Miles Richardson Date: Wed, 31 Aug 2022 22:41:43 +0100 Subject: [PATCH] test(browser integration): patch upstream `@open-draft/test-server` to fix IPv6 issue - Fix bug in IPv6 URL serialization that isn't available upstream - Source is not available, so waiting on new release Same reasoning as: https://github.com/mswjs/interceptors/pull/283 --- test/graphql-api/mutation.test.ts | 2 +- test/graphql-api/query.test.ts | 2 +- .../response-patching.node.test.ts | 2 +- test/graphql-api/response-patching.test.ts | 2 +- test/jest.setup.ts | 2 +- test/msw-api/req/passthrough.node.test.ts | 2 +- test/msw-api/req/passthrough.test.ts | 2 +- .../setup-server/life-cycle-events/on.test.ts | 2 +- .../removeAllListeners.test.ts | 2 +- .../life-cycle-events/removeListener.test.ts | 2 +- .../scenarios/cookies-request.test.ts | 2 +- .../on-unhandled-request/bypass.test.ts | 2 +- .../on-unhandled-request/error.test.ts | 2 +- .../scenarios/response-patching.test.ts | 2 +- test/msw-api/setup-server/use.test.ts | 2 +- .../scenarios/errors/network-error.test.ts | 2 +- .../scenarios/text-event-stream.test.ts | 2 +- test/patched/OpenDraftTestServer.ts | 119 ++++++++++++++++++ test/rest-api/204-response.test.ts | 2 +- .../rest-api/cookies-inheritance.node.test.ts | 2 +- test/rest-api/cors.test.ts | 2 +- .../request/matching/all.node.test.ts | 2 +- test/support/workerConsole.ts | 2 +- 23 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 test/patched/OpenDraftTestServer.ts diff --git a/test/graphql-api/mutation.test.ts b/test/graphql-api/mutation.test.ts index 57fe5449c..91bc45cee 100644 --- a/test/graphql-api/mutation.test.ts +++ b/test/graphql-api/mutation.test.ts @@ -1,5 +1,5 @@ import * as path from 'path' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../patched/OpenDraftTestServer' import { pageWith } from 'page-with' import { executeGraphQLQuery } from './utils/executeGraphQLQuery' import { gql } from '../support/graphql' diff --git a/test/graphql-api/query.test.ts b/test/graphql-api/query.test.ts index 1c81a3b6f..0c7dacaea 100644 --- a/test/graphql-api/query.test.ts +++ b/test/graphql-api/query.test.ts @@ -1,5 +1,5 @@ import * as path from 'path' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../patched/OpenDraftTestServer' import { pageWith } from 'page-with' import { executeGraphQLQuery } from './utils/executeGraphQLQuery' import { gql } from '../support/graphql' diff --git a/test/graphql-api/response-patching.node.test.ts b/test/graphql-api/response-patching.node.test.ts index 4242d5628..3b1d50af7 100644 --- a/test/graphql-api/response-patching.node.test.ts +++ b/test/graphql-api/response-patching.node.test.ts @@ -6,7 +6,7 @@ import { setupServer } from 'msw/node' import fetch from 'cross-fetch' import { graphql as executeGraphql } from 'graphql' import { buildSchema } from 'graphql/utilities' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../patched/OpenDraftTestServer' import { createGraphQLClient, gql } from '../support/graphql' let httpServer: HttpServer diff --git a/test/graphql-api/response-patching.test.ts b/test/graphql-api/response-patching.test.ts index 8cd6f4a25..57ca77ca9 100644 --- a/test/graphql-api/response-patching.test.ts +++ b/test/graphql-api/response-patching.test.ts @@ -1,7 +1,7 @@ import * as path from 'path' import { pageWith } from 'page-with' import { ExecutionResult, buildSchema, graphql } from 'graphql' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../patched/OpenDraftTestServer' import { SetupWorkerApi } from 'msw' import { gql } from '../support/graphql' diff --git a/test/jest.setup.ts b/test/jest.setup.ts index 8c8240cb1..35e7d52ff 100644 --- a/test/jest.setup.ts +++ b/test/jest.setup.ts @@ -2,7 +2,7 @@ import * as fs from 'fs' import * as path from 'path' import { invariant } from 'outvariant' import { CreateBrowserApi, createBrowser, server } from 'page-with' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from 'patched/OpenDraftTestServer' import { SERVICE_WORKER_BUILD_PATH } from '../config/constants' import { createWorkerConsoleServer, diff --git a/test/msw-api/req/passthrough.node.test.ts b/test/msw-api/req/passthrough.node.test.ts index 4d918ec50..0a7465c90 100644 --- a/test/msw-api/req/passthrough.node.test.ts +++ b/test/msw-api/req/passthrough.node.test.ts @@ -4,7 +4,7 @@ import fetch from 'node-fetch' import { rest } from 'msw' import { setupServer } from 'msw/node' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../patched/OpenDraftTestServer' let httpServer: HttpServer const server = setupServer() diff --git a/test/msw-api/req/passthrough.test.ts b/test/msw-api/req/passthrough.test.ts index 14d335bc8..acc75667c 100644 --- a/test/msw-api/req/passthrough.test.ts +++ b/test/msw-api/req/passthrough.test.ts @@ -4,7 +4,7 @@ import * as path from 'path' import { pageWith } from 'page-with' import { rest, SetupWorkerApi } from 'msw' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../patched/OpenDraftTestServer' declare namespace window { export const msw: { diff --git a/test/msw-api/setup-server/life-cycle-events/on.test.ts b/test/msw-api/setup-server/life-cycle-events/on.test.ts index 1a3bd9dc2..376c68660 100644 --- a/test/msw-api/setup-server/life-cycle-events/on.test.ts +++ b/test/msw-api/setup-server/life-cycle-events/on.test.ts @@ -4,7 +4,7 @@ import fetch from 'node-fetch' import { rest } from 'msw' import { setupServer } from 'msw/node' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../../patched/OpenDraftTestServer' import { waitFor } from '../../../support/waitFor' let httpServer: HttpServer diff --git a/test/msw-api/setup-server/life-cycle-events/removeAllListeners.test.ts b/test/msw-api/setup-server/life-cycle-events/removeAllListeners.test.ts index 65abdc0cb..4812b1e4e 100644 --- a/test/msw-api/setup-server/life-cycle-events/removeAllListeners.test.ts +++ b/test/msw-api/setup-server/life-cycle-events/removeAllListeners.test.ts @@ -4,7 +4,7 @@ import fetch from 'node-fetch' import { rest } from 'msw' import { setupServer } from 'msw/node' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../../patched/OpenDraftTestServer' let httpServer: HttpServer const server = setupServer() diff --git a/test/msw-api/setup-server/life-cycle-events/removeListener.test.ts b/test/msw-api/setup-server/life-cycle-events/removeListener.test.ts index 73d7934ae..61e0c347b 100644 --- a/test/msw-api/setup-server/life-cycle-events/removeListener.test.ts +++ b/test/msw-api/setup-server/life-cycle-events/removeListener.test.ts @@ -4,7 +4,7 @@ import fetch from 'node-fetch' import { rest } from 'msw' import { setupServer } from 'msw/node' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../../patched/OpenDraftTestServer' let httpServer: HttpServer const server = setupServer() diff --git a/test/msw-api/setup-server/scenarios/cookies-request.test.ts b/test/msw-api/setup-server/scenarios/cookies-request.test.ts index 3a1713520..b70e98114 100644 --- a/test/msw-api/setup-server/scenarios/cookies-request.test.ts +++ b/test/msw-api/setup-server/scenarios/cookies-request.test.ts @@ -1,7 +1,7 @@ import * as https from 'https' import { rest } from 'msw' import { setupServer, SetupServerApi } from 'msw/node' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../../patched/OpenDraftTestServer' let httpServer: HttpServer let server: SetupServerApi diff --git a/test/msw-api/setup-server/scenarios/on-unhandled-request/bypass.test.ts b/test/msw-api/setup-server/scenarios/on-unhandled-request/bypass.test.ts index c5e6a9b21..2d2418965 100644 --- a/test/msw-api/setup-server/scenarios/on-unhandled-request/bypass.test.ts +++ b/test/msw-api/setup-server/scenarios/on-unhandled-request/bypass.test.ts @@ -2,7 +2,7 @@ * @jest-environment node */ import fetch from 'node-fetch' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../../../patched/OpenDraftTestServer' import { setupServer } from 'msw/node' import { rest } from 'msw' diff --git a/test/msw-api/setup-server/scenarios/on-unhandled-request/error.test.ts b/test/msw-api/setup-server/scenarios/on-unhandled-request/error.test.ts index 4154c65f6..117d1a0d2 100644 --- a/test/msw-api/setup-server/scenarios/on-unhandled-request/error.test.ts +++ b/test/msw-api/setup-server/scenarios/on-unhandled-request/error.test.ts @@ -2,7 +2,7 @@ * @jest-environment node */ import fetch from 'node-fetch' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../../../patched/OpenDraftTestServer' import { rest } from 'msw' import { setupServer } from 'msw/node' diff --git a/test/msw-api/setup-server/scenarios/response-patching.test.ts b/test/msw-api/setup-server/scenarios/response-patching.test.ts index e7cef071b..a4e831aca 100644 --- a/test/msw-api/setup-server/scenarios/response-patching.test.ts +++ b/test/msw-api/setup-server/scenarios/response-patching.test.ts @@ -2,7 +2,7 @@ * @jest-environment node */ import fetch from 'node-fetch' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../../patched/OpenDraftTestServer' import { rest } from 'msw' import { setupServer } from 'msw/node' diff --git a/test/msw-api/setup-server/use.test.ts b/test/msw-api/setup-server/use.test.ts index 889724aa3..28b712fcf 100644 --- a/test/msw-api/setup-server/use.test.ts +++ b/test/msw-api/setup-server/use.test.ts @@ -5,7 +5,7 @@ import fetch from 'node-fetch' import { rest } from 'msw' import { setupServer, SetupServerApi } from 'msw/node' import { RequestHandler as ExpressRequestHandler } from 'express' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../patched/OpenDraftTestServer' let httpServer: HttpServer let server: SetupServerApi diff --git a/test/msw-api/setup-worker/scenarios/errors/network-error.test.ts b/test/msw-api/setup-worker/scenarios/errors/network-error.test.ts index 230f1d17c..5d7597489 100644 --- a/test/msw-api/setup-worker/scenarios/errors/network-error.test.ts +++ b/test/msw-api/setup-worker/scenarios/errors/network-error.test.ts @@ -3,7 +3,7 @@ import { pageWith } from 'page-with' import { until } from '@open-draft/until' import { workerConsoleSpy } from '../../../../support/workerConsole' import { waitFor } from '../../../../support/waitFor' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../../../patched/OpenDraftTestServer' let httpServer: HttpServer diff --git a/test/msw-api/setup-worker/scenarios/text-event-stream.test.ts b/test/msw-api/setup-worker/scenarios/text-event-stream.test.ts index 810b0d843..705bc87dc 100644 --- a/test/msw-api/setup-worker/scenarios/text-event-stream.test.ts +++ b/test/msw-api/setup-worker/scenarios/text-event-stream.test.ts @@ -1,6 +1,6 @@ import * as path from 'path' import { pageWith } from 'page-with' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../../patched/OpenDraftTestServer' import { sleep } from '../../../support/utils' import { waitFor } from '../../../support/waitFor' diff --git a/test/patched/OpenDraftTestServer.ts b/test/patched/OpenDraftTestServer.ts new file mode 100644 index 000000000..82e30ef6e --- /dev/null +++ b/test/patched/OpenDraftTestServer.ts @@ -0,0 +1,119 @@ +import { + HttpServer as OrigHttpServer, + httpsAgent, +} from '@open-draft/test-server/http' + +export const HttpServer: typeof OrigHttpServer = OrigHttpServer + +export type HttpServer = OrigHttpServer + +const PATCHED_IPV6: unique symbol = Symbol('isPatchedIPv6Compat') + +type PatchedForIPv6Compat = Method & { + [PATCHED_IPV6]?: true +} + +type PatchedGetServerAddress = PatchedForIPv6Compat< + typeof OrigHttpServer.getServerAddress +> +type PatchedBuildHttpServerApi = PatchedForIPv6Compat< + typeof OrigHttpServer.prototype['buildHttpServerApi'] +> + +/** + * Patch `HttpServer` from `../patched/OpenDraftTestServer`, to fix bug in URL + * serialization of IPv6 addresses, by surrounding the host with brackets, e.g.: + * + * correct: { port: 37007, host: '::1', url: 'http://[::1]:37007' } + * incorrect: { port: 37007, host: '::1', url: 'http://::1:37007' } + * ^^^^^ missing [ ] + * + * This bug appears in Node v18 due to new behavior of inheriting operating system + * DNS resolution order, so some dual-stack hosts will resolve addresses including + * `localhost` to `::1`, causing `HttpServer` to bind to `::1` instead of `127.0.0.1`. + * + * This patch is a work-around until the upstream bug is fixed in the + * `../patched/OpenDraftTestServer` package. + * + * To use this patch, change every import of `HttpServer` and `httpsAgent` to + * import from this file, instead of upstream `../patched/OpenDraftTestServer` + * + * To deprecate this patch once the upstream bug is fixed, revert all the + * imports to use `../patched/OpenDraftTestServer`, and delete this file. + */ +const applyPatchFixingIPv6URISerialization = ( + HttpServer: typeof OrigHttpServer, +): void => { + if ((HttpServer.getServerAddress as PatchedGetServerAddress)[PATCHED_IPV6]) { + console.log('DUPLICATE PATCH: HttpServer.getServerAddress') + } + + // eslint-disable-next-line no-var + var origGetServerAddress = HttpServer.getServerAddress + /** + * Patch `static HttpServer.getServer` to fix URI serialization of IPv6 hosts, + * by calling the original method and then modifying the return value. + * + * @see HttpServer.getServerAddress + */ + HttpServer.getServerAddress = function () { + const address = origGetServerAddress.apply( + {}, // we're patching a static method so we expect no valid `this` binding + // eslint-disable-next-line prefer-rest-params + arguments as unknown as Parameters, + ) + + // Copy the same behavior of the original function, but fix the ternary + Object.defineProperty(address, 'href', { + get() { + // assume: host with `:` is definitely not valid IPv4, likely valid IPv6 + return new URL( + `${this.protocol}//${ + this.host.includes(':') && + !this.host.startsWith('[') && + !this.host.endsWith(']') + ? `[${this.host}]` + : this.host + }:${this.port}`, + ).href + }, + enumerable: true, + }) + + return address + } as PatchedGetServerAddress + ;(HttpServer.getServerAddress as PatchedGetServerAddress)[PATCHED_IPV6] = true + + if ( + // @ts-ignore accessing private method .buildHttpServerApi + HttpServer.prototype.buildHttpServerApi[PATCHED_IPV6] + ) { + console.log('DUPLICATE PATCH: HttpServer.prototype.buildHttpServerApi') + } + + /** + * Patch `HttpServer.buildHttpServerApi` (private) class method, to fix + * URI serialization of IPv6 hosts. + * + * This patch re-implements the original behavior of the function, but it's necessary + * so that `buildHttpServerApi` can call the newly patched `HttpServer.getServerAddress` + */ + // @ts-ignore patching a private method + HttpServer.prototype.buildHttpServerApi = (( + server: Parameters[0], + ) => { + const address = HttpServer.getServerAddress(server) + + return { + address, + url(path = '/') { + return new URL(path, address.href).href + }, + } + }) as PatchedBuildHttpServerApi + HttpServer.prototype['buildHttpServerApi'][PATCHED_IPV6] = true +} + +applyPatchFixingIPv6URISerialization(HttpServer) + +export { httpsAgent } diff --git a/test/rest-api/204-response.test.ts b/test/rest-api/204-response.test.ts index 55f824fb0..d8bbcfbf8 100644 --- a/test/rest-api/204-response.test.ts +++ b/test/rest-api/204-response.test.ts @@ -1,6 +1,6 @@ import * as path from 'path' import { pageWith } from 'page-with' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../patched/OpenDraftTestServer' let server: HttpServer diff --git a/test/rest-api/cookies-inheritance.node.test.ts b/test/rest-api/cookies-inheritance.node.test.ts index 44a400419..9732e2885 100644 --- a/test/rest-api/cookies-inheritance.node.test.ts +++ b/test/rest-api/cookies-inheritance.node.test.ts @@ -4,7 +4,7 @@ import fetch from 'node-fetch' import { rest } from 'msw' import { setupServer, SetupServerApi } from 'msw/node' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../patched/OpenDraftTestServer' import { RequestHandler as ExpressRequestHandler } from 'express' let httpServer: HttpServer diff --git a/test/rest-api/cors.test.ts b/test/rest-api/cors.test.ts index b036fb63d..eb58d2e2f 100644 --- a/test/rest-api/cors.test.ts +++ b/test/rest-api/cors.test.ts @@ -1,6 +1,6 @@ import * as path from 'path' import { pageWith } from 'page-with' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../patched/OpenDraftTestServer' let server: HttpServer diff --git a/test/rest-api/request/matching/all.node.test.ts b/test/rest-api/request/matching/all.node.test.ts index 354ff7326..65d7471e9 100644 --- a/test/rest-api/request/matching/all.node.test.ts +++ b/test/rest-api/request/matching/all.node.test.ts @@ -2,7 +2,7 @@ * @jest-environment node */ import fetch, { Response } from 'node-fetch' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../../../patched/OpenDraftTestServer' import { RESTMethods, rest } from 'msw' import { setupServer } from 'msw/node' diff --git a/test/support/workerConsole.ts b/test/support/workerConsole.ts index 52bc32f2f..a174e5b32 100644 --- a/test/support/workerConsole.ts +++ b/test/support/workerConsole.ts @@ -1,5 +1,5 @@ import { ConsoleMessageType } from 'page-with/lib/utils/spyOnConsole' -import { HttpServer } from '@open-draft/test-server/http' +import { HttpServer } from '../patched/OpenDraftTestServer' import { format } from 'outvariant' let workerConsoleServer: HttpServer