nock 13.5.4 → 14.0.0-beta.10

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/README.md CHANGED
@@ -1,13 +1,11 @@
1
1
  # Nock
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/nock.svg)][npmjs]
4
- [![Build Status](https://travis-ci.org/nock/nock.svg)][build]
5
4
  ![Coverage Status](http://img.shields.io/badge/coverage-100%25-brightgreen.svg)
6
5
  [![Backers on Open Collective](https://opencollective.com/nock/backers/badge.svg)](#backers)
7
6
  [![Sponsors on Open Collective](https://opencollective.com/nock/sponsors/badge.svg)](#sponsors)
8
7
 
9
8
  [npmjs]: https://www.npmjs.com/package/nock
10
- [build]: https://travis-ci.org/nock/nock
11
9
 
12
10
  > **Notice**
13
11
  >
@@ -1643,10 +1641,10 @@ It does this by manipulating the modules cache of Node in a way that conflicts w
1643
1641
 
1644
1642
  ## Debugging
1645
1643
 
1646
- Nock uses [`debug`](https://github.com/visionmedia/debug), so just run with environmental variable `DEBUG` set to `nock.*`.
1644
+ Nock uses node internals [`debuglog`](https://nodejs.org/api/util.html#utildebuglogsection-callbackg), so just run with environmental variable `NODE_DEBUG` set to `nock:*`.
1647
1645
 
1648
1646
  ```console
1649
- user@local$ DEBUG=nock.* node my_test.js
1647
+ user@local$ NODE_DEBUG=nock:* node my_test.js
1650
1648
  ```
1651
1649
 
1652
1650
  Each step in the matching process is logged this way and can be useful when determining why a request was not intercepted by Nock.
@@ -1660,11 +1658,11 @@ await got('http://example.com/?foo=bar&baz=foz')
1660
1658
  ```
1661
1659
 
1662
1660
  ```console
1663
- user@local$ DEBUG=nock.scope:example.com node my_test.js
1661
+ user@local$ DEBUG=nock:scope:example.com node my_test.js
1664
1662
  ...
1665
- nock.scope:example.com Interceptor queries: {"foo":"bar"} +1ms
1666
- nock.scope:example.com Request queries: {"foo":"bar","baz":"foz"} +0ms
1667
- nock.scope:example.com query matching failed +0ms
1663
+ NOCK:SCOPE:EXAMPLE.COM 103514: Interceptor queries: {"foo":"bar"}
1664
+ NOCK:SCOPE:EXAMPLE.COM 103514: Request queries: {"foo":"bar","baz":"foz"}
1665
+ NOCK:SCOPE:EXAMPLE.COM 103514: query matching failed
1668
1666
  ```
1669
1667
 
1670
1668
  ## Contributing
package/lib/back.js CHANGED
@@ -9,10 +9,9 @@ const {
9
9
  removeAll: cleanAll,
10
10
  } = require('./intercept')
11
11
  const { loadDefs, define } = require('./scope')
12
-
12
+ const { back: debug } = require('./debug')
13
13
  const { format } = require('util')
14
14
  const path = require('path')
15
- const debug = require('debug')('nock.back')
16
15
 
17
16
  let _mode = null
18
17
 
@@ -78,7 +77,6 @@ function Back(fixtureName, options, nockedFn) {
78
77
  }
79
78
 
80
79
  debug('context:', context)
81
-
82
80
  // If nockedFn is a function then invoke it, otherwise return a promise resolving to nockDone.
83
81
  if (typeof nockedFn === 'function') {
84
82
  nockedFn.call(context, nockDone)
package/lib/common.js CHANGED
@@ -1,9 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const debug = require('debug')('nock.common')
3
+ 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.
@@ -593,31 +518,36 @@ function deepEqual(expected, actual) {
593
518
  return expected === actual
594
519
  }
595
520
 
596
- const timeouts = []
597
- const intervals = []
598
- const immediates = []
521
+ const timeouts = new Set()
522
+ const immediates = new Set()
599
523
 
600
524
  const wrapTimer =
601
525
  (timer, ids) =>
602
- (...args) => {
603
- const id = timer(...args)
604
- ids.push(id)
526
+ (callback, ...timerArgs) => {
527
+ const cb = (...callbackArgs) => {
528
+ try {
529
+ // eslint-disable-next-line n/no-callback-literal
530
+ callback(...callbackArgs)
531
+ } finally {
532
+ ids.delete(id)
533
+ }
534
+ }
535
+ const id = timer(cb, ...timerArgs)
536
+ ids.add(id)
605
537
  return id
606
538
  }
607
539
 
608
540
  const setTimeout = wrapTimer(timers.setTimeout, timeouts)
609
- const setInterval = wrapTimer(timers.setInterval, intervals)
610
541
  const setImmediate = wrapTimer(timers.setImmediate, immediates)
611
542
 
612
543
  function clearTimer(clear, ids) {
613
- while (ids.length) {
614
- clear(ids.shift())
615
- }
544
+ ids.forEach(clear)
545
+ ids.clear()
616
546
  }
617
547
 
618
548
  function removeAllTimers() {
549
+ debug('remove all timers')
619
550
  clearTimer(clearTimeout, timeouts)
620
- clearTimer(clearInterval, intervals)
621
551
  clearTimer(clearImmediate, immediates)
622
552
  }
623
553
 
@@ -649,6 +579,31 @@ function isRequestDestroyed(req) {
649
579
  )
650
580
  }
651
581
 
582
+ /**
583
+ * @param {Request} request
584
+ */
585
+ function convertFetchRequestToClientRequest(request) {
586
+ const url = new URL(request.url)
587
+ const options = {
588
+ ...urlToOptions(url),
589
+ method: request.method,
590
+ host: url.hostname,
591
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
592
+ path: url.pathname + url.search,
593
+ proto: url.protocol.slice(0, -1),
594
+ headers: Object.fromEntries(request.headers.entries()),
595
+ }
596
+
597
+ // By default, Node adds a host header, but for maximum backward compatibility, we are now removing it.
598
+ // However, we need to consider leaving the header and fixing the tests.
599
+ if (options.headers.host === options.host) {
600
+ const { host, ...restHeaders } = options.headers
601
+ options.headers = restHeaders
602
+ }
603
+
604
+ return new http.ClientRequest(options)
605
+ }
606
+
652
607
  /**
653
608
  * Returns true if the given value is a plain object and not an Array.
654
609
  * @param {*} value
@@ -756,13 +711,11 @@ module.exports = {
756
711
  normalizeClientRequestArgs,
757
712
  normalizeOrigin,
758
713
  normalizeRequestOptions,
759
- overrideRequests,
760
714
  percentDecode,
761
715
  percentEncode,
762
716
  removeAllTimers,
763
- restoreOverriddenRequests,
764
717
  setImmediate,
765
- setInterval,
766
718
  setTimeout,
767
719
  stringifyRequest,
720
+ convertFetchRequestToClientRequest,
768
721
  }
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const { STATUS_CODES } = require('http')
4
+
5
+ /**
6
+ * Creates a Fetch API `Response` instance from the given
7
+ * `http.IncomingMessage` instance.
8
+ * Inspired by: https://github.com/mswjs/interceptors/blob/04152ed914f8041272b6e92ed374216b8177e1b2/src/interceptors/ClientRequest/utils/createResponse.ts#L8
9
+ */
10
+
11
+ /**
12
+ * Response status codes for responses that cannot have body.
13
+ * @see https://fetch.spec.whatwg.org/#statuses
14
+ */
15
+ const responseStatusCodesWithoutBody = [204, 205, 304]
16
+
17
+ /**
18
+ * @param {import('http').IncomingMessage} message
19
+ */
20
+ function createResponse(message) {
21
+ const responseBodyOrNull = responseStatusCodesWithoutBody.includes(
22
+ message.statusCode || 200,
23
+ )
24
+ ? null
25
+ : new ReadableStream({
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
+ }
40
+
41
+ // @mswjs/interceptors supports rawHeaders. https://github.com/mswjs/interceptors/pull/598
42
+ const response = new Response(responseBodyOrNull, {
43
+ status: message.statusCode,
44
+ statusText: message.statusMessage || STATUS_CODES[message.statusCode],
45
+ headers: rawHeaders,
46
+ })
47
+
48
+ return response
49
+ }
50
+
51
+ module.exports = { createResponse }
package/lib/debug.js ADDED
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ const { debuglog } = require('util')
4
+
5
+ module.exports.back = debuglog('nock:back')
6
+ module.exports.common = debuglog('nock:common')
7
+ module.exports.intercept = debuglog('nock:intercept')
8
+ module.exports.request_overrider = debuglog('nock:request_overrider')
9
+ module.exports.playback_interceptor = debuglog('nock:playback_interceptor')
10
+ module.exports.recorder = debuglog('nock:recorder')
11
+ module.exports.socket = debuglog('nock:socket')
12
+ module.exports.scopeDebuglog = namespace => debuglog(`nock:scope:${namespace}`)
package/lib/intercept.js CHANGED
@@ -8,8 +8,20 @@ const { InterceptedRequestRouter } = require('./intercepted_request_router')
8
8
  const common = require('./common')
9
9
  const { inherits } = require('util')
10
10
  const http = require('http')
11
- const debug = require('debug')('nock.intercept')
11
+ const { intercept: debug } = require('./debug')
12
12
  const globalEmitter = require('./global_emitter')
13
+ const { BatchInterceptor } = require('@mswjs/interceptors')
14
+ const {
15
+ default: nodeInterceptors,
16
+ } = require('@mswjs/interceptors/presets/node')
17
+ const { createResponse } = require('./create_response')
18
+ const { once } = require('events')
19
+
20
+ const interceptor = new BatchInterceptor({
21
+ name: 'nock-interceptor',
22
+ interceptors: nodeInterceptors,
23
+ })
24
+ let isNockActive = false
13
25
 
14
26
  /**
15
27
  * @name NetConnectNotAllowedError
@@ -238,17 +250,6 @@ function removeInterceptor(options) {
238
250
  // (which might or might not be node's original http.ClientRequest)
239
251
  let originalClientRequest
240
252
 
241
- function ErroringClientRequest(error) {
242
- http.OutgoingMessage.call(this)
243
- process.nextTick(
244
- function () {
245
- this.emit('error', error)
246
- }.bind(this),
247
- )
248
- }
249
-
250
- inherits(ErroringClientRequest, http.ClientRequest)
251
-
252
253
  function overrideClientRequest() {
253
254
  // Here's some background discussion about overriding ClientRequest:
254
255
  // - https://github.com/nodejitsu/mock-request/issues/4
@@ -333,6 +334,8 @@ function restoreOverriddenClientRequest() {
333
334
  if (!originalClientRequest) {
334
335
  debug('- ClientRequest was not overridden')
335
336
  } else {
337
+ isNockActive = false
338
+ interceptor.dispose()
336
339
  http.ClientRequest = originalClientRequest
337
340
  originalClientRequest = undefined
338
341
 
@@ -341,9 +344,7 @@ function restoreOverriddenClientRequest() {
341
344
  }
342
345
 
343
346
  function isActive() {
344
- // If ClientRequest has been overwritten by Nock then originalClientRequest is not undefined.
345
- // This means that Nock has been activated.
346
- return originalClientRequest !== undefined
347
+ return isNockActive
347
348
  }
348
349
 
349
350
  function interceptorScopes() {
@@ -367,75 +368,61 @@ function activeMocks() {
367
368
  }
368
369
 
369
370
  function activate() {
370
- if (originalClientRequest) {
371
+ if (isNockActive) {
371
372
  throw new Error('Nock already active')
372
373
  }
373
374
 
374
- // ----- Overriding http.request and https.request:
375
-
376
- common.overrideRequests(function (proto, overriddenRequest, args) {
377
- // NOTE: overriddenRequest is already bound to its module.
378
-
379
- const { options, callback } = common.normalizeClientRequestArgs(...args)
380
-
381
- if (Object.keys(options).length === 0) {
382
- // As weird as it is, it's possible to call `http.request` without
383
- // options, and it makes a request to localhost or somesuch. We should
384
- // support it too, for parity. However it doesn't work today, and fixing
385
- // it seems low priority. Giving an explicit error is nicer than
386
- // crashing with a weird stack trace. `new ClientRequest()`, nock's
387
- // other client-facing entry point, makes a similar check.
388
- // https://github.com/nock/nock/pull/1386
389
- // https://github.com/nock/nock/pull/1440
390
- throw Error(
391
- 'Making a request with empty `options` is not supported in Nock',
392
- )
393
- }
394
-
395
- // The option per the docs is `protocol`. Its unclear if this line is meant to override that and is misspelled or if
396
- // the intend is to explicitly keep track of which module was called using a separate name.
397
- // Either way, `proto` is used as the source of truth from here on out.
398
- options.proto = proto
399
-
400
- const interceptors = interceptorsFor(options)
401
-
402
- if (isOn() && interceptors) {
403
- const matches = interceptors.some(interceptor =>
404
- interceptor.matchOrigin(options),
405
- )
406
- const allowUnmocked = interceptors.some(
407
- interceptor => interceptor.options.allowUnmocked,
408
- )
375
+ overrideClientRequest()
376
+ interceptor.apply()
377
+ // Force msw to forward Nock's error instead of coerce it into 500 error
378
+ interceptor.on('unhandledException', ({ controller, error }) => {
379
+ controller.errorWith(error)
380
+ })
381
+ interceptor.on(
382
+ 'request',
383
+ async function ({ request: mswRequest, controller }) {
384
+ const request = mswRequest.clone()
385
+ const { options } = common.normalizeClientRequestArgs(request.url)
386
+ options.proto = options.protocol.slice(0, -1)
387
+ options.method = request.method
388
+ const interceptors = interceptorsFor(options)
389
+ if (isOn() && interceptors) {
390
+ const matches = interceptors.some(interceptor =>
391
+ interceptor.matchOrigin(options),
392
+ )
393
+ const allowUnmocked = interceptors.some(
394
+ interceptor => interceptor.options.allowUnmocked,
395
+ )
409
396
 
410
- if (!matches && allowUnmocked) {
411
- let req
412
- if (proto === 'https') {
413
- const { ClientRequest } = http
414
- http.ClientRequest = originalClientRequest
415
- req = overriddenRequest(options, callback)
416
- http.ClientRequest = ClientRequest
397
+ const nockRequest = common.convertFetchRequestToClientRequest(request)
398
+ if (!matches && allowUnmocked) {
399
+ globalEmitter.emit('no match', nockRequest)
417
400
  } else {
418
- req = overriddenRequest(options, callback)
401
+ nockRequest.on('response', nockResponse => {
402
+ const response = createResponse(nockResponse)
403
+ controller.respondWith(response)
404
+ })
405
+
406
+ const promise = Promise.race([
407
+ // TODO: temp hacky way to handle allowUnmocked in startPlayback
408
+ once(nockRequest, 'real-request'),
409
+ once(nockRequest, 'error'),
410
+ once(nockRequest, 'response'),
411
+ ])
412
+ const buffer = await request.arrayBuffer()
413
+ nockRequest.write(buffer)
414
+ nockRequest.end()
415
+ await promise
419
416
  }
420
- globalEmitter.emit('no match', req)
421
- return req
422
- }
423
-
424
- // NOTE: Since we already overrode the http.ClientRequest we are in fact constructing
425
- // our own OverriddenClientRequest.
426
- return new http.ClientRequest(options, callback)
427
- } else {
428
- globalEmitter.emit('no match', options)
429
- if (isOff() || isEnabledForNetConnect(options)) {
430
- return overriddenRequest(options, callback)
431
417
  } else {
432
- const error = new NetConnectNotAllowedError(options.host, options.path)
433
- return new ErroringClientRequest(error)
418
+ globalEmitter.emit('no match', options)
419
+ if (!(isOff() || isEnabledForNetConnect(options))) {
420
+ throw new NetConnectNotAllowedError(options.host, options.path)
421
+ }
434
422
  }
435
- }
436
- })
437
-
438
- overrideClientRequest()
423
+ },
424
+ )
425
+ isNockActive = true
439
426
  }
440
427
 
441
428
  module.exports = {
@@ -1,12 +1,7 @@
1
1
  'use strict'
2
2
 
3
- const debug = require('debug')('nock.request_overrider')
4
- const {
5
- IncomingMessage,
6
- ClientRequest,
7
- request: originalHttpRequest,
8
- } = require('http')
9
- const { request: originalHttpsRequest } = require('https')
3
+ const { request_overrider: debug } = require('./debug')
4
+ const { IncomingMessage, ClientRequest } = require('http')
10
5
  const propagate = require('propagate')
11
6
  const common = require('./common')
12
7
  const globalEmitter = require('./global_emitter')
@@ -333,14 +328,7 @@ class InterceptedRequestRouter {
333
328
  )
334
329
 
335
330
  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)
331
+ req.emit('real-request')
344
332
  } else {
345
333
  const reqStr = common.stringifyRequest(options, requestBodyString)
346
334
  const err = new Error(`Nock: No match for request ${reqStr}`)
@@ -248,14 +248,7 @@ module.exports = class Interceptor {
248
248
  }
249
249
 
250
250
  match(req, options, body) {
251
- // check if the logger is enabled because the stringifies can be expensive.
252
- if (this.scope.logger.enabled) {
253
- this.scope.logger(
254
- 'attempting match %s, body = %s',
255
- stringify(options),
256
- stringify(body),
257
- )
258
- }
251
+ this.scope.logger('attempting match %j, body = %j', options, body)
259
252
 
260
253
  const method = (options.method || 'GET').toUpperCase()
261
254
  let { path = '/' } = options
package/lib/match_body.js CHANGED
@@ -63,10 +63,11 @@ module.exports = function matchBody(options, spec, body) {
63
63
 
64
64
  function mapValues(object, cb) {
65
65
  const keys = Object.keys(object)
66
+ const clonedObject = { ...object }
66
67
  for (const key of keys) {
67
- object[key] = cb(object[key], key, object)
68
+ clonedObject[key] = cb(clonedObject[key], key, clonedObject)
68
69
  }
69
- return object
70
+ return clonedObject
70
71
  }
71
72
 
72
73
  /**
@@ -3,7 +3,7 @@
3
3
  const stream = require('stream')
4
4
  const util = require('util')
5
5
  const zlib = require('zlib')
6
- const debug = require('debug')('nock.playback_interceptor')
6
+ const { playback_interceptor: debug } = require('./debug')
7
7
  const common = require('./common')
8
8
 
9
9
  function parseJSONRequestBody(req, requestBody) {
package/lib/recorder.js CHANGED
@@ -1,16 +1,27 @@
1
1
  'use strict'
2
2
 
3
- const debug = require('debug')('nock.recorder')
3
+ const { recorder: debug } = require('./debug')
4
4
  const querystring = require('querystring')
5
5
  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 {
11
+ default: nodeInterceptors,
12
+ } = require('@mswjs/interceptors/presets/node')
13
+ const { EventEmitter } = require('stream')
9
14
 
10
15
  const SEPARATOR = '\n<<<<<<-- cut here -->>>>>>\n'
11
16
  let recordingInProgress = false
12
17
  let outputs = []
13
18
 
19
+ // TODO: Consider use one BatchInterceptor (and not one for intercept and one for record)
20
+ const interceptor = new BatchInterceptor({
21
+ name: 'nock-interceptor',
22
+ interceptors: nodeInterceptors,
23
+ })
24
+
14
25
  function getScope(options) {
15
26
  const { proto, host, port } = common.normalizeRequestOptions(options)
16
27
  return common.normalizeOrigin(proto, host, port)
@@ -210,158 +221,114 @@ function record(recOptions) {
210
221
  // we restore any requests that may have been overridden by other parts of nock (e.g. intercept)
211
222
  // NOTE: This is hacky as hell but it keeps the backward compatibility *and* allows correct
212
223
  // behavior in the face of other modules also overriding ClientRequest.
213
- common.restoreOverriddenRequests()
224
+ // common.restoreOverriddenRequests()
214
225
  // We restore ClientRequest as it messes with recording of modules that also override ClientRequest (e.g. xhr2)
215
226
  restoreOverriddenClientRequest()
216
227
 
217
228
  // 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 = []
221
-
222
- // Node 0.11 https.request calls http.request -- don't want to record things
223
- // twice.
224
- /* istanbul ignore if */
225
- if (options._recording) {
226
- return overriddenRequest(options, callback)
227
- }
228
- options._recording = true
229
-
230
- const req = overriddenRequest(options, function (res) {
231
- debug(thisRecordingId, 'intercepting', proto, 'request to record')
232
-
233
- // We put our 'end' listener to the front of the listener array.
234
- res.once('end', function () {
235
- debug(thisRecordingId, proto, 'intercepted request ended')
236
-
237
- let reqheaders
238
- // Ignore request headers completely unless it was explicitly enabled by the user (see README)
239
- if (enableReqHeadersRecording) {
240
- // We never record user-agent headers as they are worse than useless -
241
- // they actually make testing more difficult without providing any benefit (see README)
242
- reqheaders = req.getHeaders()
243
- common.deleteHeadersField(reqheaders, 'user-agent')
244
- }
229
+ interceptor.apply()
230
+ interceptor.on(
231
+ 'request',
232
+ async function ({ request: mswRequest, requestId }) {
233
+ const request = mswRequest.clone()
234
+ const { options } = common.normalizeClientRequestArgs(request.url)
235
+ options.method = request.method
236
+ const proto = options.protocol.slice(0, -1)
237
+
238
+ // Node 0.11 https.request calls http.request -- don't want to record things
239
+ // twice.
240
+ /* istanbul ignore if */
241
+ if (options._recording) {
242
+ return
243
+ }
244
+ options._recording = true
245
+
246
+ const req = new EventEmitter()
247
+ req.on('response', function () {
248
+ debug(thisRecordingId, 'intercepting', proto, 'request to record')
249
+
250
+ // Intercept "res.once('end', ...)"-like event
251
+ interceptor.once(
252
+ 'response',
253
+ async function ({ response: mswResponse }) {
254
+ const response = mswResponse.clone()
255
+ debug(thisRecordingId, proto, 'intercepted request ended')
256
+
257
+ let reqheaders
258
+ // Ignore request headers completely unless it was explicitly enabled by the user (see README)
259
+ if (enableReqHeadersRecording) {
260
+ // We never record user-agent headers as they are worse than useless -
261
+ // they actually make testing more difficult without providing any benefit (see README)
262
+ reqheaders = Object.fromEntries(request.headers.entries())
263
+ common.deleteHeadersField(reqheaders, 'user-agent')
264
+ }
245
265
 
246
- const generateFn = outputObjects
247
- ? generateRequestAndResponseObject
248
- : generateRequestAndResponse
249
- let out = generateFn({
250
- req,
251
- bodyChunks,
252
- options,
253
- res,
254
- dataChunks,
255
- reqheaders,
256
- })
257
-
258
- debug('out:', out)
259
-
260
- // Check that the request was made during the current recording.
261
- // If it hasn't then skip it. There is no other simple way to handle
262
- // this as it depends on the timing of requests and responses. Throwing
263
- // will make some recordings/unit tests fail randomly depending on how
264
- // fast/slow the response arrived.
265
- // If you are seeing this error then you need to make sure that all
266
- // the requests made during a single recording session finish before
267
- // ending the same recording session.
268
- if (thisRecordingId !== currentRecordingId) {
269
- debug('skipping recording of an out-of-order request', out)
270
- return
271
- }
266
+ const headers = Object.fromEntries(response.headers.entries())
267
+ const res = {
268
+ statusCode: response.status,
269
+ headers,
270
+ rawHeaders: headers,
271
+ }
272
272
 
273
- outputs.push(out)
273
+ const generateFn = outputObjects
274
+ ? generateRequestAndResponseObject
275
+ : generateRequestAndResponse
276
+ let out = generateFn({
277
+ req: options,
278
+ bodyChunks: [Buffer.from(await request.arrayBuffer())],
279
+ options,
280
+ res,
281
+ dataChunks: [Buffer.from(await response.arrayBuffer())],
282
+ reqheaders,
283
+ })
284
+
285
+ debug('out:', out)
286
+
287
+ // Check that the request was made during the current recording.
288
+ // If it hasn't then skip it. There is no other simple way to handle
289
+ // this as it depends on the timing of requests and responses. Throwing
290
+ // will make some recordings/unit tests fail randomly depending on how
291
+ // fast/slow the response arrived.
292
+ // If you are seeing this error then you need to make sure that all
293
+ // the requests made during a single recording session finish before
294
+ // ending the same recording session.
295
+ if (thisRecordingId !== currentRecordingId) {
296
+ debug('skipping recording of an out-of-order request', out)
297
+ return
298
+ }
274
299
 
275
- if (!dontPrint) {
276
- if (useSeparator) {
277
- if (typeof out !== 'string') {
278
- out = JSON.stringify(out, null, 2)
300
+ outputs.push(out)
301
+
302
+ if (!dontPrint) {
303
+ if (useSeparator) {
304
+ if (typeof out !== 'string') {
305
+ out = JSON.stringify(out, null, 2)
306
+ }
307
+ logging(SEPARATOR + out + SEPARATOR)
308
+ } else {
309
+ logging(out)
310
+ }
279
311
  }
280
- logging(SEPARATOR + out + SEPARATOR)
281
- } else {
282
- logging(out)
283
- }
284
- }
285
- })
312
+ },
313
+ )
286
314
 
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
- }
315
+ debug('finished setting up intercepting')
295
316
 
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)
317
+ // We override both the http and the https modules; when we are
318
+ // serializing the request, we need to know which was called.
319
+ // By stuffing the state, we can make sure that nock records
320
+ // the intended protocol.
321
+ if (proto === 'https') {
322
+ options.proto = 'https'
305
323
  }
324
+ })
306
325
 
307
- return origResPush.call(res, data)
308
- }
309
-
310
- if (callback) {
311
- callback(res, options, callback)
312
- }
313
-
314
- debug('finished setting up intercepting')
315
-
316
- // We override both the http and the https modules; when we are
317
- // serializing the request, we need to know which was called.
318
- // By stuffing the state, we can make sure that nock records
319
- // the intended protocol.
320
- if (proto === 'https') {
321
- options.proto = 'https'
322
- }
323
- })
324
-
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
364
- })
326
+ // This is a massive change, we are trying to change minimum code, so we emit end event here
327
+ // because mswjs take care for these events
328
+ // TODO: refactor the recorder, we no longer need all the listeners and can just record the request we get from MSW
329
+ req.emit('response')
330
+ },
331
+ )
365
332
  }
366
333
 
367
334
  // Restore *all* the overridden http/https modules' properties.
@@ -371,7 +338,7 @@ function restore() {
371
338
  'restoring all the overridden http/https properties',
372
339
  )
373
340
 
374
- common.restoreOverriddenRequests()
341
+ interceptor.dispose()
375
342
  restoreOverriddenClientRequest()
376
343
  recordingInProgress = false
377
344
  }
package/lib/scope.js CHANGED
@@ -3,11 +3,11 @@
3
3
  /**
4
4
  * @module nock/scope
5
5
  */
6
+ const { scopeDebuglog } = require('./debug')
6
7
  const { addInterceptor, isOn } = require('./intercept')
7
8
  const common = require('./common')
8
9
  const assert = require('assert')
9
10
  const url = require('url')
10
- const debug = require('debug')('nock.scope')
11
11
  const { EventEmitter } = require('events')
12
12
  const Interceptor = require('./interceptor')
13
13
 
@@ -99,7 +99,7 @@ class Scope extends EventEmitter {
99
99
  logNamespace = this.urlParts.host
100
100
  }
101
101
 
102
- this.logger = debug.extend(logNamespace)
102
+ this.logger = scopeDebuglog(logNamespace)
103
103
  }
104
104
 
105
105
  add(key, interceptor) {
package/lib/socket.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { EventEmitter } = require('events')
4
- const debug = require('debug')('nock.socket')
4
+ const { socket: debug } = require('./debug')
5
5
 
6
6
  module.exports = class Socket extends EventEmitter {
7
7
  constructor(options) {
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "testing",
8
8
  "isolation"
9
9
  ],
10
- "version": "13.5.4",
10
+ "version": "14.0.0-beta.10",
11
11
  "author": "Pedro Teixeira <pedro.teixeira@gmail.com>",
12
12
  "repository": {
13
13
  "type": "git",
@@ -17,12 +17,12 @@
17
17
  "url": "https://github.com/nock/nock/issues"
18
18
  },
19
19
  "engines": {
20
- "node": ">= 10.13"
20
+ "node": ">= 18"
21
21
  },
22
22
  "main": "./index.js",
23
23
  "types": "types",
24
24
  "dependencies": {
25
- "debug": "^4.1.0",
25
+ "@mswjs/interceptors": "^0.34.0",
26
26
  "json-stringify-safe": "^5.0.1",
27
27
  "propagate": "^2.0.0"
28
28
  },
@@ -45,11 +45,11 @@
45
45
  "mocha": "^9.1.3",
46
46
  "npm-run-all": "^4.1.5",
47
47
  "nyc": "^15.0.0",
48
- "prettier": "3.2.4",
48
+ "prettier": "3.2.5",
49
49
  "proxyquire": "^2.1.0",
50
50
  "rimraf": "^3.0.0",
51
51
  "semantic-release": "^22.0.5",
52
- "sinon": "^15.0.1",
52
+ "sinon": "^17.0.1",
53
53
  "sinon-chai": "^3.7.0",
54
54
  "typescript": "^5.0.4"
55
55
  },
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
  }