nock 11.3.6 → 11.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/index.js +2 -0
- package/lib/common.js +33 -9
- package/lib/delayed_body.js +1 -1
- package/lib/intercept.js +41 -40
- package/lib/intercepted_request_router.js +10 -14
- package/lib/interceptor.js +10 -4
- package/lib/match_body.js +2 -5
- package/lib/playback_interceptor.js +9 -10
- package/lib/recorder.js +27 -30
- package/lib/scope.js +3 -2
- package/lib/socket.js +16 -0
- package/package.json +14 -6
- package/types/index.d.ts +2 -0
- package/.all-contributorsrc +0 -123
- package/.eslintignore +0 -3
- package/.eslintrc.yml +0 -34
- package/.github/FUNDING.yml +0 -1
- package/.github/ISSUE_TEMPLATE/01_bug_report.md +0 -26
- package/.github/ISSUE_TEMPLATE/02_feature_request.md +0 -20
- package/.github/ISSUE_TEMPLATE/03_support.md +0 -10
- package/.github/ISSUE_TEMPLATE/04_thanks.md +0 -20
- package/.github/lock.yml +0 -19
- package/.github/stale.yml +0 -25
- package/.github/toc.yml +0 -1
- package/.istanbul.yml +0 -5
- package/.prettierignore +0 -5
- package/.prettierrc.yml +0 -4
- package/.travis.yml +0 -47
- package/CODE_OF_CONDUCT.md +0 -46
- package/CONTRIBUTING.md +0 -164
- package/assets/reply_file_1.txt +0 -1
- package/assets/reply_file_2.txt.gz +0 -0
- package/examples/.eslintrc.yml +0 -3
- package/examples/_log.js +0 -12
- package/examples/delay-connection.js +0 -15
- package/examples/delay-response.js +0 -15
- package/examples/net-connect-default-no-mock.js +0 -18
- package/examples/net-connect-default-other-mock.js +0 -23
- package/examples/net-connect-disabled-different-host.js +0 -26
- package/examples/net-connect-mock-same-host-different-path.js +0 -24
- package/examples/socket-delay-abort.js +0 -19
- package/examples/socket-delay-no-abort.js +0 -15
- package/rfcs/rfc-001.md +0 -43
- package/types/tests.ts +0 -810
- package/types/tsconfig.json +0 -11
- package/types/tslint.json +0 -6
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')
|
|
@@ -35,6 +36,7 @@ Object.assign(module.exports, {
|
|
|
35
36
|
removeAll()
|
|
36
37
|
return module.exports
|
|
37
38
|
},
|
|
39
|
+
abortPendingRequests,
|
|
38
40
|
load,
|
|
39
41
|
loadDefs,
|
|
40
42
|
define,
|
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.
|
|
@@ -593,6 +587,32 @@ function deepEqual(expected, actual) {
|
|
|
593
587
|
return expected === actual
|
|
594
588
|
}
|
|
595
589
|
|
|
590
|
+
const timeouts = []
|
|
591
|
+
const intervals = []
|
|
592
|
+
const immediates = []
|
|
593
|
+
|
|
594
|
+
const wrapTimer = (timer, ids) => (...args) => {
|
|
595
|
+
const id = timer(...args)
|
|
596
|
+
ids.push(id)
|
|
597
|
+
return id
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const setTimeout = wrapTimer(timers.setTimeout, timeouts)
|
|
601
|
+
const setInterval = wrapTimer(timers.setInterval, intervals)
|
|
602
|
+
const setImmediate = wrapTimer(timers.setImmediate, immediates)
|
|
603
|
+
|
|
604
|
+
function clearTimer(clear, ids) {
|
|
605
|
+
while (ids.length) {
|
|
606
|
+
clear(ids.shift())
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function removeAllTimers() {
|
|
611
|
+
clearTimer(clearTimeout, timeouts)
|
|
612
|
+
clearTimer(clearInterval, intervals)
|
|
613
|
+
clearTimer(clearImmediate, immediates)
|
|
614
|
+
}
|
|
615
|
+
|
|
596
616
|
exports.normalizeClientRequestArgs = normalizeClientRequestArgs
|
|
597
617
|
exports.normalizeRequestOptions = normalizeRequestOptions
|
|
598
618
|
exports.normalizeOrigin = normalizeOrigin
|
|
@@ -615,3 +635,7 @@ exports.matchStringOrRegexp = matchStringOrRegexp
|
|
|
615
635
|
exports.formatQueryValue = formatQueryValue
|
|
616
636
|
exports.isStream = isStream
|
|
617
637
|
exports.dataEqual = dataEqual
|
|
638
|
+
exports.setTimeout = setTimeout
|
|
639
|
+
exports.setInterval = setInterval
|
|
640
|
+
exports.setImmediate = setImmediate
|
|
641
|
+
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
|
|
@@ -90,7 +89,7 @@ function isOff() {
|
|
|
90
89
|
|
|
91
90
|
function addInterceptor(key, interceptor, scope, scopeOptions, host) {
|
|
92
91
|
if (!(key in allInterceptors)) {
|
|
93
|
-
allInterceptors[key] = { key,
|
|
92
|
+
allInterceptors[key] = { key, interceptors: [] }
|
|
94
93
|
}
|
|
95
94
|
interceptor.__nock_scope = scope
|
|
96
95
|
|
|
@@ -103,7 +102,7 @@ function addInterceptor(key, interceptor, scope, scopeOptions, host) {
|
|
|
103
102
|
|
|
104
103
|
if (scopeOptions.allowUnmocked) allInterceptors[key].allowUnmocked = true
|
|
105
104
|
|
|
106
|
-
allInterceptors[key].
|
|
105
|
+
allInterceptors[key].interceptors.push(interceptor)
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
function remove(interceptor) {
|
|
@@ -113,7 +112,7 @@ function remove(interceptor) {
|
|
|
113
112
|
|
|
114
113
|
const { basePath } = interceptor
|
|
115
114
|
const interceptors =
|
|
116
|
-
(allInterceptors[basePath] && allInterceptors[basePath].
|
|
115
|
+
(allInterceptors[basePath] && allInterceptors[basePath].interceptors) || []
|
|
117
116
|
|
|
118
117
|
// TODO: There is a clearer way to write that we want to delete the first
|
|
119
118
|
// matching instance. I'm also not sure why we couldn't delete _all_
|
|
@@ -125,7 +124,7 @@ function remove(interceptor) {
|
|
|
125
124
|
|
|
126
125
|
function removeAll() {
|
|
127
126
|
Object.keys(allInterceptors).forEach(function(key) {
|
|
128
|
-
allInterceptors[key].
|
|
127
|
+
allInterceptors[key].interceptors.forEach(function(interceptor) {
|
|
129
128
|
interceptor.scope.keyedInterceptors = {}
|
|
130
129
|
})
|
|
131
130
|
})
|
|
@@ -146,32 +145,29 @@ function interceptorsFor(options) {
|
|
|
146
145
|
|
|
147
146
|
debug('filtering interceptors for basepath', basePath)
|
|
148
147
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
// First try to use filteringScope if any of the interceptors has it defined.
|
|
149
|
+
for (const { key, interceptors, allowUnmocked } of Object.values(
|
|
150
|
+
allInterceptors
|
|
151
|
+
)) {
|
|
152
|
+
for (const interceptor of interceptors) {
|
|
153
|
+
const { filteringScope } = interceptor.__nock_scopeOptions
|
|
154
154
|
|
|
155
|
-
//
|
|
156
|
-
//
|
|
155
|
+
// If scope filtering function is defined and returns a truthy value then
|
|
156
|
+
// we have to treat this as a match.
|
|
157
157
|
if (filteringScope && filteringScope(basePath)) {
|
|
158
158
|
debug('found matching scope interceptor')
|
|
159
159
|
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// Break out of _.each for scopes.
|
|
165
|
-
return false
|
|
160
|
+
// Keep the filtered scope (its key) to signal the rest of the module
|
|
161
|
+
// that this wasn't an exact but filtered match.
|
|
162
|
+
interceptor.__nock_filteredScope = interceptor.__nock_scopeKey
|
|
163
|
+
return interceptors
|
|
166
164
|
}
|
|
167
|
-
}
|
|
165
|
+
}
|
|
168
166
|
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (interceptor.scopes.length === 0 && interceptor.allowUnmocked) {
|
|
174
|
-
matchingInterceptor = [
|
|
167
|
+
if (common.matchStringOrRegexp(basePath, key)) {
|
|
168
|
+
if (allowUnmocked && interceptors.length === 0) {
|
|
169
|
+
debug('matched base path with allowUnmocked (no matching interceptors)')
|
|
170
|
+
return [
|
|
175
171
|
{
|
|
176
172
|
options: { allowUnmocked: true },
|
|
177
173
|
matchAddress() {
|
|
@@ -180,18 +176,17 @@ function interceptorsFor(options) {
|
|
|
180
176
|
},
|
|
181
177
|
]
|
|
182
178
|
} else {
|
|
183
|
-
|
|
179
|
+
debug(
|
|
180
|
+
`matched base path (${interceptors.length} interceptor${
|
|
181
|
+
interceptors.length > 1 ? 's' : ''
|
|
182
|
+
})`
|
|
183
|
+
)
|
|
184
|
+
return interceptors
|
|
184
185
|
}
|
|
185
|
-
// false to short circuit the .each
|
|
186
|
-
return false
|
|
187
186
|
}
|
|
187
|
+
}
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
// will break out of _.each for all interceptors.
|
|
191
|
-
return !matchingInterceptor
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
return matchingInterceptor
|
|
189
|
+
return undefined
|
|
195
190
|
}
|
|
196
191
|
|
|
197
192
|
function removeInterceptor(options) {
|
|
@@ -211,11 +206,14 @@ function removeInterceptor(options) {
|
|
|
211
206
|
key = `${method} ${baseUrl}${options.path || '/'}`
|
|
212
207
|
}
|
|
213
208
|
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
209
|
+
if (
|
|
210
|
+
allInterceptors[baseUrl] &&
|
|
211
|
+
allInterceptors[baseUrl].interceptors.length > 0
|
|
212
|
+
) {
|
|
213
|
+
for (let i = 0; i < allInterceptors[baseUrl].interceptors.length; i++) {
|
|
214
|
+
const interceptor = allInterceptors[baseUrl].interceptors[i]
|
|
217
215
|
if (interceptor._key === key) {
|
|
218
|
-
allInterceptors[baseUrl].
|
|
216
|
+
allInterceptors[baseUrl].interceptors.splice(i, 1)
|
|
219
217
|
interceptor.scope.remove(key, interceptor)
|
|
220
218
|
break
|
|
221
219
|
}
|
|
@@ -294,7 +292,7 @@ function overrideClientRequest() {
|
|
|
294
292
|
if (isOff() || isEnabledForNetConnect(options)) {
|
|
295
293
|
originalClientRequest.apply(this, arguments)
|
|
296
294
|
} else {
|
|
297
|
-
|
|
295
|
+
common.setImmediate(
|
|
298
296
|
function() {
|
|
299
297
|
const error = new NetConnectNotAllowedError(
|
|
300
298
|
options.host,
|
|
@@ -337,7 +335,9 @@ function isActive() {
|
|
|
337
335
|
}
|
|
338
336
|
|
|
339
337
|
function interceptorScopes() {
|
|
340
|
-
const nestedInterceptors = Object.values(allInterceptors).map(
|
|
338
|
+
const nestedInterceptors = Object.values(allInterceptors).map(
|
|
339
|
+
i => i.interceptors
|
|
340
|
+
)
|
|
341
341
|
return [].concat(...nestedInterceptors).map(i => i.scope)
|
|
342
342
|
}
|
|
343
343
|
|
|
@@ -443,4 +443,5 @@ module.exports = {
|
|
|
443
443
|
disableNetConnect,
|
|
444
444
|
overrideClientRequest,
|
|
445
445
|
restoreOverriddenClientRequest,
|
|
446
|
+
abortPendingRequests: common.removeAllTimers,
|
|
446
447
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const debug = require('debug')('nock.request_overrider')
|
|
4
|
-
const { EventEmitter } = require('events')
|
|
5
4
|
const {
|
|
6
5
|
IncomingMessage,
|
|
7
6
|
ClientRequest,
|
|
@@ -9,7 +8,6 @@ const {
|
|
|
9
8
|
} = require('http')
|
|
10
9
|
const { request: originalHttpsRequest } = require('https')
|
|
11
10
|
const propagate = require('propagate')
|
|
12
|
-
const timers = require('timers')
|
|
13
11
|
const common = require('./common')
|
|
14
12
|
const globalEmitter = require('./global_emitter')
|
|
15
13
|
const Socket = require('./socket')
|
|
@@ -32,8 +30,8 @@ class InterceptedRequestRouter {
|
|
|
32
30
|
}
|
|
33
31
|
this.interceptors = interceptors
|
|
34
32
|
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
33
|
+
this.socket = new Socket(options)
|
|
34
|
+
this.response = new IncomingMessage(this.socket)
|
|
37
35
|
this.playbackStarted = false
|
|
38
36
|
this.requestBodyBuffers = []
|
|
39
37
|
|
|
@@ -63,11 +61,8 @@ class InterceptedRequestRouter {
|
|
|
63
61
|
// ClientRequest.connection is an alias for ClientRequest.socket
|
|
64
62
|
// https://nodejs.org/api/http.html#http_request_socket
|
|
65
63
|
// https://github.com/nodejs/node/blob/b0f75818f39ed4e6bd80eb7c4010c1daf5823ef7/lib/_http_client.js#L640-L641
|
|
66
|
-
// IncomingMessage.connection & IncomingMessage.client are aliases for IncomingMessage.socket
|
|
67
|
-
// https://nodejs.org/api/http.html#http_response_socket
|
|
68
|
-
// https://github.com/nodejs/node/blob/b0f75818f39ed4e6bd80eb7c4010c1daf5823ef7/lib/_http_incoming.js#L44-L69
|
|
69
64
|
// The same Socket is shared between the request and response to mimic native behavior.
|
|
70
|
-
req.socket = req.connection =
|
|
65
|
+
req.socket = req.connection = socket
|
|
71
66
|
|
|
72
67
|
propagate(['error', 'timeout'], req.socket, req)
|
|
73
68
|
|
|
@@ -78,7 +73,7 @@ class InterceptedRequestRouter {
|
|
|
78
73
|
|
|
79
74
|
// https://github.com/nock/nock/issues/256
|
|
80
75
|
if (options.headers.expect === '100-continue') {
|
|
81
|
-
|
|
76
|
+
common.setImmediate(() => {
|
|
82
77
|
debug('continue')
|
|
83
78
|
req.emit('continue')
|
|
84
79
|
})
|
|
@@ -125,7 +120,7 @@ class InterceptedRequestRouter {
|
|
|
125
120
|
this.emitError(new Error('Request aborted'))
|
|
126
121
|
}
|
|
127
122
|
|
|
128
|
-
|
|
123
|
+
common.setImmediate(function() {
|
|
129
124
|
req.emit('drain')
|
|
130
125
|
})
|
|
131
126
|
|
|
@@ -236,8 +231,9 @@ class InterceptedRequestRouter {
|
|
|
236
231
|
// Re-update `options` with the current value of `req.path` because badly
|
|
237
232
|
// behaving agents like superagent like to change `req.path` mid-flight.
|
|
238
233
|
path: req.path,
|
|
239
|
-
//
|
|
234
|
+
// Similarly, node-http-proxy will modify headers in flight, so we have
|
|
240
235
|
// to put the headers back into options.
|
|
236
|
+
// https://github.com/nock/nock/pull/1484
|
|
241
237
|
headers: req.getHeaders(),
|
|
242
238
|
// Fixes https://github.com/nock/nock/issues/976
|
|
243
239
|
protocol: `${options.proto}:`,
|
|
@@ -250,11 +246,11 @@ class InterceptedRequestRouter {
|
|
|
250
246
|
const requestBodyBuffer = Buffer.concat(this.requestBodyBuffers)
|
|
251
247
|
// When request body is a binary buffer we internally use in its hexadecimal
|
|
252
248
|
// representation.
|
|
253
|
-
const
|
|
249
|
+
const requestBodyIsUtf8Representable = common.isUtf8Representable(
|
|
254
250
|
requestBodyBuffer
|
|
255
251
|
)
|
|
256
252
|
const requestBodyString = requestBodyBuffer.toString(
|
|
257
|
-
|
|
253
|
+
requestBodyIsUtf8Representable ? 'utf8' : 'hex'
|
|
258
254
|
)
|
|
259
255
|
|
|
260
256
|
const matchedInterceptor = interceptors.find(i =>
|
|
@@ -269,7 +265,7 @@ class InterceptedRequestRouter {
|
|
|
269
265
|
socket,
|
|
270
266
|
options,
|
|
271
267
|
requestBodyString,
|
|
272
|
-
|
|
268
|
+
requestBodyIsUtf8Representable,
|
|
273
269
|
response,
|
|
274
270
|
interceptor: matchedInterceptor,
|
|
275
271
|
})
|
package/lib/interceptor.js
CHANGED
|
@@ -31,10 +31,16 @@ module.exports = class Interceptor {
|
|
|
31
31
|
constructor(scope, uri, method, requestBody, interceptorOptions) {
|
|
32
32
|
const uriIsStr = typeof uri === 'string'
|
|
33
33
|
// Check for leading slash. Uri can be either a string or a regexp, but
|
|
34
|
-
//
|
|
35
|
-
|
|
34
|
+
// When enabled filteringScope ignores the passed URL entirely so we skip validation.
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
!scope.scopeOptions.filteringScope &&
|
|
38
|
+
uriIsStr &&
|
|
39
|
+
!uri.startsWith('/') &&
|
|
40
|
+
!uri.startsWith('*')
|
|
41
|
+
) {
|
|
36
42
|
throw Error(
|
|
37
|
-
|
|
43
|
+
`Non-wildcard URL path strings must begin with a slash (otherwise they won't match anything) (got: ${uri})`
|
|
38
44
|
)
|
|
39
45
|
}
|
|
40
46
|
|
|
@@ -224,7 +230,7 @@ module.exports = class Interceptor {
|
|
|
224
230
|
}
|
|
225
231
|
|
|
226
232
|
const method = (options.method || 'GET').toUpperCase()
|
|
227
|
-
let { path = '' } = options
|
|
233
|
+
let { path = '/' } = options
|
|
228
234
|
let matches
|
|
229
235
|
let matchKey
|
|
230
236
|
const { proto } = options
|
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 = (
|
|
@@ -1,7 +1,6 @@
|
|
|
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
6
|
const _ = require('lodash')
|
|
@@ -81,7 +80,7 @@ function playbackInterceptor({
|
|
|
81
80
|
socket,
|
|
82
81
|
options,
|
|
83
82
|
requestBodyString,
|
|
84
|
-
|
|
83
|
+
requestBodyIsUtf8Representable,
|
|
85
84
|
response,
|
|
86
85
|
interceptor,
|
|
87
86
|
}) {
|
|
@@ -106,7 +105,7 @@ function playbackInterceptor({
|
|
|
106
105
|
} else {
|
|
107
106
|
error = new Error(interceptor.errorMessage)
|
|
108
107
|
}
|
|
109
|
-
|
|
108
|
+
common.setTimeout(() => emitError(error), interceptor.getTotalDelay())
|
|
110
109
|
return
|
|
111
110
|
}
|
|
112
111
|
|
|
@@ -179,10 +178,10 @@ function playbackInterceptor({
|
|
|
179
178
|
// will eventually be JSON stringified.
|
|
180
179
|
let responseBody = interceptor.body
|
|
181
180
|
|
|
182
|
-
// If the request was
|
|
183
|
-
//
|
|
184
|
-
// what the client will expect.
|
|
185
|
-
if (
|
|
181
|
+
// If the request was not UTF8-representable then we assume that the
|
|
182
|
+
// response won't be either. In that case we send the response as a Buffer
|
|
183
|
+
// object as that's what the client will expect.
|
|
184
|
+
if (!requestBodyIsUtf8Representable && typeof responseBody === 'string') {
|
|
186
185
|
// Try to create the buffer from the interceptor's body response as hex.
|
|
187
186
|
responseBody = Buffer.from(responseBody, 'hex')
|
|
188
187
|
|
|
@@ -311,13 +310,13 @@ function playbackInterceptor({
|
|
|
311
310
|
}
|
|
312
311
|
|
|
313
312
|
// Stream the response chunks one at a time.
|
|
314
|
-
|
|
313
|
+
common.setImmediate(function emitChunk() {
|
|
315
314
|
const chunk = responseBuffers.shift()
|
|
316
315
|
|
|
317
316
|
if (chunk) {
|
|
318
317
|
debug('emitting response chunk')
|
|
319
318
|
response.push(chunk)
|
|
320
|
-
|
|
319
|
+
common.setImmediate(emitChunk)
|
|
321
320
|
} else {
|
|
322
321
|
debug('ending response stream')
|
|
323
322
|
response.push(null)
|
|
@@ -338,7 +337,7 @@ function playbackInterceptor({
|
|
|
338
337
|
interceptor.delayConnectionInMs > 0
|
|
339
338
|
) {
|
|
340
339
|
socket.applyDelay(interceptor.delayConnectionInMs)
|
|
341
|
-
setTimeout(respond, interceptor.delayConnectionInMs)
|
|
340
|
+
common.setTimeout(respond, interceptor.delayConnectionInMs)
|
|
342
341
|
} else {
|
|
343
342
|
respond()
|
|
344
343
|
}
|
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) {
|