From 4166bf0497a141784c2d32886cf52d5d005a8c32 Mon Sep 17 00:00:00 2001 From: Michael Solomon <micheal540@gmail.com> Date: Sat, 14 Dec 2024 15:33:28 +0200 Subject: [PATCH 1/2] support ClientRequest constructor --- src/interceptors/ClientRequest/index.ts | 18 +++++ .../http/intercept/client-request.test.ts | 67 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 test/modules/http/intercept/client-request.test.ts diff --git a/src/interceptors/ClientRequest/index.ts b/src/interceptors/ClientRequest/index.ts index c5dac7a3..e7b8a932 100644 --- a/src/interceptors/ClientRequest/index.ts +++ b/src/interceptors/ClientRequest/index.ts @@ -31,6 +31,24 @@ export class ClientRequestInterceptor extends Interceptor<HttpRequestEventMap> { const onRequest = this.onRequest.bind(this) const onResponse = this.onResponse.bind(this) + http.ClientRequest = new Proxy(http.ClientRequest, { + construct: (target, args: Parameters<typeof http.request>) => { + const [url, options, callback] = normalizeClientRequestArgs( + 'http:', + args + ) + + const mockAgent = new MockAgent({ + customAgent: options.agent, + onRequest, + onResponse, + }) + options.agent = mockAgent + + return Reflect.construct(target, [url, options, callback]) + }, + }) + http.request = new Proxy(http.request, { apply: (target, thisArg, args: Parameters<typeof http.request>) => { const [url, options, callback] = normalizeClientRequestArgs( diff --git a/test/modules/http/intercept/client-request.test.ts b/test/modules/http/intercept/client-request.test.ts new file mode 100644 index 00000000..50ffa7f9 --- /dev/null +++ b/test/modules/http/intercept/client-request.test.ts @@ -0,0 +1,67 @@ +import { vi, it, expect, beforeAll, afterEach, afterAll } from 'vitest' +import http from 'http' +import { HttpServer } from '@open-draft/test-server/http' +import { ClientRequestInterceptor } from '../../../../src/interceptors/ClientRequest' +import { REQUEST_ID_REGEXP, waitForClientRequest } from '../../../helpers' +import { RequestController } from '../../../../src/RequestController' +import { HttpRequestEventMap } from '../../../../src/glossary' + +const httpServer = new HttpServer((app) => { + app.get('/user', (req, res) => { + res.status(200).send('user-body') + }) +}) + +const resolver = vi.fn<HttpRequestEventMap['request']>() + +const interceptor = new ClientRequestInterceptor() +interceptor.on('request', resolver) + +beforeAll(async () => { + await httpServer.listen() + interceptor.apply() +}) + +afterEach(() => { + vi.resetAllMocks() +}) + +afterAll(async () => { + interceptor.dispose() + await httpServer.close() +}) + +it('intercepts a ClientRequest request with options', async () => { + const url = new URL(httpServer.http.url('/user?id=123')) + + // send options object instead of (url, options) as in other tests + // because the @types/node is incorrect and does not have the correct signature + const req = new http.ClientRequest({ + hostname: url.hostname, + port: url.port, + path: url.pathname + url.search, + headers: { + 'x-custom-header': 'yes', + }, + }) + req.end() + const { text } = await waitForClientRequest(req) + + expect(resolver).toHaveBeenCalledTimes(1) + + const [{ request, requestId, controller }] = resolver.mock.calls[0] + + expect(request.method).toBe('GET') + expect(request.url).toBe(url.toString()) + expect(Object.fromEntries(request.headers.entries())).toMatchObject({ + 'x-custom-header': 'yes', + }) + expect(request.credentials).toBe('same-origin') + expect(request.body).toBe(null) + expect(controller).toBeInstanceOf(RequestController) + + expect(requestId).toMatch(REQUEST_ID_REGEXP) + + // Must receive the original response. + expect(await text()).toBe('user-body') +}) From b15a2d0c4dc84f04d8af37d0061d321f1bbb9781 Mon Sep 17 00:00:00 2001 From: Michael Solomon <micheal540@gmail.com> Date: Mon, 16 Dec 2024 21:16:12 +0200 Subject: [PATCH 2/2] fix test --- test/modules/http/intercept/client-request.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/modules/http/intercept/client-request.test.ts b/test/modules/http/intercept/client-request.test.ts index 50ffa7f9..e869b365 100644 --- a/test/modules/http/intercept/client-request.test.ts +++ b/test/modules/http/intercept/client-request.test.ts @@ -12,10 +12,7 @@ const httpServer = new HttpServer((app) => { }) }) -const resolver = vi.fn<HttpRequestEventMap['request']>() - const interceptor = new ClientRequestInterceptor() -interceptor.on('request', resolver) beforeAll(async () => { await httpServer.listen() @@ -24,6 +21,7 @@ beforeAll(async () => { afterEach(() => { vi.resetAllMocks() + interceptor.removeAllListeners() }) afterAll(async () => { @@ -33,6 +31,8 @@ afterAll(async () => { it('intercepts a ClientRequest request with options', async () => { const url = new URL(httpServer.http.url('/user?id=123')) + const requestListener = vi.fn<HttpRequestEventMap['request']>() + interceptor.on('request', requestListener) // send options object instead of (url, options) as in other tests // because the @types/node is incorrect and does not have the correct signature @@ -47,9 +47,9 @@ it('intercepts a ClientRequest request with options', async () => { req.end() const { text } = await waitForClientRequest(req) - expect(resolver).toHaveBeenCalledTimes(1) + expect(requestListener).toHaveBeenCalledTimes(1) - const [{ request, requestId, controller }] = resolver.mock.calls[0] + const [{ request, requestId, controller }] = requestListener.mock.calls[0] expect(request.method).toBe('GET') expect(request.url).toBe(url.toString())