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 +0 -1
- package/lib/common.js +28 -97
- package/lib/create_response.js +22 -67
- package/lib/intercept.js +58 -99
- package/lib/intercepted_request_router.js +1 -10
- package/lib/interceptor.js +10 -13
- package/lib/recorder.js +39 -79
- package/package.json +3 -2
- package/types/index.d.ts +2 -2
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
|
|
717
|
+
convertFetchRequestToClientRequest,
|
|
787
718
|
}
|
package/lib/create_response.js
CHANGED
|
@@ -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('
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
369
|
+
if (isNockActive) {
|
|
378
370
|
throw new Error('Nock already active')
|
|
379
371
|
}
|
|
380
372
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}`)
|
package/lib/interceptor.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
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
|
|
239
|
+
return
|
|
227
240
|
}
|
|
228
241
|
options._recording = true
|
|
229
242
|
|
|
230
|
-
const req =
|
|
243
|
+
const req = new EventEmitter();
|
|
244
|
+
req.on('response', function () {
|
|
231
245
|
debug(thisRecordingId, 'intercepting', proto, 'request to record')
|
|
232
246
|
|
|
233
|
-
//
|
|
234
|
-
|
|
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 =
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
}
|