nock 11.5.0 → 11.7.2
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 +9 -0
- package/index.js +3 -1
- package/lib/back.js +12 -8
- package/lib/common.js +38 -11
- package/lib/delayed_body.js +1 -1
- package/lib/intercept.js +14 -12
- package/lib/intercepted_request_router.js +12 -8
- package/lib/interceptor.js +11 -9
- package/lib/match_body.js +5 -17
- package/lib/playback_interceptor.js +10 -12
- package/lib/recorder.js +27 -30
- package/lib/scope.js +3 -2
- package/lib/socket.js +16 -0
- package/package.json +12 -11
- package/types/index.d.ts +2 -0
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@ For instance, if a module performs HTTP requests to a CouchDB server or makes HT
|
|
|
58
58
|
- [Expectations](#expectations)
|
|
59
59
|
- [.isDone()](#isdone)
|
|
60
60
|
- [.cleanAll()](#cleanall)
|
|
61
|
+
- [.abortPendingRequests()](#abortpendingrequests)
|
|
61
62
|
- [.persist()](#persist)
|
|
62
63
|
- [.pendingMocks()](#pendingmocks)
|
|
63
64
|
- [.activeMocks()](#activemocks)
|
|
@@ -995,6 +996,14 @@ You can cleanup all the prepared mocks (could be useful to cleanup some state af
|
|
|
995
996
|
nock.cleanAll()
|
|
996
997
|
```
|
|
997
998
|
|
|
999
|
+
### .abortPendingRequests()
|
|
1000
|
+
|
|
1001
|
+
You can abort all current pending request like this:
|
|
1002
|
+
|
|
1003
|
+
```js
|
|
1004
|
+
nock.abortPendingRequests()
|
|
1005
|
+
```
|
|
1006
|
+
|
|
998
1007
|
### .persist()
|
|
999
1008
|
|
|
1000
1009
|
You can make all the interceptors for a scope persist by calling `.persist()` on it:
|
package/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
disableNetConnect,
|
|
13
13
|
enableNetConnect,
|
|
14
14
|
removeAll,
|
|
15
|
+
abortPendingRequests,
|
|
15
16
|
} = require('./lib/intercept')
|
|
16
17
|
const recorder = require('./lib/recorder')
|
|
17
18
|
const { Scope, load, loadDefs, define } = require('./lib/scope')
|
|
@@ -28,13 +29,14 @@ Object.assign(module.exports, {
|
|
|
28
29
|
disableNetConnect,
|
|
29
30
|
enableNetConnect,
|
|
30
31
|
// TODO-12.x Historically `nock.cleanAll()` has returned the nock global.
|
|
31
|
-
// The other global methods do
|
|
32
|
+
// The other global methods do not do this, so it's not clear this was
|
|
32
33
|
// deliberate or is even helpful. This shim is included for backward
|
|
33
34
|
// compatibility and shoulud be replaced with an alias to `removeAll()`.
|
|
34
35
|
cleanAll() {
|
|
35
36
|
removeAll()
|
|
36
37
|
return module.exports
|
|
37
38
|
},
|
|
39
|
+
abortPendingRequests,
|
|
38
40
|
load,
|
|
39
41
|
loadDefs,
|
|
40
42
|
define,
|
package/lib/back.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const assert = require('assert')
|
|
3
4
|
const _ = require('lodash')
|
|
4
5
|
const recorder = require('./recorder')
|
|
5
6
|
const {
|
|
@@ -12,7 +13,6 @@ const { loadDefs, define } = require('./scope')
|
|
|
12
13
|
|
|
13
14
|
const { format } = require('util')
|
|
14
15
|
const path = require('path')
|
|
15
|
-
const { expect } = require('chai')
|
|
16
16
|
const debug = require('debug')('nock.back')
|
|
17
17
|
|
|
18
18
|
let _mode = null
|
|
@@ -61,6 +61,7 @@ function Back(fixtureName, options, nockedFn) {
|
|
|
61
61
|
)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// TODO-12.x: Replace with `typeof fixtureName === 'string'`.
|
|
64
65
|
if (!_.isString(fixtureName)) {
|
|
65
66
|
throw new Error('Parameter fixtureName must be a string')
|
|
66
67
|
}
|
|
@@ -70,7 +71,7 @@ function Back(fixtureName, options, nockedFn) {
|
|
|
70
71
|
} else if (arguments.length === 2) {
|
|
71
72
|
// If 2nd parameter is a function then `options` has been omitted
|
|
72
73
|
// otherwise `options` haven't been omitted but `nockedFn` was.
|
|
73
|
-
if (
|
|
74
|
+
if (typeof options === 'function') {
|
|
74
75
|
nockedFn = options
|
|
75
76
|
options = {}
|
|
76
77
|
}
|
|
@@ -88,7 +89,7 @@ function Back(fixtureName, options, nockedFn) {
|
|
|
88
89
|
debug('context:', context)
|
|
89
90
|
|
|
90
91
|
// If nockedFn is a function then invoke it, otherwise return a promise resolving to nockDone.
|
|
91
|
-
if (
|
|
92
|
+
if (typeof nockedFn === 'function') {
|
|
92
93
|
nockedFn.call(context, nockDone)
|
|
93
94
|
} else {
|
|
94
95
|
return Promise.resolve({ nockDone, context })
|
|
@@ -244,16 +245,19 @@ function fixtureExists(fixture) {
|
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
function assertScopes(scopes, fixture) {
|
|
247
|
-
scopes
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
const pending = scopes
|
|
249
|
+
.filter(scope => !scope.isDone())
|
|
250
|
+
.map(scope => scope.pendingMocks())
|
|
251
|
+
|
|
252
|
+
if (pending.length) {
|
|
253
|
+
assert.fail(
|
|
250
254
|
format(
|
|
251
255
|
'%j was not used, consider removing %s to rerecord fixture',
|
|
252
|
-
|
|
256
|
+
[].concat(...pending),
|
|
253
257
|
fixture
|
|
254
258
|
)
|
|
255
259
|
)
|
|
256
|
-
}
|
|
260
|
+
}
|
|
257
261
|
}
|
|
258
262
|
|
|
259
263
|
const Modes = {
|
package/lib/common.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const _ = require('lodash')
|
|
4
4
|
const debug = require('debug')('nock.common')
|
|
5
5
|
const url = require('url')
|
|
6
|
+
const timers = require('timers')
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Normalizes the request options so that it always has `host` property.
|
|
@@ -37,23 +38,16 @@ function normalizeRequestOptions(options) {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
* Returns
|
|
41
|
+
* Returns true if the data contained in buffer can be reconstructed
|
|
41
42
|
* from its utf8 representation.
|
|
42
43
|
*
|
|
43
|
-
* TODO: Reverse the semantics of this method and refactor calling code
|
|
44
|
-
* accordingly. We've inadvertently gotten it flipped.
|
|
45
|
-
*
|
|
46
44
|
* @param {Object} buffer - a Buffer object
|
|
47
45
|
* @returns {boolean}
|
|
48
46
|
*/
|
|
49
47
|
function isUtf8Representable(buffer) {
|
|
50
|
-
if (!Buffer.isBuffer(buffer)) {
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
53
|
-
|
|
54
48
|
const utfEncodedBuffer = buffer.toString('utf8')
|
|
55
49
|
const reconstructedBuffer = Buffer.from(utfEncodedBuffer, 'utf8')
|
|
56
|
-
return
|
|
50
|
+
return reconstructedBuffer.equals(buffer)
|
|
57
51
|
}
|
|
58
52
|
|
|
59
53
|
// Array where all information about all the overridden requests are held.
|
|
@@ -176,6 +170,7 @@ function stringifyRequest(options, body) {
|
|
|
176
170
|
|
|
177
171
|
function isContentEncoded(headers) {
|
|
178
172
|
const contentEncoding = headers['content-encoding']
|
|
173
|
+
// TODO-12.x: Replace with `typeof contentEncoding === 'string'`.
|
|
179
174
|
return _.isString(contentEncoding) && contentEncoding !== ''
|
|
180
175
|
}
|
|
181
176
|
|
|
@@ -365,6 +360,7 @@ function deleteHeadersField(headers, fieldNameToDelete) {
|
|
|
365
360
|
throw Error('headers must be an object')
|
|
366
361
|
}
|
|
367
362
|
|
|
363
|
+
// TODO-12.x: Replace with `typeof fieldNameToDelete !== 'string'`.
|
|
368
364
|
if (!_.isString(fieldNameToDelete)) {
|
|
369
365
|
throw Error('field name must be a string')
|
|
370
366
|
}
|
|
@@ -446,6 +442,7 @@ function formatQueryValue(key, value, stringFormattingFn) {
|
|
|
446
442
|
case value === undefined:
|
|
447
443
|
value = ''
|
|
448
444
|
break
|
|
445
|
+
// TODO-12.x: Replace with `typeof value === 'string'`.
|
|
449
446
|
case _.isString(value):
|
|
450
447
|
if (stringFormattingFn) {
|
|
451
448
|
value = stringFormattingFn(value)
|
|
@@ -459,7 +456,7 @@ function formatQueryValue(key, value, stringFormattingFn) {
|
|
|
459
456
|
})
|
|
460
457
|
break
|
|
461
458
|
}
|
|
462
|
-
case
|
|
459
|
+
case typeof value === 'object': {
|
|
463
460
|
value = Object.entries(value).reduce(function(acc, [subKey, subVal]) {
|
|
464
461
|
const subPair = formatQueryValue(subKey, subVal, stringFormattingFn)
|
|
465
462
|
acc[subPair[0]] = subPair[1]
|
|
@@ -479,7 +476,7 @@ function isStream(obj) {
|
|
|
479
476
|
obj &&
|
|
480
477
|
typeof obj !== 'string' &&
|
|
481
478
|
!Buffer.isBuffer(obj) &&
|
|
482
|
-
|
|
479
|
+
typeof obj.setEncoding === 'function'
|
|
483
480
|
)
|
|
484
481
|
}
|
|
485
482
|
|
|
@@ -593,6 +590,32 @@ function deepEqual(expected, actual) {
|
|
|
593
590
|
return expected === actual
|
|
594
591
|
}
|
|
595
592
|
|
|
593
|
+
const timeouts = []
|
|
594
|
+
const intervals = []
|
|
595
|
+
const immediates = []
|
|
596
|
+
|
|
597
|
+
const wrapTimer = (timer, ids) => (...args) => {
|
|
598
|
+
const id = timer(...args)
|
|
599
|
+
ids.push(id)
|
|
600
|
+
return id
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const setTimeout = wrapTimer(timers.setTimeout, timeouts)
|
|
604
|
+
const setInterval = wrapTimer(timers.setInterval, intervals)
|
|
605
|
+
const setImmediate = wrapTimer(timers.setImmediate, immediates)
|
|
606
|
+
|
|
607
|
+
function clearTimer(clear, ids) {
|
|
608
|
+
while (ids.length) {
|
|
609
|
+
clear(ids.shift())
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function removeAllTimers() {
|
|
614
|
+
clearTimer(clearTimeout, timeouts)
|
|
615
|
+
clearTimer(clearInterval, intervals)
|
|
616
|
+
clearTimer(clearImmediate, immediates)
|
|
617
|
+
}
|
|
618
|
+
|
|
596
619
|
exports.normalizeClientRequestArgs = normalizeClientRequestArgs
|
|
597
620
|
exports.normalizeRequestOptions = normalizeRequestOptions
|
|
598
621
|
exports.normalizeOrigin = normalizeOrigin
|
|
@@ -615,3 +638,7 @@ exports.matchStringOrRegexp = matchStringOrRegexp
|
|
|
615
638
|
exports.formatQueryValue = formatQueryValue
|
|
616
639
|
exports.isStream = isStream
|
|
617
640
|
exports.dataEqual = dataEqual
|
|
641
|
+
exports.setTimeout = setTimeout
|
|
642
|
+
exports.setInterval = setInterval
|
|
643
|
+
exports.setImmediate = setImmediate
|
|
644
|
+
exports.removeAllTimers = removeAllTimers
|
package/lib/delayed_body.js
CHANGED
|
@@ -34,7 +34,7 @@ module.exports = class DelayedBody extends Transform {
|
|
|
34
34
|
|
|
35
35
|
// TODO: This would be more readable if the stream case were moved into
|
|
36
36
|
// the `if` statement above.
|
|
37
|
-
setTimeout(function() {
|
|
37
|
+
common.setTimeout(function() {
|
|
38
38
|
if (common.isStream(body) && !ended) {
|
|
39
39
|
body.once('end', function() {
|
|
40
40
|
self.end(data)
|
package/lib/intercept.js
CHANGED
|
@@ -11,7 +11,6 @@ const http = require('http')
|
|
|
11
11
|
const _ = require('lodash')
|
|
12
12
|
const debug = require('debug')('nock.intercept')
|
|
13
13
|
const globalEmitter = require('./global_emitter')
|
|
14
|
-
const timers = require('timers')
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* @name NetConnectNotAllowedError
|
|
@@ -53,9 +52,10 @@ let allowNetConnect
|
|
|
53
52
|
* nock.enableNetConnect(/(google|amazon)/);
|
|
54
53
|
*/
|
|
55
54
|
function enableNetConnect(matcher) {
|
|
55
|
+
// TODO-12.x: Replace with `typeof matcher === 'string'`.
|
|
56
56
|
if (_.isString(matcher)) {
|
|
57
57
|
allowNetConnect = new RegExp(matcher)
|
|
58
|
-
} else if (
|
|
58
|
+
} else if (matcher instanceof RegExp) {
|
|
59
59
|
allowNetConnect = matcher
|
|
60
60
|
} else {
|
|
61
61
|
allowNetConnect = /.*/
|
|
@@ -160,7 +160,9 @@ function interceptorsFor(options) {
|
|
|
160
160
|
|
|
161
161
|
// Keep the filtered scope (its key) to signal the rest of the module
|
|
162
162
|
// that this wasn't an exact but filtered match.
|
|
163
|
-
|
|
163
|
+
interceptors.forEach(ic => {
|
|
164
|
+
ic.__nock_filteredScope = ic.__nock_scopeKey
|
|
165
|
+
})
|
|
164
166
|
return interceptors
|
|
165
167
|
}
|
|
166
168
|
}
|
|
@@ -171,7 +173,7 @@ function interceptorsFor(options) {
|
|
|
171
173
|
return [
|
|
172
174
|
{
|
|
173
175
|
options: { allowUnmocked: true },
|
|
174
|
-
|
|
176
|
+
matchOrigin() {
|
|
175
177
|
return false
|
|
176
178
|
},
|
|
177
179
|
},
|
|
@@ -293,7 +295,7 @@ function overrideClientRequest() {
|
|
|
293
295
|
if (isOff() || isEnabledForNetConnect(options)) {
|
|
294
296
|
originalClientRequest.apply(this, arguments)
|
|
295
297
|
} else {
|
|
296
|
-
|
|
298
|
+
common.setImmediate(
|
|
297
299
|
function() {
|
|
298
300
|
const error = new NetConnectNotAllowedError(
|
|
299
301
|
options.host,
|
|
@@ -390,13 +392,12 @@ function activate() {
|
|
|
390
392
|
const interceptors = interceptorsFor(options)
|
|
391
393
|
|
|
392
394
|
if (isOn() && interceptors) {
|
|
393
|
-
const matches =
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
})
|
|
395
|
+
const matches = interceptors.some(interceptor =>
|
|
396
|
+
interceptor.matchOrigin(options)
|
|
397
|
+
)
|
|
398
|
+
const allowUnmocked = interceptors.some(
|
|
399
|
+
interceptor => interceptor.options.allowUnmocked
|
|
400
|
+
)
|
|
400
401
|
|
|
401
402
|
if (!matches && allowUnmocked) {
|
|
402
403
|
let req
|
|
@@ -444,4 +445,5 @@ module.exports = {
|
|
|
444
445
|
disableNetConnect,
|
|
445
446
|
overrideClientRequest,
|
|
446
447
|
restoreOverriddenClientRequest,
|
|
448
|
+
abortPendingRequests: common.removeAllTimers,
|
|
447
449
|
}
|
|
@@ -8,7 +8,6 @@ const {
|
|
|
8
8
|
} = require('http')
|
|
9
9
|
const { request: originalHttpsRequest } = require('https')
|
|
10
10
|
const propagate = require('propagate')
|
|
11
|
-
const timers = require('timers')
|
|
12
11
|
const common = require('./common')
|
|
13
12
|
const globalEmitter = require('./global_emitter')
|
|
14
13
|
const Socket = require('./socket')
|
|
@@ -31,7 +30,7 @@ class InterceptedRequestRouter {
|
|
|
31
30
|
}
|
|
32
31
|
this.interceptors = interceptors
|
|
33
32
|
|
|
34
|
-
this.socket = new Socket(
|
|
33
|
+
this.socket = new Socket(options)
|
|
35
34
|
this.response = new IncomingMessage(this.socket)
|
|
36
35
|
this.playbackStarted = false
|
|
37
36
|
this.requestBodyBuffers = []
|
|
@@ -74,7 +73,7 @@ class InterceptedRequestRouter {
|
|
|
74
73
|
|
|
75
74
|
// https://github.com/nock/nock/issues/256
|
|
76
75
|
if (options.headers.expect === '100-continue') {
|
|
77
|
-
|
|
76
|
+
common.setImmediate(() => {
|
|
78
77
|
debug('continue')
|
|
79
78
|
req.emit('continue')
|
|
80
79
|
})
|
|
@@ -114,6 +113,10 @@ class InterceptedRequestRouter {
|
|
|
114
113
|
}
|
|
115
114
|
this.requestBodyBuffers.push(buffer)
|
|
116
115
|
}
|
|
116
|
+
// can't use instanceof Function because some test runners
|
|
117
|
+
// run tests in vm.runInNewContext where Function is not same
|
|
118
|
+
// as that in the current context
|
|
119
|
+
// https://github.com/nock/nock/pull/1754#issuecomment-571531407
|
|
117
120
|
if (typeof callback === 'function') {
|
|
118
121
|
callback()
|
|
119
122
|
}
|
|
@@ -121,7 +124,7 @@ class InterceptedRequestRouter {
|
|
|
121
124
|
this.emitError(new Error('Request aborted'))
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
|
|
127
|
+
common.setImmediate(function() {
|
|
125
128
|
req.emit('drain')
|
|
126
129
|
})
|
|
127
130
|
|
|
@@ -232,8 +235,9 @@ class InterceptedRequestRouter {
|
|
|
232
235
|
// Re-update `options` with the current value of `req.path` because badly
|
|
233
236
|
// behaving agents like superagent like to change `req.path` mid-flight.
|
|
234
237
|
path: req.path,
|
|
235
|
-
//
|
|
238
|
+
// Similarly, node-http-proxy will modify headers in flight, so we have
|
|
236
239
|
// to put the headers back into options.
|
|
240
|
+
// https://github.com/nock/nock/pull/1484
|
|
237
241
|
headers: req.getHeaders(),
|
|
238
242
|
// Fixes https://github.com/nock/nock/issues/976
|
|
239
243
|
protocol: `${options.proto}:`,
|
|
@@ -246,11 +250,11 @@ class InterceptedRequestRouter {
|
|
|
246
250
|
const requestBodyBuffer = Buffer.concat(this.requestBodyBuffers)
|
|
247
251
|
// When request body is a binary buffer we internally use in its hexadecimal
|
|
248
252
|
// representation.
|
|
249
|
-
const
|
|
253
|
+
const requestBodyIsUtf8Representable = common.isUtf8Representable(
|
|
250
254
|
requestBodyBuffer
|
|
251
255
|
)
|
|
252
256
|
const requestBodyString = requestBodyBuffer.toString(
|
|
253
|
-
|
|
257
|
+
requestBodyIsUtf8Representable ? 'utf8' : 'hex'
|
|
254
258
|
)
|
|
255
259
|
|
|
256
260
|
const matchedInterceptor = interceptors.find(i =>
|
|
@@ -265,7 +269,7 @@ class InterceptedRequestRouter {
|
|
|
265
269
|
socket,
|
|
266
270
|
options,
|
|
267
271
|
requestBodyString,
|
|
268
|
-
|
|
272
|
+
requestBodyIsUtf8Representable,
|
|
269
273
|
response,
|
|
270
274
|
interceptor: matchedInterceptor,
|
|
271
275
|
})
|
package/lib/interceptor.js
CHANGED
|
@@ -110,7 +110,7 @@ module.exports = class Interceptor {
|
|
|
110
110
|
|
|
111
111
|
reply(statusCode, body, rawHeaders) {
|
|
112
112
|
// support the format of only passing in a callback
|
|
113
|
-
if (
|
|
113
|
+
if (typeof statusCode === 'function') {
|
|
114
114
|
if (arguments.length > 1) {
|
|
115
115
|
// It's not very Javascript-y to throw an error for extra args to a function, but because
|
|
116
116
|
// of legacy behavior, this error was added to reduce confusion for those migrating.
|
|
@@ -126,7 +126,7 @@ module.exports = class Interceptor {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
this.statusCode = statusCode || 200
|
|
129
|
-
if (
|
|
129
|
+
if (typeof body === 'function') {
|
|
130
130
|
this.replyFunction = body
|
|
131
131
|
body = null
|
|
132
132
|
}
|
|
@@ -200,6 +200,8 @@ module.exports = class Interceptor {
|
|
|
200
200
|
const reqHeader = this.reqheaders[key]
|
|
201
201
|
let header = options.headers[key]
|
|
202
202
|
|
|
203
|
+
// https://github.com/nock/nock/issues/399
|
|
204
|
+
// https://github.com/nock/nock/issues/822
|
|
203
205
|
if (header && typeof header !== 'string' && header.toString) {
|
|
204
206
|
header = header.toString()
|
|
205
207
|
}
|
|
@@ -213,7 +215,7 @@ module.exports = class Interceptor {
|
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
if (reqHeader !== undefined && header !== undefined) {
|
|
216
|
-
if (
|
|
218
|
+
if (typeof reqHeader === 'function') {
|
|
217
219
|
return reqHeader(header)
|
|
218
220
|
} else if (common.matchStringOrRegexp(header, reqHeader)) {
|
|
219
221
|
return true
|
|
@@ -352,9 +354,9 @@ module.exports = class Interceptor {
|
|
|
352
354
|
* Return true when the interceptor's method, protocol, host, port, and path
|
|
353
355
|
* match the provided options.
|
|
354
356
|
*/
|
|
355
|
-
|
|
356
|
-
const isRegex =
|
|
357
|
-
const isRegexBasePath =
|
|
357
|
+
matchOrigin(options) {
|
|
358
|
+
const isRegex = this.path instanceof RegExp
|
|
359
|
+
const isRegexBasePath = this.scope.basePath instanceof RegExp
|
|
358
360
|
|
|
359
361
|
const method = (options.method || 'GET').toUpperCase()
|
|
360
362
|
let { path } = options
|
|
@@ -395,7 +397,7 @@ module.exports = class Interceptor {
|
|
|
395
397
|
debug('Interceptor queries: %j', this.queries)
|
|
396
398
|
debug(' Request queries: %j', reqQueries)
|
|
397
399
|
|
|
398
|
-
if (
|
|
400
|
+
if (typeof this.queries === 'function') {
|
|
399
401
|
return this.queries(reqQueries)
|
|
400
402
|
}
|
|
401
403
|
|
|
@@ -456,7 +458,7 @@ module.exports = class Interceptor {
|
|
|
456
458
|
return this
|
|
457
459
|
}
|
|
458
460
|
|
|
459
|
-
if (
|
|
461
|
+
if (typeof queries === 'function') {
|
|
460
462
|
this.queries = queries
|
|
461
463
|
return this
|
|
462
464
|
}
|
|
@@ -553,7 +555,7 @@ module.exports = class Interceptor {
|
|
|
553
555
|
if (_.isNumber(opts)) {
|
|
554
556
|
headDelay = opts
|
|
555
557
|
bodyDelay = 0
|
|
556
|
-
} else if (
|
|
558
|
+
} else if (typeof opts === 'object') {
|
|
557
559
|
headDelay = opts.head || 0
|
|
558
560
|
bodyDelay = opts.body || 0
|
|
559
561
|
} else {
|
package/lib/match_body.js
CHANGED
|
@@ -11,11 +11,8 @@ module.exports = function matchBody(options, spec, body) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
if (Buffer.isBuffer(spec)) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} else {
|
|
17
|
-
spec = spec.toString('utf8')
|
|
18
|
-
}
|
|
14
|
+
const encoding = common.isUtf8Representable(spec) ? 'utf8' : 'hex'
|
|
15
|
+
spec = spec.toString(encoding)
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
const contentType = (
|
|
@@ -59,12 +56,7 @@ module.exports = function matchBody(options, spec, body) {
|
|
|
59
56
|
// Because the nature of URL encoding, all the values in the body have been cast to strings.
|
|
60
57
|
// dataEqual does strict checking so we we have to cast the non-regexp values in the spec too.
|
|
61
58
|
if (isUrlencoded) {
|
|
62
|
-
spec = mapValuesDeep(spec,
|
|
63
|
-
if (_.isRegExp(val)) {
|
|
64
|
-
return val
|
|
65
|
-
}
|
|
66
|
-
return `${val}`
|
|
67
|
-
})
|
|
59
|
+
spec = mapValuesDeep(spec, val => (val instanceof RegExp ? val : `${val}`))
|
|
68
60
|
}
|
|
69
61
|
|
|
70
62
|
return common.dataEqual(spec, body)
|
|
@@ -76,14 +68,10 @@ module.exports = function matchBody(options, spec, body) {
|
|
|
76
68
|
*/
|
|
77
69
|
function mapValuesDeep(obj, cb) {
|
|
78
70
|
if (Array.isArray(obj)) {
|
|
79
|
-
return obj.map(
|
|
80
|
-
return mapValuesDeep(v, cb)
|
|
81
|
-
})
|
|
71
|
+
return obj.map(v => mapValuesDeep(v, cb))
|
|
82
72
|
}
|
|
83
73
|
if (_.isPlainObject(obj)) {
|
|
84
|
-
return _.mapValues(obj,
|
|
85
|
-
return mapValuesDeep(v, cb)
|
|
86
|
-
})
|
|
74
|
+
return _.mapValues(obj, v => mapValuesDeep(v, cb))
|
|
87
75
|
}
|
|
88
76
|
return cb(obj)
|
|
89
77
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const util = require('util')
|
|
4
|
-
const timers = require('timers')
|
|
5
4
|
const zlib = require('zlib')
|
|
6
5
|
const debug = require('debug')('nock.playback_interceptor')
|
|
7
|
-
const _ = require('lodash')
|
|
8
6
|
const common = require('./common')
|
|
9
7
|
const DelayedBody = require('./delayed_body')
|
|
10
8
|
|
|
@@ -81,7 +79,7 @@ function playbackInterceptor({
|
|
|
81
79
|
socket,
|
|
82
80
|
options,
|
|
83
81
|
requestBodyString,
|
|
84
|
-
|
|
82
|
+
requestBodyIsUtf8Representable,
|
|
85
83
|
response,
|
|
86
84
|
interceptor,
|
|
87
85
|
}) {
|
|
@@ -101,12 +99,12 @@ function playbackInterceptor({
|
|
|
101
99
|
interceptor.markConsumed()
|
|
102
100
|
|
|
103
101
|
let error
|
|
104
|
-
if (
|
|
102
|
+
if (typeof interceptor.errorMessage === 'object') {
|
|
105
103
|
error = interceptor.errorMessage
|
|
106
104
|
} else {
|
|
107
105
|
error = new Error(interceptor.errorMessage)
|
|
108
106
|
}
|
|
109
|
-
|
|
107
|
+
common.setTimeout(() => emitError(error), interceptor.getTotalDelay())
|
|
110
108
|
return
|
|
111
109
|
}
|
|
112
110
|
|
|
@@ -179,10 +177,10 @@ function playbackInterceptor({
|
|
|
179
177
|
// will eventually be JSON stringified.
|
|
180
178
|
let responseBody = interceptor.body
|
|
181
179
|
|
|
182
|
-
// If the request was
|
|
183
|
-
//
|
|
184
|
-
// what the client will expect.
|
|
185
|
-
if (
|
|
180
|
+
// If the request was not UTF8-representable then we assume that the
|
|
181
|
+
// response won't be either. In that case we send the response as a Buffer
|
|
182
|
+
// object as that's what the client will expect.
|
|
183
|
+
if (!requestBodyIsUtf8Representable && typeof responseBody === 'string') {
|
|
186
184
|
// Try to create the buffer from the interceptor's body response as hex.
|
|
187
185
|
responseBody = Buffer.from(responseBody, 'hex')
|
|
188
186
|
|
|
@@ -311,13 +309,13 @@ function playbackInterceptor({
|
|
|
311
309
|
}
|
|
312
310
|
|
|
313
311
|
// Stream the response chunks one at a time.
|
|
314
|
-
|
|
312
|
+
common.setImmediate(function emitChunk() {
|
|
315
313
|
const chunk = responseBuffers.shift()
|
|
316
314
|
|
|
317
315
|
if (chunk) {
|
|
318
316
|
debug('emitting response chunk')
|
|
319
317
|
response.push(chunk)
|
|
320
|
-
|
|
318
|
+
common.setImmediate(emitChunk)
|
|
321
319
|
} else {
|
|
322
320
|
debug('ending response stream')
|
|
323
321
|
response.push(null)
|
|
@@ -338,7 +336,7 @@ function playbackInterceptor({
|
|
|
338
336
|
interceptor.delayConnectionInMs > 0
|
|
339
337
|
) {
|
|
340
338
|
socket.applyDelay(interceptor.delayConnectionInMs)
|
|
341
|
-
setTimeout(respond, interceptor.delayConnectionInMs)
|
|
339
|
+
common.setTimeout(respond, interceptor.delayConnectionInMs)
|
|
342
340
|
} else {
|
|
343
341
|
respond()
|
|
344
342
|
}
|
package/lib/recorder.js
CHANGED
|
@@ -21,9 +21,9 @@ function getMethod(options) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function getBodyFromChunks(chunks, headers) {
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
24
|
+
// If we have headers and there is content-encoding it means that the body
|
|
25
|
+
// shouldn't be merged but instead persisted as an array of hex strings so
|
|
26
|
+
// that the response chunks can be mocked one by one.
|
|
27
27
|
if (headers && common.isContentEncoded(headers)) {
|
|
28
28
|
return {
|
|
29
29
|
body: chunks.map(chunk => chunk.toString('hex')),
|
|
@@ -32,30 +32,29 @@ function getBodyFromChunks(chunks, headers) {
|
|
|
32
32
|
|
|
33
33
|
const mergedBuffer = Buffer.concat(chunks)
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (isBinary) {
|
|
42
|
-
return {
|
|
43
|
-
body: mergedBuffer.toString('hex'),
|
|
44
|
-
isBinary: true,
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
35
|
+
// The merged buffer can be one of three things:
|
|
36
|
+
// 1. A UTF-8-representable string buffer which represents a JSON object.
|
|
37
|
+
// 2. A UTF-8-representable buffer which doesn't represent a JSON object.
|
|
38
|
+
// 3. A non-UTF-8-representable buffer which then has to be recorded as a hex string.
|
|
39
|
+
const isUtf8Representable = common.isUtf8Representable(mergedBuffer)
|
|
40
|
+
if (isUtf8Representable) {
|
|
47
41
|
const maybeStringifiedJson = mergedBuffer.toString('utf8')
|
|
48
42
|
try {
|
|
49
43
|
return {
|
|
44
|
+
isUtf8Representable,
|
|
50
45
|
body: JSON.parse(maybeStringifiedJson),
|
|
51
|
-
isBinary: false,
|
|
52
46
|
}
|
|
53
47
|
} catch (err) {
|
|
54
48
|
return {
|
|
49
|
+
isUtf8Representable,
|
|
55
50
|
body: maybeStringifiedJson,
|
|
56
|
-
isBinary: false,
|
|
57
51
|
}
|
|
58
52
|
}
|
|
53
|
+
} else {
|
|
54
|
+
return {
|
|
55
|
+
isUtf8Representable,
|
|
56
|
+
body: mergedBuffer.toString('hex'),
|
|
57
|
+
}
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
|
|
@@ -67,28 +66,26 @@ function generateRequestAndResponseObject({
|
|
|
67
66
|
dataChunks,
|
|
68
67
|
reqheaders,
|
|
69
68
|
}) {
|
|
70
|
-
const
|
|
69
|
+
const { body, isUtf8Representable } = getBodyFromChunks(
|
|
70
|
+
dataChunks,
|
|
71
|
+
res.headers
|
|
72
|
+
)
|
|
71
73
|
options.path = req.path
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
return {
|
|
74
76
|
scope: getScope(options),
|
|
75
77
|
method: getMethod(options),
|
|
76
78
|
path: options.path,
|
|
79
|
+
// Is it deliberate that `getBodyFromChunks()` is called a second time?
|
|
77
80
|
body: getBodyFromChunks(bodyChunks).body,
|
|
78
81
|
status: res.statusCode,
|
|
79
|
-
response:
|
|
82
|
+
response: body,
|
|
80
83
|
rawHeaders: res.rawHeaders,
|
|
84
|
+
reqheaders: reqheaders || undefined,
|
|
85
|
+
// When content-encoding is enabled, isUtf8Representable is `undefined`,
|
|
86
|
+
// so we explicitly check for `false`.
|
|
87
|
+
responseIsBinary: isUtf8Representable === false,
|
|
81
88
|
}
|
|
82
|
-
|
|
83
|
-
if (reqheaders) {
|
|
84
|
-
nockDef.reqheaders = reqheaders
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (response.isBinary) {
|
|
88
|
-
nockDef.responseIsBinary = true
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return nockDef
|
|
92
89
|
}
|
|
93
90
|
|
|
94
91
|
function generateRequestAndResponse({
|
package/lib/scope.js
CHANGED
|
@@ -347,11 +347,12 @@ function define(nockDefs) {
|
|
|
347
347
|
body = undefined
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
//
|
|
351
|
-
//
|
|
350
|
+
// Response is not always JSON as it could be a string or binary data or
|
|
351
|
+
// even an array of binary buffers (e.g. when content is encoded).
|
|
352
352
|
let response
|
|
353
353
|
if (!nockDef.response) {
|
|
354
354
|
response = ''
|
|
355
|
+
// TODO: Rename `responseIsBinary` to `reponseIsUtf8Representable`.
|
|
355
356
|
} else if (nockDef.responseIsBinary) {
|
|
356
357
|
response = Buffer.from(nockDef.response, 'hex')
|
|
357
358
|
} else {
|
package/lib/socket.js
CHANGED
|
@@ -12,8 +12,10 @@ module.exports = class Socket extends EventEmitter {
|
|
|
12
12
|
this.authorized = true
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
this.bufferSize = 0
|
|
15
16
|
this.writable = true
|
|
16
17
|
this.readable = true
|
|
18
|
+
this.pending = false
|
|
17
19
|
this.destroyed = false
|
|
18
20
|
this.connecting = false
|
|
19
21
|
|
|
@@ -23,13 +25,27 @@ module.exports = class Socket extends EventEmitter {
|
|
|
23
25
|
this.totalDelayMs = 0
|
|
24
26
|
// Maximum allowed delay. Null means unlimited.
|
|
25
27
|
this.timeoutMs = null
|
|
28
|
+
|
|
29
|
+
const ipv6 = options.family === 6
|
|
30
|
+
this.remoteFamily = ipv6 ? 'IPv6' : 'IPv4'
|
|
31
|
+
this.localAddress = this.remoteAddress = ipv6 ? '::1' : '127.0.0.1'
|
|
32
|
+
this.localPort = this.remotePort = parseInt(options.port)
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
setNoDelay() {}
|
|
29
36
|
setKeepAlive() {}
|
|
30
37
|
resume() {}
|
|
38
|
+
ref() {}
|
|
31
39
|
unref() {}
|
|
32
40
|
|
|
41
|
+
address() {
|
|
42
|
+
return {
|
|
43
|
+
port: this.remotePort,
|
|
44
|
+
family: this.remoteFamily,
|
|
45
|
+
address: this.remoteAddress,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
setTimeout(timeoutMs, fn) {
|
|
34
50
|
this.timeoutMs = timeoutMs
|
|
35
51
|
if (fn) {
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"testing",
|
|
8
8
|
"isolation"
|
|
9
9
|
],
|
|
10
|
-
"version": "11.
|
|
10
|
+
"version": "11.7.2",
|
|
11
11
|
"author": "Pedro Teixeira <pedro.teixeira@gmail.com>",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"main": "./index.js",
|
|
23
23
|
"types": "types",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"chai": "^4.1.2",
|
|
26
25
|
"debug": "^4.1.0",
|
|
27
26
|
"json-stringify-safe": "^5.0.1",
|
|
28
27
|
"lodash": "^4.17.13",
|
|
@@ -31,28 +30,29 @@
|
|
|
31
30
|
},
|
|
32
31
|
"devDependencies": {
|
|
33
32
|
"assert-rejects": "^1.0.0",
|
|
34
|
-
"
|
|
33
|
+
"chai": "^4.1.2",
|
|
34
|
+
"dirty-chai": "^2.0.1",
|
|
35
|
+
"dtslint": "^2.0.2",
|
|
35
36
|
"eslint": "^6.0.0",
|
|
36
37
|
"eslint-config-prettier": "^6.0.0",
|
|
37
38
|
"eslint-config-standard": "^14.0.0",
|
|
38
39
|
"eslint-plugin-import": "^2.16.0",
|
|
39
|
-
"eslint-plugin-
|
|
40
|
+
"eslint-plugin-mocha": "^6.2.0",
|
|
41
|
+
"eslint-plugin-node": "^11.0.0",
|
|
40
42
|
"eslint-plugin-promise": "^4.1.1",
|
|
41
43
|
"eslint-plugin-standard": "^4.0.0",
|
|
42
44
|
"got": "^9.6.0",
|
|
43
|
-
"hyperquest": "^2.1.3",
|
|
44
|
-
"isomorphic-fetch": "^2.2.0",
|
|
45
45
|
"lolex": "^5.0.0",
|
|
46
|
-
"
|
|
46
|
+
"mocha": "^6.2.2",
|
|
47
47
|
"npm-run-all": "^4.1.5",
|
|
48
|
-
"nyc": "^
|
|
49
|
-
"prettier": "1.
|
|
48
|
+
"nyc": "^15.0.0",
|
|
49
|
+
"prettier": "1.19.0",
|
|
50
50
|
"proxyquire": "^2.1.0",
|
|
51
51
|
"request": "^2.83.0",
|
|
52
|
-
"restify-clients": "^2.2.0",
|
|
53
52
|
"rimraf": "^3.0.0",
|
|
54
53
|
"semantic-release": "^16.0.0-beta.22",
|
|
55
|
-
"sinon": "^
|
|
54
|
+
"sinon": "^8.0.0",
|
|
55
|
+
"sinon-chai": "^3.3.0",
|
|
56
56
|
"superagent": "^5.0.2",
|
|
57
57
|
"tap": "^14.0.0"
|
|
58
58
|
},
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"test": "npm run -s unit",
|
|
63
63
|
"posttest": "npm run -s prettier:check",
|
|
64
64
|
"coverage": "tap --coverage-report=html && open coverage/lcov-report/index.html",
|
|
65
|
+
"mocha": "nyc mocha $(grep -lr '^\\s*it(' tests)",
|
|
65
66
|
"lint": "eslint \"**/*.js\"",
|
|
66
67
|
"prettier": "prettier --write \"**/*.@(js|json|md|ts|yml)\"",
|
|
67
68
|
"prettier:check": "prettier --check \"**/*.@(js|json|md|ts|yml)\"",
|
package/types/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ declare namespace nock {
|
|
|
26
26
|
function loadDefs(path: string): Definition[]
|
|
27
27
|
function define(defs: Definition[]): Scope[]
|
|
28
28
|
function restore(): void
|
|
29
|
+
function abortPendingRequests(): void
|
|
29
30
|
|
|
30
31
|
let back: Back
|
|
31
32
|
let emitter: NodeJS.EventEmitter
|
|
@@ -243,6 +244,7 @@ declare namespace nock {
|
|
|
243
244
|
type BackMode = 'wild' | 'dryrun' | 'record' | 'lockdown'
|
|
244
245
|
|
|
245
246
|
interface Back {
|
|
247
|
+
currentMode: BackMode
|
|
246
248
|
fixtures: string
|
|
247
249
|
setMode(mode: BackMode): void
|
|
248
250
|
|