@mswjs/interceptors 0.15.3 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +68 -49
  3. package/lib/BatchInterceptor.d.ts +18 -0
  4. package/lib/BatchInterceptor.js +79 -0
  5. package/lib/BatchInterceptor.js.map +1 -0
  6. package/lib/Interceptor.d.ts +49 -0
  7. package/lib/Interceptor.js +197 -0
  8. package/lib/Interceptor.js.map +1 -0
  9. package/lib/RemoteInterceptor.d.ts +24 -0
  10. package/lib/RemoteInterceptor.js +216 -0
  11. package/lib/RemoteInterceptor.js.map +1 -0
  12. package/lib/glossary.d.ts +32 -0
  13. package/lib/glossary.js +3 -0
  14. package/lib/glossary.js.map +1 -0
  15. package/lib/index.d.ts +2 -2
  16. package/lib/index.js +2 -2
  17. package/lib/index.js.map +1 -1
  18. package/lib/interceptors/ClientRequest/NodeClientRequest.d.ts +5 -5
  19. package/lib/interceptors/ClientRequest/NodeClientRequest.js +56 -15
  20. package/lib/interceptors/ClientRequest/NodeClientRequest.js.map +1 -1
  21. package/lib/interceptors/ClientRequest/http.get.d.ts +2 -3
  22. package/lib/interceptors/ClientRequest/http.get.js +2 -5
  23. package/lib/interceptors/ClientRequest/http.get.js.map +1 -1
  24. package/lib/interceptors/ClientRequest/http.request.d.ts +2 -3
  25. package/lib/interceptors/ClientRequest/http.request.js +3 -6
  26. package/lib/interceptors/ClientRequest/http.request.js.map +1 -1
  27. package/lib/interceptors/ClientRequest/index.d.ts +14 -4
  28. package/lib/interceptors/ClientRequest/index.js +59 -46
  29. package/lib/interceptors/ClientRequest/index.js.map +1 -1
  30. package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js +7 -2
  31. package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js.map +1 -1
  32. package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.d.ts +11 -4
  33. package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js +110 -58
  34. package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js.map +1 -1
  35. package/lib/interceptors/XMLHttpRequest/index.d.ts +11 -5
  36. package/lib/interceptors/XMLHttpRequest/index.js +43 -25
  37. package/lib/interceptors/XMLHttpRequest/index.js.map +1 -1
  38. package/lib/interceptors/fetch/index.d.ts +8 -2
  39. package/lib/interceptors/fetch/index.js +120 -68
  40. package/lib/interceptors/fetch/index.js.map +1 -1
  41. package/lib/presets/browser.d.ts +3 -1
  42. package/lib/presets/browser.js +2 -2
  43. package/lib/presets/browser.js.map +1 -1
  44. package/lib/presets/node.d.ts +3 -1
  45. package/lib/presets/node.js +1 -1
  46. package/lib/presets/node.js.map +1 -1
  47. package/lib/utils/AsyncEventEmitter.d.ts +29 -0
  48. package/lib/utils/AsyncEventEmitter.js +241 -0
  49. package/lib/utils/AsyncEventEmitter.js.map +1 -0
  50. package/lib/utils/createLazyCallback.d.ts +11 -0
  51. package/lib/utils/createLazyCallback.js +75 -0
  52. package/lib/utils/createLazyCallback.js.map +1 -0
  53. package/lib/utils/nextTick.d.ts +2 -0
  54. package/lib/utils/nextTick.js +16 -0
  55. package/lib/utils/nextTick.js.map +1 -0
  56. package/lib/utils/toIsoResponse.d.ts +1 -1
  57. package/package.json +6 -6
  58. package/src/BatchInterceptor.test.ts +113 -0
  59. package/src/BatchInterceptor.ts +60 -0
  60. package/src/Interceptor.test.ts +166 -0
  61. package/src/Interceptor.ts +226 -0
  62. package/src/RemoteInterceptor.ts +176 -0
  63. package/src/glossary.ts +42 -0
  64. package/src/index.ts +2 -2
  65. package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +87 -70
  66. package/src/interceptors/ClientRequest/NodeClientRequest.ts +139 -100
  67. package/src/interceptors/ClientRequest/http.get.ts +7 -11
  68. package/src/interceptors/ClientRequest/http.request.ts +8 -12
  69. package/src/interceptors/ClientRequest/index.test.ts +43 -0
  70. package/src/interceptors/ClientRequest/index.ts +46 -46
  71. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.test.ts +9 -0
  72. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +9 -2
  73. package/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts +215 -159
  74. package/src/interceptors/XMLHttpRequest/index.ts +41 -23
  75. package/src/interceptors/fetch/index.ts +81 -55
  76. package/src/presets/browser.ts +3 -3
  77. package/src/presets/node.ts +3 -3
  78. package/src/utils/AsyncEventEmitter.test.ts +68 -0
  79. package/src/utils/AsyncEventEmitter.ts +171 -0
  80. package/src/utils/createLazyCallback.ts +49 -0
  81. package/src/utils/nextTick.ts +11 -0
  82. package/src/utils/toIsoResponse.ts +1 -1
  83. package/lib/createInterceptor.d.ts +0 -54
  84. package/lib/createInterceptor.js +0 -27
  85. package/lib/createInterceptor.js.map +0 -1
  86. package/lib/remote.d.ts +0 -21
  87. package/lib/remote.js +0 -178
  88. package/lib/remote.js.map +0 -1
  89. package/src/createInterceptor.ts +0 -100
  90. package/src/remote.ts +0 -174
  91. package/src/utils/.DS_Store +0 -0
