nock 14.0.0-beta.7 → 14.0.0-beta.8

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.
package/lib/back.js CHANGED
@@ -77,7 +77,6 @@ function Back(fixtureName, options, nockedFn) {
77
77
  }
78
78
 
79
79
  debug('context:', context)
80
-
81
80
  // If nockedFn is a function then invoke it, otherwise return a promise resolving to nockDone.
82
81
  if (typeof nockedFn === 'function') {
83
82
  nockedFn.call(context, nockDone)
package/lib/common.js CHANGED
@@ -4,6 +4,7 @@ const { common: debug } = require('./debug')
4
4
  const timers = require('timers')
5
5
  const url = require('url')
6
6
  const util = require('util')
7
+ const http = require('http')
7
8
 
8
9
  /**
9
10
  * Normalizes the request options so that it always has `host` property.
@@ -50,82 +51,6 @@ function isUtf8Representable(buffer) {
50
51
  return reconstructedBuffer.equals(buffer)
51
52
  }
52
53
 
53
- // Array where all information about all the overridden requests are held.
54
- let requestOverrides = {}
55
-
56
- /**
57
- * Overrides the current `request` function of `http` and `https` modules with
58
- * our own version which intercepts issues HTTP/HTTPS requests and forwards them
59
- * to the given `newRequest` function.
60
- *
61
- * @param {Function} newRequest - a function handling requests; it accepts four arguments:
62
- * - proto - a string with the overridden module's protocol name (either `http` or `https`)
63
- * - overriddenRequest - the overridden module's request function already bound to module's object
64
- * - options - the options of the issued request
65
- * - callback - the callback of the issued request
66
- */
67
- function overrideRequests(newRequest) {
68
- debug('overriding requests')
69
- ;['http', 'https'].forEach(function (proto) {
70
- debug('- overriding request for', proto)
71
-
72
- const moduleName = proto // 1 to 1 match of protocol and module is fortunate :)
73
- const module = require(proto)
74
- const overriddenRequest = module.request
75
- const overriddenGet = module.get
76
-
77
- if (requestOverrides[moduleName]) {
78
- throw new Error(
79
- `Module's request already overridden for ${moduleName} protocol.`,
80
- )
81
- }
82
-
83
- // Store the properties of the overridden request so that it can be restored later on.
84
- requestOverrides[moduleName] = {
85
- module,
86
- request: overriddenRequest,
87
- get: overriddenGet,
88
- }
89
- // https://nodejs.org/api/http.html#http_http_request_url_options_callback
90
- module.request = function (input, options, callback) {
91
- return newRequest(proto, overriddenRequest.bind(module), [
92
- input,
93
- options,
94
- callback,
95
- ])
96
- }
97
- // https://nodejs.org/api/http.html#http_http_get_options_callback
98
- module.get = function (input, options, callback) {
99
- const req = newRequest(proto, overriddenGet.bind(module), [
100
- input,
101
- options,
102
- callback,
103
- ])
104
- req.end()
105
- return req
106
- }
107
-
108
- debug('- overridden request for', proto)
109
- })
110
- }
111
-
112
- /**
113
- * Restores `request` function of `http` and `https` modules to values they
114
- * held before they were overridden by us.
115
- */
116
- function restoreOverriddenRequests() {
117
- debug('restoring requests')
118
- Object.entries(requestOverrides).forEach(
119
- ([proto, { module, request, get }]) => {
120
- debug('- restoring request for', proto)
121
- module.request = request
122
- module.get = get
123
- debug('- restored request for', proto)
124
- },
125
- )
126
- requestOverrides = {}
127
- }
128
-
129
54
  /**
130
55
  * In WHATWG URL vernacular, this returns the origin portion of a URL.
131
56
  * However, the port is not included if it's standard and not already present on the host.
@@ -616,6 +541,7 @@ function clearTimer(clear, ids) {
616
541
  }
617
542
 
618
543
  function removeAllTimers() {
544
+ debug('remove all timers')
619
545
  clearTimer(clearTimeout, timeouts)
620
546
  clearTimer(clearInterval, intervals)
621
547
  clearTimer(clearImmediate, immediates)
@@ -649,6 +575,31 @@ function isRequestDestroyed(req) {
649
575
  )
650
576
  }
651
577
 
578
+ /**
579
+ * @param {Request} request
580
+ */
581
+ function convertFetchRequestToClientRequest(request) {
582
+ const url = new URL(request.url);
583
+ const options = {
584
+ ...urlToOptions(url),
585
+ method: request.method,
586
+ host: url.hostname,
587
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
588
+ path: url.pathname + url.search,
589
+ proto: url.protocol.slice(0, -1),
590
+ headers: Object.fromEntries(request.headers.entries())
591
+ };
592
+
593
+ // By default, Node adds a host header, but for maximum backward compatibility, we are now removing it.
594
+ // However, we need to consider leaving the header and fixing the tests.
595
+ if (options.headers.host === options.host) {
596
+ const { host, ...restHeaders } = options.headers
597
+ options.headers = restHeaders
598
+ }
599
+
600
+ return new http.ClientRequest(options);
601
+ }
602
+
652
603
  /**
653
604
  * Returns true if the given value is a plain object and not an Array.
654
605
  * @param {*} value
@@ -735,24 +686,6 @@ const expand = input => {
735
686
  return result
736
687
  }
737
688
 
738
- /**
739
- * @param {Request} request
740
- */
741
- function convertFetchRequestToOptions(request) {
742
- const url = new URL(request.url)
743
- const options = {
744
- ...urlToOptions(url),
745
- method: request.method,
746
- host: url.hostname,
747
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
748
- path: url.pathname + url.search,
749
- proto: url.protocol.slice(0, -1),
750
- headers: Object.fromEntries(request.headers.entries()),
751
- }
752
-
753
- return options
754
- }
755
-
756
689
  module.exports = {
757
690
  contentEncoding,
758
691
  dataEqual,
@@ -774,14 +707,12 @@ module.exports = {
774
707
  normalizeClientRequestArgs,
775
708
  normalizeOrigin,
776
709
  normalizeRequestOptions,
777
- overrideRequests,
778
710
  percentDecode,
779
711
  percentEncode,
780
712
  removeAllTimers,
781
- restoreOverriddenRequests,
782
713
  setImmediate,
783
714
  setInterval,
784
715
  setTimeout,
785
716
  stringifyRequest,
786
- convertFetchRequestToClientRequest: convertFetchRequestToOptions,
717
+ convertFetchRequestToClientRequest,
787
718
  }
@@ -1,9 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const zlib = require('node:zlib')
4
- const { headersArrayToObject } = require('./common')
5
3
  const { STATUS_CODES } = require('http')
6
- const { pipeline, Readable } = require('node:stream')
7
4
 
8
5
  /**
9
6
  * Creates a Fetch API `Response` instance from the given
@@ -18,79 +15,37 @@ const { pipeline, Readable } = require('node:stream')
18
15
  const responseStatusCodesWithoutBody = [204, 205, 304]
19
16
 
20
17
  /**
21
- * @param {import('node:http').IncomingMessage} message
18
+ * @param {import('http').IncomingMessage} message
22
19
  */
23
20
  function createResponse(message) {
24
- // https://github.com/Uzlopak/undici/blob/main/lib/fetch/index.js#L2031
25
- const decoders = []
26
- const codings =
27
- message.headers['content-encoding']
28
- ?.toLowerCase()
29
- .split(',')
30
- .map(x => x.trim())
31
- .reverse() || []
32
- for (const coding of codings) {
33
- if (coding === 'gzip' || coding === 'x-gzip') {
34
- decoders.push(
35
- zlib.createGunzip({
36
- flush: zlib.constants.Z_SYNC_FLUSH,
37
- finishFlush: zlib.constants.Z_SYNC_FLUSH,
38
- }),
39
- )
40
- } else if (coding === 'deflate') {
41
- decoders.push(zlib.createInflate())
42
- } else if (coding === 'br') {
43
- decoders.push(zlib.createBrotliDecompress())
44
- } else {
45
- decoders.length = 0
46
- break
47
- }
48
- }
49
-
50
- let isCanceled = false
51
- const chunks = []
52
21
  const responseBodyOrNull = responseStatusCodesWithoutBody.includes(
53
- message.statusCode,
22
+ message.statusCode || 200
54
23
  )
55
24
  ? null
56
25
  : new ReadableStream({
57
- start(controller) {
58
- message.on('data', chunk => chunks.push(chunk))
59
- message.on('end', () => {
60
- pipeline(
61
- Readable.from(chunks),
62
- ...decoders,
63
- async function* (source) {
64
- for await (const chunk of source) {
65
- yield controller.enqueue(chunk)
66
- }
67
- },
68
- error => {
69
- if (error) {
70
- controller.error(error)
71
- } else if (!isCanceled) {
72
- controller.close()
73
- }
74
- },
75
- )
76
- })
77
-
78
- /**
79
- * @todo Should also listen to the "error" on the message
80
- * and forward it to the controller. Otherwise the stream
81
- * will pend indefinitely.
82
- */
83
- },
84
- cancel() {
85
- isCanceled = true
86
- },
87
- })
26
+ start(controller) {
27
+ message.on('data', (chunk) => controller.enqueue(chunk))
28
+ message.on('end', () => controller.close())
29
+ message.on('error', (error) => controller.error(error))
30
+ },
31
+ cancel() {
32
+ message.destroy()
33
+ },
34
+ })
35
+
36
+ const rawHeaders = new Headers()
37
+ for (let i = 0; i < message.rawHeaders.length; i += 2) {
38
+ rawHeaders.append(message.rawHeaders[i], message.rawHeaders[i + 1])
39
+ }
88
40
 
89
- return new Response(responseBodyOrNull, {
41
+ // @mswjs/interceptors supports rawHeaders. https://github.com/mswjs/interceptors/pull/598
42
+ const response = new Response(responseBodyOrNull, {
90
43
  status: message.statusCode,
91
- statusText: STATUS_CODES[message.statusCode],
92
- headers: headersArrayToObject(message.rawHeaders),
44
+ statusText: message.statusMessage || STATUS_CODES[message.statusCode],
45
+ headers: rawHeaders,
93
46
  })
47
+
48
+ return response
94
49
  }
95
50
 
96
51
  module.exports = { createResponse }
package/lib/intercept.js CHANGED
@@ -10,7 +10,17 @@ const { inherits } = require('util')
10
10
  const http = require('http')
11
11
  const { intercept: debug } = require('./debug')
12
12
  const globalEmitter = require('./global_emitter')
13
+ const { BatchInterceptor } = require('@mswjs/interceptors')
14
+ const { FetchInterceptor } = require('@mswjs/interceptors/fetch')
15
+ const { default: nodeInterceptors } = require('@mswjs/interceptors/presets/node')
13
16
  const { createResponse } = require('./create_response')
17
+ const { once } = require('events')
18
+
19
+ const interceptor = new BatchInterceptor({
20
+ name: 'nock-interceptor',
21
+ interceptors: [...nodeInterceptors, new FetchInterceptor()],
22
+ })
23
+ let isNockActive = false
14
24
 
15
25
  /**
16
26
  * @name NetConnectNotAllowedError
@@ -184,8 +194,7 @@ function interceptorsFor(options) {
184
194
  ]
185
195
  } else {
186
196
  debug(
187
- `matched base path (${interceptors.length} interceptor${
188
- interceptors.length > 1 ? 's' : ''
197
+ `matched base path (${interceptors.length} interceptor${interceptors.length > 1 ? 's' : ''
189
198
  })`,
190
199
  )
191
200
  return interceptors
@@ -238,19 +247,6 @@ function removeInterceptor(options) {
238
247
  // Variable where we keep the ClientRequest we have overridden
239
248
  // (which might or might not be node's original http.ClientRequest)
240
249
  let originalClientRequest
241
- // Variable where we keep the fetch we have overridden
242
- let originalFetch
243
-
244
- function ErroringClientRequest(error) {
245
- http.OutgoingMessage.call(this)
246
- process.nextTick(
247
- function () {
248
- this.emit('error', error)
249
- }.bind(this),
250
- )
251
- }
252
-
253
- inherits(ErroringClientRequest, http.ClientRequest)
254
250
 
255
251
  function overrideClientRequest() {
256
252
  // Here's some background discussion about overriding ClientRequest:
@@ -305,9 +301,7 @@ function overrideClientRequest() {
305
301
 
306
302
  // Fallback to original ClientRequest if nock is off or the net connection is enabled.
307
303
  if (isOff() || isEnabledForNetConnect(options)) {
308
- if (options.isFetchRequest === undefined) {
309
- originalClientRequest.apply(this, arguments)
310
- }
304
+ originalClientRequest.apply(this, arguments)
311
305
  } else {
312
306
  common.setImmediate(
313
307
  function () {
@@ -338,27 +332,25 @@ function restoreOverriddenClientRequest() {
338
332
  if (!originalClientRequest) {
339
333
  debug('- ClientRequest was not overridden')
340
334
  } else {
335
+ isNockActive = false;
336
+ interceptor.dispose()
341
337
  http.ClientRequest = originalClientRequest
342
338
  originalClientRequest = undefined
343
339
 
344
- global.fetch = originalFetch
345
- originalFetch = undefined
346
-
347
340
  debug('- ClientRequest restored')
348
341
  }
349
342
  }
350
343
 
351
344
  function isActive() {
352
- // If ClientRequest has been overwritten by Nock then originalClientRequest is not undefined.
353
- // This means that Nock has been activated.
354
- return originalClientRequest !== undefined
345
+ return isNockActive
355
346
  }
356
347
 
357
348
  function interceptorScopes() {
358
349
  const nestedInterceptors = Object.values(allInterceptors).map(
359
350
  i => i.interceptors,
360
351
  )
361
- return [].concat(...nestedInterceptors).map(i => i.scope)
352
+ const scopes = new Set([].concat(...nestedInterceptors).map(i => i.scope))
353
+ return [...scopes]
362
354
  }
363
355
 
364
356
  function isDone() {
@@ -374,97 +366,64 @@ function activeMocks() {
374
366
  }
375
367
 
376
368
  function activate() {
377
- if (originalClientRequest) {
369
+ if (isNockActive) {
378
370
  throw new Error('Nock already active')
379
371
  }
380
372
 
381
- // ----- Overriding http.request and https.request:
382
-
383
- common.overrideRequests(function (proto, overriddenRequest, args) {
384
- // NOTE: overriddenRequest is already bound to its module.
385
-
386
- const { options, callback } = common.normalizeClientRequestArgs(...args)
387
-
388
- if (Object.keys(options).length === 0) {
389
- // As weird as it is, it's possible to call `http.request` without
390
- // options, and it makes a request to localhost or somesuch. We should
391
- // support it too, for parity. However it doesn't work today, and fixing
392
- // it seems low priority. Giving an explicit error is nicer than
393
- // crashing with a weird stack trace. `new ClientRequest()`, nock's
394
- // other client-facing entry point, makes a similar check.
395
- // https://github.com/nock/nock/pull/1386
396
- // https://github.com/nock/nock/pull/1440
397
- throw Error(
398
- 'Making a request with empty `options` is not supported in Nock',
399
- )
400
- }
401
-
402
- // The option per the docs is `protocol`. Its unclear if this line is meant to override that and is misspelled or if
403
- // the intend is to explicitly keep track of which module was called using a separate name.
404
- // Either way, `proto` is used as the source of truth from here on out.
405
- options.proto = proto
406
-
373
+ overrideClientRequest()
374
+ interceptor.apply();
375
+ // Force msw to forward Nock's error instead of coerce it into 500 error
376
+ interceptor.on('unhandledException', ({ controller, error }) => {
377
+ controller.errorWith(error)
378
+ })
379
+ interceptor.on('request', async function ({ request: mswRequest, controller }) {
380
+ const request = mswRequest.clone()
381
+ const { options } = common.normalizeClientRequestArgs(request.url)
382
+ options.proto = options.protocol.slice(0, -1)
383
+ options.method = request.method
407
384
  const interceptors = interceptorsFor(options)
408
-
409
385
  if (isOn() && interceptors) {
410
386
  const matches = interceptors.some(interceptor =>
411
- interceptor.matchOrigin(options),
387
+ interceptor.matchOrigin(options)
412
388
  )
413
389
  const allowUnmocked = interceptors.some(
414
- interceptor => interceptor.options.allowUnmocked,
390
+ interceptor => interceptor.options.allowUnmocked
415
391
  )
416
392
 
393
+ const nockRequest = common.convertFetchRequestToClientRequest(request);
417
394
  if (!matches && allowUnmocked) {
418
- let req
419
- if (proto === 'https') {
420
- const { ClientRequest } = http
421
- http.ClientRequest = originalClientRequest
422
- req = overriddenRequest(options, callback)
423
- http.ClientRequest = ClientRequest
424
- } else {
425
- req = overriddenRequest(options, callback)
426
- }
427
- globalEmitter.emit('no match', req)
428
- return req
429
- }
395
+ globalEmitter.emit('no match', nockRequest)
396
+ } else {
397
+ nockRequest.on('response', nockResponse => {
398
+ // TODO: Consider put empty headers object as default when create the ClientRequest
399
+ if (nockResponse.req.headers) {
400
+ // forward Nock request headers to the MSW request
401
+ Object.entries(nockResponse.req.headers).map(([k, v]) => mswRequest.headers.set(k, v))
402
+ }
403
+
404
+ const response = createResponse(nockResponse)
405
+ controller.respondWith(response)
406
+ })
430
407
 
431
- // NOTE: Since we already overrode the http.ClientRequest we are in fact constructing
432
- // our own OverriddenClientRequest.
433
- return new http.ClientRequest(options, callback)
408
+ const promise = Promise.race([
409
+ // TODO: temp hacky way to handle allowUnmocked in startPlayback
410
+ once(nockRequest, 'real-request'),
411
+ once(nockRequest, 'error'),
412
+ once(nockRequest, 'response'),
413
+ ])
414
+ const buffer = await request.arrayBuffer()
415
+ nockRequest.write(buffer)
416
+ nockRequest.end()
417
+ await promise
418
+ }
434
419
  } else {
435
420
  globalEmitter.emit('no match', options)
436
- if (isOff() || isEnabledForNetConnect(options)) {
437
- return overriddenRequest(options, callback)
438
- } else {
439
- const error = new NetConnectNotAllowedError(options.host, options.path)
440
- return new ErroringClientRequest(error)
421
+ if (!(isOff() || isEnabledForNetConnect(options))) {
422
+ throw new NetConnectNotAllowedError(options.host, options.path)
441
423
  }
442
424
  }
443
425
  })
444
-
445
- originalFetch = global.fetch
446
- global.fetch = async (input, init = undefined) => {
447
- const request = new Request(input, init)
448
- const options = common.convertFetchRequestToClientRequest(request)
449
- options.isFetchRequest = true
450
- const body = await request.arrayBuffer()
451
- const clientRequest = new http.ClientRequest(options)
452
-
453
- // If mock found
454
- if (clientRequest.interceptors) {
455
- return new Promise((resolve, reject) => {
456
- clientRequest.on('response', response => {
457
- resolve(createResponse(response))
458
- })
459
- clientRequest.on('error', reject)
460
- clientRequest.end(body)
461
- })
462
- } else {
463
- return originalFetch(input, init)
464
- }
465
- }
466
-
467
- overrideClientRequest()
426
+ isNockActive = true
468
427
  }
469
428
 
470
429
  module.exports = {
@@ -4,9 +4,7 @@ const { request_overrider: debug } = require('./debug')
4
4
  const {
5
5
  IncomingMessage,
6
6
  ClientRequest,
7
- request: originalHttpRequest,
8
7
  } = require('http')
9
- const { request: originalHttpsRequest } = require('https')
10
8
  const propagate = require('propagate')
11
9
  const common = require('./common')
12
10
  const globalEmitter = require('./global_emitter')
@@ -333,14 +331,7 @@ class InterceptedRequestRouter {
333
331
  )
334
332
 
335
333
  if (allowUnmocked && req instanceof ClientRequest) {
336
- const newReq =
337
- options.proto === 'https'
338
- ? originalHttpsRequest(options)
339
- : originalHttpRequest(options)
340
-
341
- propagate(newReq, req)
342
- // We send the raw buffer as we received it, not as we interpreted it.
343
- newReq.end(requestBodyBuffer)
334
+ req.emit('real-request')
344
335
  } else {
345
336
  const reqStr = common.stringifyRequest(options, requestBodyString)
346
337
  const err = new Error(`Nock: No match for request ${reqStr}`)
@@ -198,10 +198,16 @@ module.exports = class Interceptor {
198
198
  if (!fs) {
199
199
  throw new Error('No fs')
200
200
  }
201
- const readStream = fs.createReadStream(filePath)
202
- readStream.pause()
203
201
  this.filePath = filePath
204
- return this.reply(statusCode, readStream, headers)
202
+ return this.reply(
203
+ statusCode,
204
+ () => {
205
+ const readStream = fs.createReadStream(filePath)
206
+ readStream.pause()
207
+ return readStream
208
+ },
209
+ headers,
210
+ )
205
211
  }
206
212
 
207
213
  // Also match request headers
@@ -446,15 +452,6 @@ module.exports = class Interceptor {
446
452
  markConsumed() {
447
453
  this.interceptionCounter++
448
454
 
449
- if (
450
- (this.scope.shouldPersist() || this.counter > 0) &&
451
- this.interceptionCounter > 1 &&
452
- this.filePath
453
- ) {
454
- this.body = fs.createReadStream(this.filePath)
455
- this.body.pause()
456
- }
457
-
458
455
  remove(this)
459
456
 
460
457
  if (!this.scope.shouldPersist() && this.counter < 1) {
@@ -503,7 +500,7 @@ module.exports = class Interceptor {
503
500
  strFormattingFn = common.percentDecode
504
501
  }
505
502
 
506
- if (queries instanceof URLSearchParams) {
503
+ if (queries instanceof URLSearchParams || typeof queries === 'string') {
507
504
  // Normalize the data into the shape that is matched against.
508
505
  // Duplicate keys are handled by combining the values into an array.
509
506
  queries = querystring.parse(queries.toString())
package/lib/recorder.js CHANGED
@@ -6,11 +6,21 @@ const { inspect } = require('util')
6
6
 
7
7
  const common = require('./common')
8
8
  const { restoreOverriddenClientRequest } = require('./intercept')
9
+ const { BatchInterceptor } = require('@mswjs/interceptors')
10
+ const { FetchInterceptor } = require('@mswjs/interceptors/fetch')
11
+ const { default: nodeInterceptors } = require('@mswjs/interceptors/presets/node')
12
+ const { EventEmitter } = require('stream')
9
13
 
10
14
  const SEPARATOR = '\n<<<<<<-- cut here -->>>>>>\n'
11
15
  let recordingInProgress = false
12
16
  let outputs = []
13
17
 
18
+ // TODO: Consider use one BatchInterceptor (and not one for intercept and one for record)
19
+ const interceptor = new BatchInterceptor({
20
+ name: 'nock-interceptor',
21
+ interceptors: [...nodeInterceptors, new FetchInterceptor()],
22
+ })
23
+
14
24
  function getScope(options) {
15
25
  const { proto, host, port } = common.normalizeRequestOptions(options)
16
26
  return common.normalizeOrigin(proto, host, port)
@@ -210,28 +220,33 @@ function record(recOptions) {
210
220
  // we restore any requests that may have been overridden by other parts of nock (e.g. intercept)
211
221
  // NOTE: This is hacky as hell but it keeps the backward compatibility *and* allows correct
212
222
  // behavior in the face of other modules also overriding ClientRequest.
213
- common.restoreOverriddenRequests()
223
+ // common.restoreOverriddenRequests()
214
224
  // We restore ClientRequest as it messes with recording of modules that also override ClientRequest (e.g. xhr2)
215
225
  restoreOverriddenClientRequest()
216
226
 
217
227
  // We override the requests so that we can save information on them before executing.
218
- common.overrideRequests(function (proto, overriddenRequest, rawArgs) {
219
- const { options, callback } = common.normalizeClientRequestArgs(...rawArgs)
220
- const bodyChunks = []
228
+ interceptor.apply();
229
+ interceptor.on('request', async function ({ request: mswRequest, requestId }) {
230
+ const request = mswRequest.clone()
231
+ const { options } = common.normalizeClientRequestArgs(request.url)
232
+ options.method = request.method
233
+ const proto = options.protocol.slice(0, -1)
221
234
 
222
235
  // Node 0.11 https.request calls http.request -- don't want to record things
223
236
  // twice.
224
237
  /* istanbul ignore if */
225
238
  if (options._recording) {
226
- return overriddenRequest(options, callback)
239
+ return
227
240
  }
228
241
  options._recording = true
229
242
 
230
- const req = overriddenRequest(options, function (res) {
243
+ const req = new EventEmitter();
244
+ req.on('response', function () {
231
245
  debug(thisRecordingId, 'intercepting', proto, 'request to record')
232
246
 
233
- // We put our 'end' listener to the front of the listener array.
234
- res.once('end', function () {
247
+ // Intercept "res.once('end', ...)"-like event
248
+ interceptor.once('response', async function ({ response: mswResponse }) {
249
+ const response = mswResponse.clone()
235
250
  debug(thisRecordingId, proto, 'intercepted request ended')
236
251
 
237
252
  let reqheaders
@@ -239,19 +254,26 @@ function record(recOptions) {
239
254
  if (enableReqHeadersRecording) {
240
255
  // We never record user-agent headers as they are worse than useless -
241
256
  // they actually make testing more difficult without providing any benefit (see README)
242
- reqheaders = req.getHeaders()
257
+ reqheaders = Object.fromEntries(request.headers.entries())
243
258
  common.deleteHeadersField(reqheaders, 'user-agent')
244
259
  }
245
260
 
261
+ const headers = Object.fromEntries(response.headers.entries())
262
+ const res = {
263
+ statusCode: response.status,
264
+ headers,
265
+ rawHeaders: headers,
266
+ }
267
+
246
268
  const generateFn = outputObjects
247
269
  ? generateRequestAndResponseObject
248
270
  : generateRequestAndResponse
249
271
  let out = generateFn({
250
- req,
251
- bodyChunks,
272
+ req: options,
273
+ bodyChunks: [Buffer.from(await request.arrayBuffer())],
252
274
  options,
253
275
  res,
254
- dataChunks,
276
+ dataChunks: [Buffer.from(await response.arrayBuffer())],
255
277
  reqheaders,
256
278
  })
257
279
 
@@ -284,33 +306,6 @@ function record(recOptions) {
284
306
  }
285
307
  })
286
308
 
287
- let encoding
288
- // We need to be aware of changes to the stream's encoding so that we
289
- // don't accidentally mangle the data.
290
- const { setEncoding } = res
291
- res.setEncoding = function (newEncoding) {
292
- encoding = newEncoding
293
- return setEncoding.apply(this, arguments)
294
- }
295
-
296
- const dataChunks = []
297
- // Replace res.push with our own implementation that stores chunks
298
- const origResPush = res.push
299
- res.push = function (data) {
300
- if (data) {
301
- if (encoding) {
302
- data = Buffer.from(data, encoding)
303
- }
304
- dataChunks.push(data)
305
- }
306
-
307
- return origResPush.call(res, data)
308
- }
309
-
310
- if (callback) {
311
- callback(res, options, callback)
312
- }
313
-
314
309
  debug('finished setting up intercepting')
315
310
 
316
311
  // We override both the http and the https modules; when we are
@@ -322,45 +317,10 @@ function record(recOptions) {
322
317
  }
323
318
  })
324
319
 
325
- const recordChunk = (chunk, encoding) => {
326
- debug(thisRecordingId, 'new', proto, 'body chunk')
327
- if (!Buffer.isBuffer(chunk)) {
328
- chunk = Buffer.from(chunk, encoding)
329
- }
330
- bodyChunks.push(chunk)
331
- }
332
-
333
- const oldWrite = req.write
334
- req.write = function (chunk, encoding) {
335
- if (typeof chunk !== 'undefined') {
336
- recordChunk(chunk, encoding)
337
- oldWrite.apply(req, arguments)
338
- } else {
339
- throw new Error('Data was undefined.')
340
- }
341
- }
342
-
343
- // Starting in Node 8, `OutgoingMessage.end()` directly calls an internal
344
- // `write_` function instead of proxying to the public
345
- // `OutgoingMessage.write()` method, so we have to wrap `end` too.
346
- const oldEnd = req.end
347
- req.end = function (chunk, encoding, callback) {
348
- debug('req.end')
349
- if (typeof chunk === 'function') {
350
- callback = chunk
351
- chunk = null
352
- } else if (typeof encoding === 'function') {
353
- callback = encoding
354
- encoding = null
355
- }
356
-
357
- if (chunk) {
358
- recordChunk(chunk, encoding)
359
- }
360
- oldEnd.call(req, chunk, encoding, callback)
361
- }
362
-
363
- return req
320
+ // This is a massive change, we are trying to change minimum code, so we emit end event here
321
+ // because mswjs take care for these events
322
+ // TODO: refactor the recorder, we no longer need all the listeners and can just record the request we get from MSW
323
+ req.emit('response')
364
324
  })
365
325
  }
366
326
 
@@ -371,7 +331,7 @@ function restore() {
371
331
  'restoring all the overridden http/https properties',
372
332
  )
373
333
 
374
- common.restoreOverriddenRequests()
334
+ interceptor.dispose()
375
335
  restoreOverriddenClientRequest()
376
336
  recordingInProgress = false
377
337
  }
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "testing",
8
8
  "isolation"
9
9
  ],
10
- "version": "14.0.0-beta.7",
10
+ "version": "14.0.0-beta.8",
11
11
  "author": "Pedro Teixeira <pedro.teixeira@gmail.com>",
12
12
  "repository": {
13
13
  "type": "git",
@@ -22,6 +22,7 @@
22
22
  "main": "./index.js",
23
23
  "types": "types",
24
24
  "dependencies": {
25
+ "@mswjs/interceptors": "^0.33.1",
25
26
  "json-stringify-safe": "^5.0.1",
26
27
  "propagate": "^2.0.0"
27
28
  },
@@ -44,7 +45,7 @@
44
45
  "mocha": "^9.1.3",
45
46
  "npm-run-all": "^4.1.5",
46
47
  "nyc": "^15.0.0",
47
- "prettier": "3.1.0",
48
+ "prettier": "3.2.5",
48
49
  "proxyquire": "^2.1.0",
49
50
  "rimraf": "^3.0.0",
50
51
  "semantic-release": "^22.0.5",
package/types/index.d.ts CHANGED
@@ -256,13 +256,13 @@ declare namespace nock {
256
256
  (
257
257
  fixtureName: string,
258
258
  options: BackOptions,
259
- nockedFn: (nockDone: () => void) => void,
259
+ nockedFn: (nockDone: () => Promise<void>) => void,
260
260
  ): void
261
261
  (
262
262
  fixtureName: string,
263
263
  options?: BackOptions,
264
264
  ): Promise<{
265
- nockDone: () => void
265
+ nockDone: () => Promise<void>
266
266
  context: BackContext
267
267
  }>
268
268
  }