Skip to content

Commit 2e779f0

Browse files
mastermattpaulmelnikow
authored andcommitted
fix: Update and clarify how .reply() can be invoked with functions (#1520)
Ref https://github.com/nock/nock/pull/1517/files#r280139478 Closes #1222
1 parent 3c8a26d commit 2e779f0

16 files changed

+536
-219
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,14 @@ const scope = nock('http://www.google.com')
360360
.reply(201, (uri, requestBody) => requestBody)
361361
```
362362

363+
In Nock 11.x it was possible to invoke `.reply()` with a status code and a
364+
function that returns an array containing a status code and body. (The status
365+
code from the array would take precedence over the one passed directly to
366+
reply.) This is no longer allowed. In 12.x, either call `.reply()` with a
367+
status code and a function that returns the body, or call it with a single
368+
argument: a function that returns an array containing both the status code and
369+
body.
370+
363371
An asynchronous function that gets an error-first callback as its last argument also works:
364372

365373
```js

lib/common.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const normalizeRequestOptions = function(options) {
4040
* from its utf8 representation.
4141
*
4242
* TODO: Reverse the semantics of this method and refactor calling code
43-
* accordingly. We've inadvertantly gotten it flipped.
43+
* accordingly. We've inadvertently gotten it flipped.
4444
*
4545
* @param {Object} buffer - a Buffer object
4646
*/
@@ -245,7 +245,7 @@ const headersArrayToObject = function(rawHeaders) {
245245
const value = rawHeaders[i + 1]
246246

247247
if (headers[key]) {
248-
headers[key] = _.isArray(headers[key]) ? headers[key] : [headers[key]]
248+
headers[key] = Array.isArray(headers[key]) ? headers[key] : [headers[key]]
249249
headers[key].push(value)
250250
} else {
251251
headers[key] = value
@@ -324,7 +324,7 @@ function matchStringOrRegexp(target, pattern) {
324324
* @param stringFormattingFn The function used to format string values. Can
325325
* be used to encode or decode the query value.
326326
*
327-
* @returns the formatted [key, value] pair.
327+
* @returns *[] the formatted [key, value] pair.
328328
*/
329329
function formatQueryValue(key, value, stringFormattingFn) {
330330
// TODO-coverage: Find out what's not covered. Probably refactor code to
@@ -371,7 +371,7 @@ function formatQueryValue(key, value, stringFormattingFn) {
371371
function isStream(obj) {
372372
return (
373373
obj &&
374-
typeof a !== 'string' &&
374+
typeof obj !== 'string' &&
375375
!Buffer.isBuffer(obj) &&
376376
_.isFunction(obj.setEncoding)
377377
)

lib/interceptor.js

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,28 @@ Interceptor.prototype.replyWithError = function replyWithError(errorMessage) {
7878
}
7979

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

86-
this.statusCode = statusCode
97+
this.statusCode = statusCode || 200
98+
if (_.isFunction(body)) {
99+
this.replyFunction = body
100+
body = null
101+
}
102+
}
87103

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

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

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

109127
for (const key in headers) {
110-
this.rawHeaders.push(key)
111-
this.rawHeaders.push(headers[key])
128+
this.rawHeaders.push(key, headers[key])
112129
}
113130

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

121138
// If the content is not encoded we may need to transform the response body.
122139
// Otherwise we leave it as it is.
123-
if (!common.isContentEncoded(this.headers)) {
124-
if (
125-
body &&
126-
typeof body !== 'string' &&
127-
typeof body !== 'function' &&
128-
!Buffer.isBuffer(body) &&
129-
!common.isStream(body)
130-
) {
131-
try {
132-
body = stringify(body)
133-
if (!this.headers) {
134-
this.headers = {}
135-
}
136-
if (!this.headers['content-type']) {
137-
this.headers['content-type'] = 'application/json'
138-
}
139-
if (this.scope.contentLen) {
140-
this.headers['content-length'] = body.length
141-
}
142-
} catch (err) {
143-
throw new Error('Error encoding response body into JSON')
140+
if (
141+
body &&
142+
typeof body !== 'string' &&
143+
typeof body !== 'function' &&
144+
!Buffer.isBuffer(body) &&
145+
!common.isStream(body) &&
146+
!common.isContentEncoded(this.headers)
147+
) {
148+
try {
149+
body = stringify(body)
150+
if (!this.headers) {
151+
this.headers = {}
152+
}
153+
if (!this.headers['content-type']) {
154+
this.headers['content-type'] = 'application/json'
155+
}
156+
if (this.scope.contentLen) {
157+
this.headers['content-length'] = body.length
144158
}
159+
} catch (err) {
160+
throw new Error('Error encoding response body into JSON')
145161
}
146162
}
147163

@@ -454,7 +470,7 @@ Interceptor.prototype.basicAuth = function basicAuth(options) {
454470
/**
455471
* Set query strings for the interceptor
456472
* @name query
457-
* @param Object Object of query string name,values (accepts regexp values)
473+
* @param queries Object of query string name,values (accepts regexp values)
458474
* @public
459475
* @example
460476
* // Will match 'http://zombo.com/?q=t'
@@ -496,7 +512,7 @@ Interceptor.prototype.query = function query(queries) {
496512
/**
497513
* Set number of times will repeat the interceptor
498514
* @name times
499-
* @param Integer Number of times to repeat (should be > 0)
515+
* @param newCounter Number of times to repeat (should be > 0)
500516
* @public
501517
* @example
502518
* // Will repeat mock 5 times for same king of request
@@ -554,7 +570,7 @@ Interceptor.prototype.thrice = function thrice() {
554570
* @param {(integer|object)} opts - Number of milliseconds to wait, or an object
555571
* @param {integer} [opts.head] - Number of milliseconds to wait before response is sent
556572
* @param {integer} [opts.body] - Number of milliseconds to wait before response body is sent
557-
* @return {interceptor} - the current interceptor for chaining
573+
* @return {Interceptor} - the current interceptor for chaining
558574
*/
559575
Interceptor.prototype.delay = function delay(opts) {
560576
let headDelay = 0
@@ -565,7 +581,7 @@ Interceptor.prototype.delay = function delay(opts) {
565581
headDelay = opts.head || 0
566582
bodyDelay = opts.body || 0
567583
} else {
568-
throw new Error(`Unexpected input opts${opts}`)
584+
throw new Error(`Unexpected input opts ${opts}`)
569585
}
570586

571587
return this.delayConnection(headDelay).delayBody(bodyDelay)
@@ -575,7 +591,7 @@ Interceptor.prototype.delay = function delay(opts) {
575591
* Delay the response body by a certain number of ms.
576592
*
577593
* @param {integer} ms - Number of milliseconds to wait before response is sent
578-
* @return {interceptor} - the current interceptor for chaining
594+
* @return {Interceptor} - the current interceptor for chaining
579595
*/
580596
Interceptor.prototype.delayBody = function delayBody(ms) {
581597
this.delayInMs += ms
@@ -586,7 +602,7 @@ Interceptor.prototype.delayBody = function delayBody(ms) {
586602
* Delay the connection by a certain number of ms.
587603
*
588604
* @param {integer} ms - Number of milliseconds to wait
589-
* @return {interceptor} - the current interceptor for chaining
605+
* @return {Interceptor} - the current interceptor for chaining
590606
*/
591607
Interceptor.prototype.delayConnection = function delayConnection(ms) {
592608
this.delayConnectionInMs += ms
@@ -601,7 +617,7 @@ Interceptor.prototype.getTotalDelay = function getTotalDelay() {
601617
* Make the socket idle for a certain number of ms (simulated).
602618
*
603619
* @param {integer} ms - Number of milliseconds to wait
604-
* @return {interceptor} - the current interceptor for chaining
620+
* @return {Interceptor} - the current interceptor for chaining
605621
*/
606622
Interceptor.prototype.socketDelay = function socketDelay(ms) {
607623
this.socketDelayInMs = ms

0 commit comments

Comments
 (0)