@@ -1,36 +1,35 @@
1
1
  /**
2
2
  * @jest-environment node
3
3
  */
4
- import { EventEmitter } from 'events'
4
+ import { debug } from 'debug'
5
5
  import * as express from 'express'
6
- import { ServerApi, createServer } from '@open-draft/test-server'
6
+ import { HttpServer } from '@open-draft/test-server/http'
7
7
  import { NodeClientRequest } from './NodeClientRequest'
8
8
  import { getIncomingMessageBody } from './utils/getIncomingMessageBody'
9
9
  import { normalizeClientRequestArgs } from './utils/normalizeClientRequestArgs'
10
+ import { AsyncEventEmitter } from '../../utils/AsyncEventEmitter'
11
+ import { sleep } from '../../../test/helpers'
12
+ import { HttpRequestEventMap } from '../../glossary'
10
13
 
11
14
  interface ErrorConnectionRefused extends NodeJS.ErrnoException {
12
15
  address: string
13
16
  port: number
14
17
  }
15
18
 
16
- let httpServer: ServerApi
19
+ const httpServer = new HttpServer((app) => {
20
+ app.post('/comment', (_req, res) => {
21
+ res.status(200).send('original-response')
22
+ })
17
23
 
18
- function waitFor(duration: number): Promise<void> {
19
- return new Promise((resolve) => {
20
- setTimeout(resolve, duration)
24
+ app.post('/write', express.text(), (req, res) => {
25
+ res.status(200).send(req.body)
21
26
  })
22
- }
27
+ })
23
28
 
24
- beforeAll(async () => {
25
- httpServer = await createServer((app) => {
26
- app.post('/comment', (_req, res) => {
27
- res.status(200).send('original-response')
28
- })
29
+ const log = debug('test')
29
30
 
30
- app.post('/write', express.text(), (req, res) => {
31
- res.status(200).send(req.body)
32
- })
33
- })
31
+ beforeAll(async () => {
32
+ await httpServer.listen()
34
33
  })
35
34
 
36
35
  afterAll(async () => {
@@ -38,24 +37,27 @@ afterAll(async () => {
38
37
  })
39
38
 
40
39
  test('gracefully finishes the request when it has a mocked response', (done) => {
40
+ const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
41
41
  const request = new NodeClientRequest(
42
42
  normalizeClientRequestArgs('http:', 'http://any.thing', {
43
43
  method: 'PUT',
44
44
  }),
45
45
  {
46
- observer: new EventEmitter(),
47
- resolver() {
48
- return {
49
- status: 301,
50
- headers: {
51
- 'x-custom-header': 'yes',
52
- },
53
- body: 'mocked-response',
54
- }
55
- },
46
+ emitter,
47
+ log,
56
48
  }
57
49
  )
58
50
 
51
+ emitter.on('request', (request) => {
52
+ request.respondWith({
53
+ status: 301,
54
+ headers: {
55
+ 'x-custom-header': 'yes',
56
+ },
57
+ body: 'mocked-response',
58
+ })
59
+ })
60
+
59
61
  request.on('response', async (response) => {
60
62
  // Request must be marked as finished.
61
63
  expect(request.finished).toEqual(true)
@@ -76,20 +78,22 @@ test('gracefully finishes the request when it has a mocked response', (done) =>
76
78
  })
77
79
 
78
80
  test('responds with a mocked response when requesting an existing hostname', (done) => {
81
+ const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
79
82
  const request = new NodeClientRequest(
80
- normalizeClientRequestArgs('http:', httpServer.http.makeUrl('/comment')),
83
+ normalizeClientRequestArgs('http:', httpServer.http.url('/comment')),
81
84
  {
82
- observer: new EventEmitter(),
83
- async resolver() {
84
- await waitFor(250)
85
- return {
86
- status: 201,
87
- body: 'mocked-response',
88
- }
89
- },
85
+ emitter,
86
+ log,
90
87
  }
91
88
  )
92
89
 
90
+ emitter.on('request', (request) => {
91
+ request.respondWith({
92
+ status: 201,
93
+ body: 'mocked-response',
94
+ })
95
+ })
96
+
93
97
  request.on('response', async (response) => {
94
98
  expect(response.statusCode).toEqual(201)
95
99
 
@@ -103,13 +107,14 @@ test('responds with a mocked response when requesting an existing hostname', (do
103
107
  })
104
108
 
105
109
  test('performs the request as-is given resolver returned no mocked response', (done) => {
110
+ const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
106
111
  const request = new NodeClientRequest(
107
- normalizeClientRequestArgs('http:', httpServer.http.makeUrl('/comment'), {
112
+ normalizeClientRequestArgs('http:', httpServer.http.url('/comment'), {
108
113
  method: 'POST',
109
114
  }),
110
115
  {
111
- observer: new EventEmitter(),
112
- resolver() {},
116
+ emitter,
117
+ log,
113
118
  }
114
119
  )
115
120
 
@@ -131,11 +136,12 @@ test('performs the request as-is given resolver returned no mocked response', (d
131
136
  })
132
137
 
133
138
  test('emits the ENOTFOUND error connecting to a non-existing hostname given no mocked response', (done) => {
139
+ const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
134
140
  const request = new NodeClientRequest(
135
141
  normalizeClientRequestArgs('http:', 'http://non-existing-url.com'),
136
142
  {
137
- observer: new EventEmitter(),
138
- resolver() {},
143
+ emitter,
144
+ log,
139
145
  }
140
146
  )
141
147
 
@@ -149,11 +155,12 @@ test('emits the ENOTFOUND error connecting to a non-existing hostname given no m
149
155
  })
150
156
 
151
157
  test('emits the ECONNREFUSED error connecting to an inactive server given no mocked response', (done) => {
158
+ const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
152
159
  const request = new NodeClientRequest(
153
160
  normalizeClientRequestArgs('http:', 'http://localhost:12345'),
154
161
  {
155
- observer: new EventEmitter(),
156
- resolver() {},
162
+ emitter,
163
+ log,
157
164
  }
158
165
  )
159
166
 
@@ -170,21 +177,24 @@ test('emits the ECONNREFUSED error connecting to an inactive server given no moc
170
177
  })
171
178
 
172
179
  test('does not emit ENOTFOUND error connecting to an inactive server given mocked response', (done) => {
180
+ const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
173
181
  const handleError = jest.fn()
174
182
  const request = new NodeClientRequest(
175
183
  normalizeClientRequestArgs('http:', 'http://non-existing-url.com'),
176
184
  {
177
- observer: new EventEmitter(),
178
- async resolver() {
179
- await waitFor(250)
180
- return {
181
- status: 200,
182
- statusText: 'Works',
183
- }
184
- },
185
+ emitter,
186
+ log,
185
187
  }
186
188
  )
187
189
 
190
+ emitter.on('request', async (request) => {
191
+ await sleep(250)
192
+ request.respondWith({
193
+ status: 200,
194
+ statusText: 'Works',
195
+ })
196
+ })
197
+
188
198
  request.on('error', handleError)
189
199
  request.on('response', (response) => {
190
200
  expect(handleError).not.toHaveBeenCalled()
@@ -196,21 +206,24 @@ test('does not emit ENOTFOUND error connecting to an inactive server given mocke
196
206
  })
197
207
 
198
208
  test('does not emit ECONNREFUSED error connecting to an inactive server given mocked response', (done) => {
209
+ const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
199
210
  const handleError = jest.fn()
200
211
  const request = new NodeClientRequest(
201
212
  normalizeClientRequestArgs('http:', 'http://localhost:9876'),
202
213
  {
203
- observer: new EventEmitter(),
204
- async resolver() {
205
- await waitFor(250)
206
- return {
207
- status: 200,
208
- statusText: 'Works',
209
- }
210
- },
214
+ emitter,
215
+ log,
211
216
  }
212
217
  )
213
218
 
219
+ emitter.on('request', async (request) => {
220
+ await sleep(250)
221
+ request.respondWith({
222
+ status: 200,
223
+ statusText: 'Works',
224
+ })
225
+ })
226
+
214
227
  request.on('error', handleError)
215
228
  request.on('response', (response) => {
216
229
  expect(handleError).not.toHaveBeenCalled()
@@ -222,16 +235,17 @@ test('does not emit ECONNREFUSED error connecting to an inactive server given mo
222
235
  })
223
236
 
224
237
  test('sends the request body to the server given no mocked response', (done) => {
238
+ const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
225
239
  const request = new NodeClientRequest(
226
- normalizeClientRequestArgs('http:', httpServer.http.makeUrl('/write'), {
240
+ normalizeClientRequestArgs('http:', httpServer.http.url('/write'), {
227
241
  method: 'POST',
228
242
  headers: {
229
243
  'Content-Type': 'text/plain',
230
244
  },
231
245
  }),
232
246
  {
233
- observer: new EventEmitter(),
234
- resolver() {},
247
+ emitter,
248
+ log,
235
249
  }
236
250
  )
237
251
 
@@ -251,22 +265,25 @@ test('sends the request body to the server given no mocked response', (done) =>
251
265
  })
252
266
 
253
267
  test('does not send request body to the original server given mocked response', (done) => {
268
+ const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
254
269
  const request = new NodeClientRequest(
255
- normalizeClientRequestArgs('http:', httpServer.http.makeUrl('/write'), {
270
+ normalizeClientRequestArgs('http:', httpServer.http.url('/write'), {
256
271
  method: 'POST',
257
272
  }),
258
273
  {
259
- observer: new EventEmitter(),
260
- async resolver() {
261
- await waitFor(200)
262
- return {
263
- status: 301,
264
- body: 'mock created!',
265
- }
266
- },
274
+ emitter,
275
+ log,
267
276
  }
268
277
  )
269
278
 
279
+ emitter.on('request', async (request) => {
280
+ await sleep(200)
281
+ request.respondWith({
282
+ status: 301,
283
+ body: 'mock created!',
284
+ })
285
+ })
286
+
270
287
  request.write('one')
271
288
  request.write('two')
272
289
 
@@ -1,15 +1,15 @@
1
- import { Debugger, debug } from 'debug'
1
+ import type { Debugger } from 'debug'
2
2
  import type { RequestOptions } from 'http'
3
3
  import { ClientRequest, IncomingMessage } from 'http'
4
4
  import { until } from '@open-draft/until'
5
5
  import { Headers, objectToHeaders } from 'headers-polyfill/lib'
6
6
  import type {
7
+ InteractiveIsomorphicRequest,
7
8
  IsomorphicRequest,
8
9
  MockedResponse,
9
- Observer,
10
- Resolver,
11
- } from '../../createInterceptor'
10
+ } from '../../glossary'
12
11
  import { uuidv4 } from '../../utils/uuid'
12
+ import type { ClientRequestEmitter } from '.'
13
13
  import { concatChunkToBuffer } from './utils/concatChunkToBuffer'
14
14
  import {
15
15
  ClientRequestEndChunk,
@@ -25,12 +25,14 @@ import {
25
25
  normalizeClientRequestWriteArgs,
26
26
  } from './utils/normalizeClientRequestWriteArgs'
27
27
  import { cloneIncomingMessage } from './utils/cloneIncomingMessage'
28
+ import { createLazyCallback } from '../../utils/createLazyCallback'
29
+ import { invariant } from 'outvariant'
28
30
 
29
31
  export type Protocol = 'http' | 'https'
30
32
 
31
33
  export interface NodeClientOptions {
32
- observer: Observer
33
- resolver: Resolver
34
+ emitter: ClientRequestEmitter
35
+ log: Debugger
34
36
  }
35
37
 
36
38
  export class NodeClientRequest extends ClientRequest {
@@ -38,13 +40,17 @@ export class NodeClientRequest extends ClientRequest {
38
40
  * The list of internal Node.js errors to suppress while
39
41
  * using the "mock" response source.
40
42
  */
41
- static suppressErrorCodes = ['ENOTFOUND', 'ECONNREFUSED', 'ECONNRESET']
43
+ static suppressErrorCodes = [
44
+ 'ENOTFOUND',
45
+ 'ECONNREFUSED',
46
+ 'ECONNRESET',
47
+ 'EAI_AGAIN',
48
+ ]
42
49
 
43
50
  private url: URL
44
51
  private options: RequestOptions
45
52
  private response: IncomingMessage
46
- private resolver: Resolver
47
- private observer: Observer
53
+ private emitter: ClientRequestEmitter
48
54
  private log: Debugger
49
55
  private chunks: Array<{
50
56
  chunk?: string | Buffer
@@ -62,7 +68,9 @@ export class NodeClientRequest extends ClientRequest {
62
68
  ) {
63
69
  super(requestOptions, callback)
64
70
 
65
- this.log = debug(`http ${requestOptions.method} ${url.href}`)
71
+ this.log = options.log.extend(
72
+ `request ${requestOptions.method} ${url.href}`
73
+ )
66
74
 
67
75
  this.log('constructing ClientRequest using options:', {
68
76
  url,
@@ -72,8 +80,7 @@ export class NodeClientRequest extends ClientRequest {
72
80
 
73
81
  this.url = url
74
82
  this.options = requestOptions
75
- this.resolver = options.resolver
76
- this.observer = options.observer
83
+ this.emitter = options.emitter
77
84
 
78
85
  // Construct a mocked response message.
79
86
  this.response = new IncomingMessage(this.socket!)
@@ -96,9 +103,14 @@ export class NodeClientRequest extends ClientRequest {
96
103
  }
97
104
 
98
105
  /**
99
- * @todo Do not call callback if the `chunk` is empty.
106
+ * Prevent invoking the callback if the written chunk is empty.
100
107
  * @see https://nodejs.org/api/http.html#requestwritechunk-encoding-callback
101
108
  */
109
+ if (!chunk || chunk.length === 0) {
110
+ this.log('written chunk is empty, skipping callback...')
111
+ return
112
+ }
113
+
102
114
  this.log('executing custom write callback:', callback)
103
115
  callback?.(error)
104
116
  },
@@ -121,110 +133,137 @@ export class NodeClientRequest extends ClientRequest {
121
133
 
122
134
  const requestBody = this.getRequestBody(chunk)
123
135
  const isomorphicRequest = this.toIsomorphicRequest(requestBody)
124
- this.observer.emit('request', isomorphicRequest)
136
+ const interactiveIsomorphicRequest: InteractiveIsomorphicRequest = {
137
+ ...isomorphicRequest,
138
+ respondWith: createLazyCallback({
139
+ maxCalls: 1,
140
+ maxCallsCallback() {
141
+ invariant(
142
+ false,
143
+ 'Failed to respond to "%s %s" request: the "request" event has already been responded to.',
144
+ isomorphicRequest.method,
145
+ isomorphicRequest.url.href
146
+ )
147
+ },
148
+ }),
149
+ }
125
150
 
126
- this.log('executing response resolver...')
151
+ // Notify the interceptor about the request.
152
+ // This will call any "request" listeners the users have.
153
+ this.log(
154
+ 'emitting the "request" event for %d listener(s)...',
155
+ this.emitter.listenerCount('request')
156
+ )
157
+ this.emitter.emit('request', interactiveIsomorphicRequest)
127
158
 
128
159
  // Execute the resolver Promise like a side-effect.
129
160
  // Node.js 16 forces "ClientRequest.end" to be synchronous and return "this".
130
- until(async () => this.resolver(isomorphicRequest, this.response)).then(
131
- ([resolverException, mockedResponse]) => {
132
- this.log('resolver has finished')
133
-
134
- // Halt the request whenever the resolver throws an exception.
135
- if (resolverException) {
136
- this.log(
137
- 'encountered resolver exception, aborting request...',
138
- resolverException
139
- )
140
- this.emit('error', resolverException)
141
- this.terminate()
142
-
143
- return this
144
- }
161
+ until(async () => {
162
+ await this.emitter.untilIdle('request')
163
+ this.log('all request listeners have been resolved!')
164
+
165
+ const [mockedResponse] =
166
+ await interactiveIsomorphicRequest.respondWith.invoked()
167
+ this.log('event.respondWith called with:', mockedResponse)
168
+
169
+ return mockedResponse
170
+ }).then(([resolverException, mockedResponse]) => {
171
+ this.log('the listeners promise awaited!')
172
+
173
+ // Halt the request whenever the resolver throws an exception.
174
+ if (resolverException) {
175
+ this.log(
176
+ 'encountered resolver exception, aborting request...',
177
+ resolverException
178
+ )
179
+ this.emit('error', resolverException)
180
+ this.terminate()
145
181
 
146
- if (mockedResponse) {
147
- this.log('received mocked response:', mockedResponse)
148
- this.responseSource = 'mock'
149
-
150
- const isomorphicResponse = toIsoResponse(mockedResponse)
151
- this.respondWith(mockedResponse)
152
- this.log(
153
- isomorphicResponse.status,
154
- isomorphicResponse.statusText,
155
- isomorphicResponse.body,
156
- '(MOCKED)'
157
- )
182
+ return this
183
+ }
158
184
 
159
- callback?.()
185
+ if (mockedResponse) {
186
+ this.log('received mocked response:', mockedResponse)
187
+ this.responseSource = 'mock'
188
+
189
+ const isomorphicResponse = toIsoResponse(mockedResponse)
190
+ this.respondWith(mockedResponse)
191
+ this.log(
192
+ isomorphicResponse.status,
193
+ isomorphicResponse.statusText,
194
+ isomorphicResponse.body,
195
+ '(MOCKED)'
196
+ )
160
197
 
161
- this.log('emitting the custom "response" event...')
162
- this.observer.emit('response', isomorphicRequest, isomorphicResponse)
198
+ callback?.()
163
199
 
164
- return this
165
- }
200
+ this.log('emitting the custom "response" event...')
166
201
 
167
- this.log('no mocked response found!')
202
+ this.emitter.emit('response', isomorphicRequest, isomorphicResponse)
168
203
 
169
- // Set the response source to "bypass".
170
- // Any errors emitted past this point are not suppressed.
171
- this.responseSource = 'bypass'
204
+ return this
205
+ }
172
206
 
173
- // Propagate previously captured errors.
174
- // For example, a ECONNREFUSED error when connecting to a non-existing host.
175
- if (this.capturedError) {
176
- this.emit('error', this.capturedError)
177
- return this
178
- }
207
+ this.log('no mocked response received!')
179
208
 
180
- // Write the request body chunks in the order of ".write()" calls.
181
- // Note that no request body has been written prior to this point
182
- // in order to prevent the Socket to communicate with a potentially
183
- // existing server.
184
- this.log('writing request chunks...', this.chunks)
209
+ // Set the response source to "bypass".
210
+ // Any errors emitted past this point are not suppressed.
211
+ this.responseSource = 'bypass'
185
212
 
186
- for (const { chunk, encoding, callback } of this.chunks) {
187
- encoding
188
- ? super.write(chunk, encoding, callback)
189
- : super.write(chunk, callback)
190
- }
213
+ // Propagate previously captured errors.
214
+ // For example, a ECONNREFUSED error when connecting to a non-existing host.
215
+ if (this.capturedError) {
216
+ this.emit('error', this.capturedError)
217
+ return this
218
+ }
191
219
 
192
- this.once('error', (error) => {
193
- this.log('original request error:', error)
194
- })
220
+ // Write the request body chunks in the order of ".write()" calls.
221
+ // Note that no request body has been written prior to this point
222
+ // in order to prevent the Socket to communicate with a potentially
223
+ // existing server.
224
+ this.log('writing request chunks...', this.chunks)
195
225
 
196
- this.once('abort', () => {
197
- this.log('original request aborted!')
198
- })
226
+ for (const { chunk, encoding, callback } of this.chunks) {
227
+ encoding
228
+ ? super.write(chunk, encoding, callback)
229
+ : super.write(chunk, callback)
230
+ }
199
231
 
200
- this.once('response-internal', async (response: IncomingMessage) => {
201
- const responseBody = await getIncomingMessageBody(response)
202
- this.log(response.statusCode, response.statusMessage, responseBody)
203
- this.log('original response headers:', response.headers)
204
-
205
- this.log('emitting the custom "response" event...')
206
- this.observer.emit('response', isomorphicRequest, {
207
- status: response.statusCode || 200,
208
- statusText: response.statusMessage || 'OK',
209
- headers: objectToHeaders(response.headers),
210
- body: responseBody,
211
- })
232
+ this.once('error', (error) => {
233
+ this.log('original request error:', error)
234
+ })
235
+
236
+ this.once('abort', () => {
237
+ this.log('original request aborted!')
238
+ })
239
+
240
+ this.once('response-internal', async (response: IncomingMessage) => {
241
+ const responseBody = await getIncomingMessageBody(response)
242
+ this.log(response.statusCode, response.statusMessage, responseBody)
243
+ this.log('original response headers:', response.headers)
244
+
245
+ this.log('emitting the custom "response" event...')
246
+ this.emitter.emit('response', isomorphicRequest, {
247
+ status: response.statusCode || 200,
248
+ statusText: response.statusMessage || 'OK',
249
+ headers: objectToHeaders(response.headers),
250
+ body: responseBody,
212
251
  })
213
-
214
- this.log('performing original request...')
215
-
216
- return super.end(
217
- ...[
218
- chunk,
219
- encoding as any,
220
- () => {
221
- this.log('original request end!')
222
- callback?.()
223
- },
224
- ].filter(Boolean)
225
- )
226
- }
227
- )
252
+ })
253
+
254
+ this.log('performing original request...')
255
+
256
+ return super.end(
257
+ ...[
258
+ chunk,
259
+ encoding as any,
260
+ () => {
261
+ this.log('original request end!')
262
+ callback?.()
263
+ },
264
+ ].filter(Boolean)
265
+ )
266
+ })
228
267
 
229
268
  return this
230
269
  }
@@ -1,25 +1,21 @@
1
1
  import { ClientRequest } from 'node:http'
2
- import { Observer, Resolver } from '../../createInterceptor'
3
- import { NodeClientRequest, Protocol } from './NodeClientRequest'
2
+ import {
3
+ NodeClientOptions,
4
+ NodeClientRequest,
5
+ Protocol,
6
+ } from './NodeClientRequest'
4
7
  import {
5
8
  ClientRequestArgs,
6
9
  normalizeClientRequestArgs,
7
10
  } from './utils/normalizeClientRequestArgs'
8
11
 
9
- export function get(
10
- protocol: Protocol,
11
- resolver: Resolver,
12
- observer: Observer
13
- ) {
12
+ export function get(protocol: Protocol, options: NodeClientOptions) {
14
13
  return (...args: ClientRequestArgs): ClientRequest => {
15
14
  const clientRequestArgs = normalizeClientRequestArgs(
16
15
  `${protocol}:`,
17
16
  ...args
18
17
  )
19
- const request = new NodeClientRequest(clientRequestArgs, {
20
- resolver,
21
- observer,
22
- })
18
+ const request = new NodeClientRequest(clientRequestArgs, options)
23
19
 
24
20
  /**
25
21
  * @note https://nodejs.org/api/http.html#httpgetoptions-callback
@@ -1,19 +1,18 @@
1
1
  import { debug } from 'debug'
2
2
  import { ClientRequest } from 'http'
3
- import { Observer, Resolver } from '../../createInterceptor'
4
- import { NodeClientRequest, Protocol } from './NodeClientRequest'
3
+ import {
4
+ NodeClientOptions,
5
+ NodeClientRequest,
6
+ Protocol,
7
+ } from './NodeClientRequest'
5
8
  import {
6
9
  normalizeClientRequestArgs,
7
10
  ClientRequestArgs,
8
11
  } from './utils/normalizeClientRequestArgs'
9
12
 
10
- const log = debug('http.request')
13
+ const log = debug('http request')
11
14
 
12
- export function request(
13
- protocol: Protocol,
14
- resolver: Resolver,
15
- observer: Observer
16
- ) {
15
+ export function request(protocol: Protocol, options: NodeClientOptions) {
17
16
  return (...args: ClientRequestArgs): ClientRequest => {
18
17
  log('request call (protocol "%s"):', protocol, args)
19
18
 
@@ -21,9 +20,6 @@ export function request(
21
20
  `${protocol}:`,
22
21
  ...args
23
22
  )
24
- return new NodeClientRequest(clientRequestArgs, {
25
- observer,
26
- resolver,
27
- })
23
+ return new NodeClientRequest(clientRequestArgs, options)
28
24
  }
29
25
  }