Comparing version 13.0.0-beta.3 to 13.0.0-beta.4
@@ -284,2 +284,4 @@ 'use strict' | ||
// Use filtered interceptors to intercept requests. | ||
// TODO: this shouldn't be a class anymore | ||
// the overrider explicitly overrides methods and attrs on the request so the `assign` below should be removed. | ||
const overrider = new InterceptedRequestRouter({ | ||
@@ -286,0 +288,0 @@ req: this, |
@@ -74,3 +74,3 @@ 'use strict' | ||
this.delayInMs = 0 | ||
this.delayBodyInMs = 0 | ||
this.delayConnectionInMs = 0 | ||
@@ -602,3 +602,3 @@ | ||
delayBody(ms) { | ||
this.delayInMs += ms | ||
this.delayBodyInMs = ms | ||
return this | ||
@@ -614,24 +614,5 @@ } | ||
delayConnection(ms) { | ||
this.delayConnectionInMs += ms | ||
this.delayConnectionInMs = ms | ||
return this | ||
} | ||
/** | ||
* @private | ||
* @returns {number} | ||
*/ | ||
getTotalDelay() { | ||
return this.delayInMs + this.delayConnectionInMs | ||
} | ||
/** | ||
* Make the socket idle for a certain number of ms (simulated). | ||
* | ||
* @param {integer} ms - Number of milliseconds to wait | ||
* @return {Interceptor} - the current interceptor for chaining | ||
*/ | ||
socketDelay(ms) { | ||
this.socketDelayInMs = ms | ||
return this | ||
} | ||
} |
'use strict' | ||
const stream = require('stream') | ||
const util = require('util') | ||
@@ -7,3 +8,2 @@ const zlib = require('zlib') | ||
const common = require('./common') | ||
const DelayedBody = require('./delayed_body') | ||
@@ -75,2 +75,40 @@ function parseJSONRequestBody(req, requestBody) { | ||
// Presents a list of Buffers as a Readable | ||
class ReadableBuffers extends stream.Readable { | ||
constructor(buffers, opts = {}) { | ||
super(opts) | ||
this.buffers = buffers | ||
} | ||
_read(size) { | ||
while (this.buffers.length) { | ||
if (!this.push(this.buffers.shift())) { | ||
return | ||
} | ||
} | ||
this.push(null) | ||
} | ||
} | ||
function convertBodyToStream(body) { | ||
if (common.isStream(body)) { | ||
return body | ||
} | ||
if (body === undefined) { | ||
return new ReadableBuffers([]) | ||
} | ||
if (Buffer.isBuffer(body)) { | ||
return new ReadableBuffers([body]) | ||
} | ||
if (typeof body !== 'string') { | ||
body = JSON.stringify(body) | ||
} | ||
return new ReadableBuffers([Buffer.from(body)]) | ||
} | ||
/** | ||
@@ -97,2 +135,3 @@ * Play back an interceptor using the given request and mock response. | ||
function start() { | ||
interceptor.markConsumed() | ||
interceptor.req = req | ||
@@ -104,4 +143,2 @@ req.headers = req.getHeaders() | ||
if (typeof interceptor.errorMessage !== 'undefined') { | ||
interceptor.markConsumed() | ||
let error | ||
@@ -113,3 +150,5 @@ if (typeof interceptor.errorMessage === 'object') { | ||
} | ||
common.setTimeout(() => emitError(error), interceptor.getTotalDelay()) | ||
const delay = interceptor.delayBodyInMs + interceptor.delayConnectionInMs | ||
common.setTimeout(emitError, delay, error) | ||
return | ||
@@ -138,4 +177,4 @@ } | ||
Promise.resolve(fn.call(interceptor, options.path, parsedRequestBody)) | ||
.then(responseBody => continueWithResponseBody({ responseBody })) | ||
.catch(err => emitError(err)) | ||
.then(continueWithResponseBody) | ||
.catch(emitError) | ||
return | ||
@@ -153,4 +192,4 @@ } | ||
Promise.resolve(fn.call(interceptor, options.path, parsedRequestBody)) | ||
.then(fullReplyResult => continueWithFullResponse({ fullReplyResult })) | ||
.catch(err => emitError(err)) | ||
.then(continueWithFullResponse) | ||
.catch(emitError) | ||
return | ||
@@ -167,12 +206,2 @@ } | ||
// buffer by buffer and not one single merged buffer) | ||
if (interceptor.delayInMs) { | ||
emitError( | ||
new Error( | ||
'Response delay of the body is currently not supported with content-encoded responses.' | ||
) | ||
) | ||
return | ||
} | ||
const bufferData = Array.isArray(interceptor.body) | ||
@@ -182,3 +211,4 @@ ? interceptor.body | ||
const responseBuffers = bufferData.map(data => Buffer.from(data, 'hex')) | ||
continueWithResponseBody({ responseBuffers }) | ||
const responseBody = new ReadableBuffers(responseBuffers) | ||
continueWithResponseBody(responseBody) | ||
return | ||
@@ -208,70 +238,35 @@ } | ||
return continueWithResponseBody({ responseBody }) | ||
return continueWithResponseBody(responseBody) | ||
} | ||
function continueWithFullResponse({ fullReplyResult }) { | ||
function continueWithFullResponse(fullReplyResult) { | ||
let responseBody | ||
try { | ||
responseBody = parseFullReplyResult(response, fullReplyResult) | ||
} catch (innerErr) { | ||
emitError(innerErr) | ||
} catch (err) { | ||
emitError(err) | ||
return | ||
} | ||
continueWithResponseBody({ responseBody }) | ||
continueWithResponseBody(responseBody) | ||
} | ||
function continueWithResponseBody({ responseBuffers, responseBody }) { | ||
// Transform the response body if it exists (it may not exist | ||
// if we have `responseBuffers` instead) | ||
if (responseBody !== undefined) { | ||
logger('transform the response body') | ||
function prepareResponseHeaders(body) { | ||
const defaultHeaders = [...interceptor.scope._defaultReplyHeaders] | ||
if (interceptor.delayInMs) { | ||
logger( | ||
'delaying the response for', | ||
interceptor.delayInMs, | ||
'milliseconds' | ||
) | ||
// Because setTimeout is called immediately in DelayedBody(), so we | ||
// need count in the delayConnectionInMs. | ||
responseBody = new DelayedBody( | ||
interceptor.getTotalDelay(), | ||
responseBody | ||
) | ||
} | ||
// Include a JSON content type when JSON.stringify is called on the body. | ||
// This is a convenience added by Nock that has no analog in Node. It's added to the | ||
// defaults, so it will be ignored if the caller explicitly provided the header already. | ||
const isJSON = | ||
body !== undefined && | ||
typeof body !== 'string' && | ||
!Buffer.isBuffer(body) && | ||
!common.isStream(body) | ||
if (common.isStream(responseBody)) { | ||
logger('response body is a stream') | ||
responseBody.pause() | ||
responseBody.on('data', function (d) { | ||
response.push(d) | ||
}) | ||
responseBody.on('end', function () { | ||
response.push(null) | ||
// https://nodejs.org/dist/latest-v10.x/docs/api/http.html#http_message_complete | ||
response.complete = true | ||
}) | ||
responseBody.on('error', function (err) { | ||
response.emit('error', err) | ||
}) | ||
} else if (!Buffer.isBuffer(responseBody)) { | ||
if (typeof responseBody === 'string') { | ||
responseBody = Buffer.from(responseBody) | ||
} else { | ||
responseBody = JSON.stringify(responseBody) | ||
response.rawHeaders.push('Content-Type', 'application/json') | ||
} | ||
} | ||
// Why are strings converted to a Buffer, but JSON data is left as a string? | ||
// Related to https://github.com/nock/nock/issues/1542 ? | ||
if (isJSON) { | ||
defaultHeaders.push('Content-Type', 'application/json') | ||
} | ||
interceptor.markConsumed() | ||
response.rawHeaders.push( | ||
...selectDefaultHeaders( | ||
response.rawHeaders, | ||
interceptor.scope._defaultReplyHeaders | ||
) | ||
...selectDefaultHeaders(response.rawHeaders, defaultHeaders) | ||
) | ||
@@ -282,3 +277,3 @@ | ||
if (typeof value === 'function') { | ||
response.rawHeaders[i + 1] = value(req, response, responseBody) | ||
response.rawHeaders[i + 1] = value(req, response, body) | ||
} | ||
@@ -288,10 +283,26 @@ }) | ||
response.headers = common.headersArrayToObject(response.rawHeaders) | ||
} | ||
respondUsingInterceptor({ | ||
responseBody, | ||
responseBuffers, | ||
function continueWithResponseBody(rawBody) { | ||
prepareResponseHeaders(rawBody) | ||
const bodyAsStream = convertBodyToStream(rawBody) | ||
bodyAsStream.pause() | ||
// IncomingMessage extends Readable so we can't simply pipe. | ||
bodyAsStream.on('data', function (chunk) { | ||
response.push(chunk) | ||
}) | ||
} | ||
bodyAsStream.on('end', function () { | ||
// https://nodejs.org/dist/latest-v10.x/docs/api/http.html#http_message_complete | ||
response.complete = true | ||
response.push(null) | ||
function respondUsingInterceptor({ responseBody, responseBuffers }) { | ||
interceptor.scope.emit('replied', req, interceptor) | ||
}) | ||
bodyAsStream.on('error', function (err) { | ||
response.emit('error', err) | ||
}) | ||
const { delayBodyInMs, delayConnectionInMs } = interceptor | ||
function respond() { | ||
@@ -311,51 +322,13 @@ if (req.aborted) { | ||
if (common.isStream(responseBody)) { | ||
logger('resuming response stream') | ||
responseBody.resume() | ||
} else { | ||
responseBuffers = responseBuffers || [] | ||
if (typeof responseBody !== 'undefined') { | ||
logger('adding body to buffer list') | ||
responseBuffers.push(responseBody) | ||
} | ||
// Stream the response chunks one at a time. | ||
common.setImmediate(function emitChunk() { | ||
const chunk = responseBuffers.shift() | ||
if (chunk) { | ||
logger('emitting response chunk') | ||
response.push(chunk) | ||
common.setImmediate(emitChunk) | ||
} else { | ||
logger('ending response stream') | ||
response.push(null) | ||
// https://nodejs.org/dist/latest-v10.x/docs/api/http.html#http_message_complete | ||
response.complete = true | ||
interceptor.scope.emit('replied', req, interceptor) | ||
} | ||
}) | ||
} | ||
common.setTimeout(() => bodyAsStream.resume(), delayBodyInMs) | ||
} | ||
if (interceptor.socketDelayInMs && interceptor.socketDelayInMs > 0) { | ||
socket.applyDelay(interceptor.socketDelayInMs) | ||
} | ||
if ( | ||
interceptor.delayConnectionInMs && | ||
interceptor.delayConnectionInMs > 0 | ||
) { | ||
socket.applyDelay(interceptor.delayConnectionInMs) | ||
common.setTimeout(respond, interceptor.delayConnectionInMs) | ||
} else { | ||
respond() | ||
} | ||
socket.applyDelay(delayConnectionInMs) | ||
common.setTimeout(respond, delayConnectionInMs) | ||
} | ||
// If there are no delays configured on the Interceptor, calling `start` will | ||
// take the request all the way to the 'response' event during a single microtask | ||
// execution. This setImmediate stalls the playback to ensure the correct events | ||
// are emitted first ('socket', 'finish') and any aborts in the in the queue | ||
// or called during a 'finish' listener can be called. | ||
// Calling `start` immediately could take the request all the way to the connection delay | ||
// during a single microtask execution. This setImmediate stalls the playback to ensure the | ||
// correct events are emitted first ('socket', 'finish') and any aborts in the in the queue or | ||
// called during a 'finish' listener can be called. | ||
common.setImmediate(() => { | ||
@@ -362,0 +335,0 @@ if (!req.aborted) { |
@@ -23,8 +23,4 @@ 'use strict' | ||
// totalDelay that has already been applied to the current | ||
// request/connection, timeout error will be generated if | ||
// it is timed-out. | ||
this.totalDelayMs = 0 | ||
// Maximum allowed delay. Null means unlimited. | ||
this.timeoutMs = null | ||
// Maximum allowed delay. 0 means unlimited. | ||
this.timeout = 0 | ||
@@ -52,12 +48,18 @@ const ipv6 = options.family === 6 | ||
setTimeout(timeoutMs, fn) { | ||
this.timeoutMs = timeoutMs | ||
this.timeout = timeoutMs | ||
if (fn) { | ||
this.once('timeout', fn) | ||
} | ||
return this | ||
} | ||
/** | ||
* Artificial delay that will trip socket timeouts when appropriate. | ||
* | ||
* Doesn't actually wait for time to pass. | ||
* Timeout events don't necessarily end the request. | ||
* While many clients choose to abort the request upon a timeout, Node itself does not. | ||
*/ | ||
applyDelay(delayMs) { | ||
this.totalDelayMs += delayMs | ||
if (this.timeoutMs && this.totalDelayMs > this.timeoutMs) { | ||
if (this.timeout && delayMs > this.timeout) { | ||
debug('socket timeout') | ||
@@ -64,0 +66,0 @@ this.emit('timeout') |
@@ -10,3 +10,3 @@ { | ||
], | ||
"version": "13.0.0-beta.3", | ||
"version": "13.0.0-beta.4", | ||
"author": "Pedro Teixeira <pedro.teixeira@gmail.com>", | ||
@@ -13,0 +13,0 @@ "repository": { |
@@ -46,6 +46,3 @@ # Nock | ||
- [Repeat response n times](#repeat-response-n-times) | ||
- [Delay the response body](#delay-the-response-body) | ||
- [Delay the response](#delay-the-response) | ||
- [Delay the connection](#delay-the-connection) | ||
- [Socket timeout](#socket-timeout) | ||
- [Chaining](#chaining) | ||
@@ -664,18 +661,5 @@ - [Scope filtering](#scope-filtering) | ||
### Delay the response body | ||
You are able to specify the number of milliseconds that the response body should be delayed. Response header will be replied immediately. | ||
`delayBody(1000)` is equivalent to `delay({body: 1000})`. | ||
```js | ||
nock('http://my.server.com') | ||
.get('/') | ||
.delayBody(2000) // 2 seconds | ||
.reply(200, '<html></html>') | ||
``` | ||
NOTE: the [`'response'`](http://nodejs.org/api/http.html#http_event_response) event will occur immediately, but the [IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) will not emit its `'end'` event until after the delay. | ||
### Delay the response | ||
Nock can simulate response latency to allow you to test timeouts, race conditions, an other timing related scenarios. | ||
You are able to specify the number of milliseconds that your reply should be delayed. | ||
@@ -690,50 +674,51 @@ | ||
`delay()` could also be used as | ||
`delay(1000)` is an alias for `delayConnection(1000).delayBody(0)` | ||
`delay({ head: 1000, body: 2000 })` is an alias for `delayConnection(1000).delayBody(2000)` | ||
Both of which are covered in detail below. | ||
``` | ||
delay({ | ||
head: headDelayInMs, | ||
body: bodyDelayInMs | ||
}) | ||
``` | ||
#### Delay the connection | ||
for example | ||
You are able to specify the number of milliseconds that your connection should be idle before it starts to receive the response. | ||
To simulate a socket timeout, provide a larger value than the timeout setting on the request. | ||
```js | ||
nock('http://my.server.com') | ||
.get('/') | ||
.delay({ | ||
head: 2000, // header will be delayed for 2 seconds, i.e. the whole response will be delayed for 2 seconds. | ||
body: 3000, // body will be delayed for another 3 seconds after header is sent out. | ||
}) | ||
.delayConnection(2000) // 2 seconds | ||
.reply(200, '<html></html>') | ||
req = http.request('http://my.server.com', { timeout: 1000 }) | ||
``` | ||
### Delay the connection | ||
Nock emits timeout events almost immediately by comparing the requested connection delay to the timeout parameter passed to `http.request()` or `http.ClientRequest#setTimeout()`. | ||
This allows you to test timeouts without using fake timers or slowing down your tests. | ||
If the client chooses to _not_ take an action (e.g. abort the request), the request and response will continue on as normal, after real clock time has passed. | ||
`delayConnection(1000)` is equivalent to `delay({ head: 1000 })`. | ||
##### Technical Details | ||
### Socket timeout | ||
Following the `'finish'` event being emitted by `ClientRequest`, Nock will wait for the next event loop iteration before checking if the request has been aborted. | ||
At this point, any connection delay value is compared against any request timeout setting and a [`'timeout'`](https://nodejs.org/api/http.html#http_event_timeout) is emitted when appropriate from the socket and the request objects. | ||
A Node timeout timer is then registered with any connection delay value to delay real time before checking again if the request has been aborted and the [`'response'`](http://nodejs.org/api/http.html#http_event_response) is emitted by the request. | ||
You are able to specify the number of milliseconds that your connection should be idle, to simulate a socket timeout. | ||
A similar method, `.socketDelay()` was removed in version 13. It was thought that having two methods so subtlety similar was confusing. | ||
The discussion can be found at https://github.com/nock/nock/pull/1974. | ||
#### Delay the response body | ||
You are able to specify the number of milliseconds that the response body should be delayed. | ||
This is the time between the headers being received and the body starting to be received. | ||
```js | ||
nock('http://my.server.com') | ||
.get('/') | ||
.socketDelay(2000) // 2 seconds | ||
.delayBody(2000) // 2 seconds | ||
.reply(200, '<html></html>') | ||
``` | ||
To test a request like the following: | ||
##### Technical Details | ||
```js | ||
req = http.request('http://my.server.com', res => { | ||
... | ||
}) | ||
req.setTimeout(1000, () => { req.abort() }) | ||
req.end() | ||
``` | ||
Following the [`'response'`](http://nodejs.org/api/http.html#http_event_response) being emitted by `ClientRequest`, | ||
Nock will register a timeout timer with the body delay value to delay real time before the [IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) emits its first `'data'` or the `'end'` event. | ||
NOTE: the timeout will be fired immediately, and will not leave the simulated connection idle for the specified period of time. | ||
### Chaining | ||
@@ -740,0 +725,0 @@ |
@@ -206,3 +206,2 @@ // TypeScript Version: 3.5 | ||
delayConnection(timeMs: number): this | ||
socketDelay(timeMs: number): this | ||
} | ||
@@ -209,0 +208,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
172795
17
3402
1588