Skip to content

Commit

Permalink
fix: Update and clarify how .reply() can be invoked with functions (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
mastermatt authored and gr2m committed Sep 5, 2019
1 parent 8cc3bb6 commit ff88c1f
Show file tree
Hide file tree
Showing 16 changed files with 536 additions and 219 deletions.
8 changes: 8 additions & 0 deletions README.md
Expand Up @@ -368,6 +368,14 @@ const scope = nock('http://www.google.com')
.reply(201, (uri, requestBody) => requestBody)
```

In Nock 11.x it was possible to invoke `.reply()` with a status code and a
function that returns an array containing a status code and body. (The status
code from the array would take precedence over the one passed directly to
reply.) This is no longer allowed. In 12.x, either call `.reply()` with a
status code and a function that returns the body, or call it with a single
argument: a function that returns an array containing both the status code and
body.

An asynchronous function that gets an error-first callback as its last argument also works:

```js
Expand Down
6 changes: 3 additions & 3 deletions lib/common.js
Expand Up @@ -40,7 +40,7 @@ const normalizeRequestOptions = function(options) {
* from its utf8 representation.
*
* TODO: Reverse the semantics of this method and refactor calling code
* accordingly. We've inadvertantly gotten it flipped.
* accordingly. We've inadvertently gotten it flipped.
*
* @param {Object} buffer - a Buffer object
*/
Expand Down Expand Up @@ -245,7 +245,7 @@ const headersArrayToObject = function(rawHeaders) {
const value = rawHeaders[i + 1]

if (headers[key]) {
headers[key] = _.isArray(headers[key]) ? headers[key] : [headers[key]]
headers[key] = Array.isArray(headers[key]) ? headers[key] : [headers[key]]
headers[key].push(value)
} else {
headers[key] = value
Expand Down Expand Up @@ -378,7 +378,7 @@ function formatQueryValue(key, value, stringFormattingFn) {
function isStream(obj) {
return (
obj &&
typeof a !== 'string' &&
typeof obj !== 'string' &&
!Buffer.isBuffer(obj) &&
_.isFunction(obj.setEncoding)
)
Expand Down
86 changes: 51 additions & 35 deletions lib/interceptor.js
Expand Up @@ -78,12 +78,28 @@ Interceptor.prototype.replyWithError = function replyWithError(errorMessage) {
}

Interceptor.prototype.reply = function reply(statusCode, body, rawHeaders) {
if (arguments.length <= 2 && _.isFunction(statusCode)) {
body = statusCode
statusCode = 200
}
// support the format of only passing in a callback
if (_.isFunction(statusCode)) {
if (arguments.length > 1) {
// It's not very Javascript-y to throw an error for extra args to a function, but because
// of legacy behavior, this error was added to reduce confusion for those migrating.
throw Error(
'Invalid arguments. When providing a function for the first argument, .reply does not accept other arguments.'
)
}
this.statusCode = null
this.fullReplyFunction = statusCode
} else {
if (statusCode && !Number.isInteger(statusCode)) {
throw new Error(`Invalid ${typeof statusCode} value for status code`)
}

this.statusCode = statusCode
this.statusCode = statusCode || 200
if (_.isFunction(body)) {
this.replyFunction = body
body = null
}
}

_.defaults(this.options, this.scope.scopeOptions)

Expand All @@ -94,6 +110,8 @@ Interceptor.prototype.reply = function reply(statusCode, body, rawHeaders) {

if (this.scope._defaultReplyHeaders) {
headers = headers || {}
// Because of this, this.rawHeaders gets lower-cased versions of the `rawHeaders` param.
// https://github.com/nock/nock/issues/1553
headers = common.headersFieldNamesToLowerCase(headers)
headers = mixin(this.scope._defaultReplyHeaders, headers)
}
Expand All @@ -107,8 +125,7 @@ Interceptor.prototype.reply = function reply(statusCode, body, rawHeaders) {
this.rawHeaders = []

for (const key in headers) {
this.rawHeaders.push(key)
this.rawHeaders.push(headers[key])
this.rawHeaders.push(key, headers[key])
}

// We use lower-case headers throughout Nock.
Expand All @@ -120,28 +137,27 @@ Interceptor.prototype.reply = function reply(statusCode, body, rawHeaders) {

// If the content is not encoded we may need to transform the response body.
// Otherwise we leave it as it is.
if (!common.isContentEncoded(this.headers)) {
if (
body &&
typeof body !== 'string' &&
typeof body !== 'function' &&
!Buffer.isBuffer(body) &&
!common.isStream(body)
) {
try {
body = stringify(body)
if (!this.headers) {
this.headers = {}
}
if (!this.headers['content-type']) {
this.headers['content-type'] = 'application/json'
}
if (this.scope.contentLen) {
this.headers['content-length'] = body.length
}
} catch (err) {
throw new Error('Error encoding response body into JSON')
if (
body &&
typeof body !== 'string' &&
typeof body !== 'function' &&
!Buffer.isBuffer(body) &&
!common.isStream(body) &&
!common.isContentEncoded(this.headers)
) {
try {
body = stringify(body)
if (!this.headers) {
this.headers = {}
}
if (!this.headers['content-type']) {
this.headers['content-type'] = 'application/json'
}
if (this.scope.contentLen) {
this.headers['content-length'] = body.length
}
} catch (err) {
throw new Error('Error encoding response body into JSON')
}
}

Expand Down Expand Up @@ -454,7 +470,7 @@ Interceptor.prototype.basicAuth = function basicAuth(options) {
/**
* Set query strings for the interceptor
* @name query
* @param Object Object of query string name,values (accepts regexp values)
* @param queries Object of query string name,values (accepts regexp values)
* @public
* @example
* // Will match 'http://zombo.com/?q=t'
Expand Down Expand Up @@ -496,7 +512,7 @@ Interceptor.prototype.query = function query(queries) {
/**
* Set number of times will repeat the interceptor
* @name times
* @param Integer Number of times to repeat (should be > 0)
* @param newCounter Number of times to repeat (should be > 0)
* @public
* @example
* // Will repeat mock 5 times for same king of request
Expand Down Expand Up @@ -554,7 +570,7 @@ Interceptor.prototype.thrice = function thrice() {
* @param {(integer|object)} opts - Number of milliseconds to wait, or an object
* @param {integer} [opts.head] - Number of milliseconds to wait before response is sent
* @param {integer} [opts.body] - Number of milliseconds to wait before response body is sent
* @return {interceptor} - the current interceptor for chaining
* @return {Interceptor} - the current interceptor for chaining
*/
Interceptor.prototype.delay = function delay(opts) {
let headDelay = 0
Expand All @@ -565,7 +581,7 @@ Interceptor.prototype.delay = function delay(opts) {
headDelay = opts.head || 0
bodyDelay = opts.body || 0
} else {
throw new Error(`Unexpected input opts${opts}`)
throw new Error(`Unexpected input opts ${opts}`)
}

return this.delayConnection(headDelay).delayBody(bodyDelay)
Expand All @@ -575,7 +591,7 @@ Interceptor.prototype.delay = function delay(opts) {
* Delay the response body by a certain number of ms.
*
* @param {integer} ms - Number of milliseconds to wait before response is sent
* @return {interceptor} - the current interceptor for chaining
* @return {Interceptor} - the current interceptor for chaining
*/
Interceptor.prototype.delayBody = function delayBody(ms) {
this.delayInMs += ms
Expand All @@ -586,7 +602,7 @@ Interceptor.prototype.delayBody = function delayBody(ms) {
* Delay the connection by a certain number of ms.
*
* @param {integer} ms - Number of milliseconds to wait
* @return {interceptor} - the current interceptor for chaining
* @return {Interceptor} - the current interceptor for chaining
*/
Interceptor.prototype.delayConnection = function delayConnection(ms) {
this.delayConnectionInMs += ms
Expand All @@ -601,7 +617,7 @@ Interceptor.prototype.getTotalDelay = function getTotalDelay() {
* 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
* @return {Interceptor} - the current interceptor for chaining
*/
Interceptor.prototype.socketDelay = function socketDelay(ms) {
this.socketDelayInMs = ms
Expand Down

0 comments on commit ff88c1f

Please sign in to comment.