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())