Skip to content

Commit ad34222

Browse files
mastermattpaulmelnikow
authored andcommitted
fix: request.end accepted arguments (#1591)
* Fix typo about wrapping request.end. I'm making this change it's own commit so I have a place to comment the findings. Digging through Node git history, I found the change that created the breaking change in nock (ref nock PR 929). nodejs/node@a10bdb5#diff-286202fdbdd74ede6f5f5334b6176b5cL779 Before Node v8, `OutgoingMessage`, which is extended by `ClientRequest`, would literally do what it says in the docs if data was provided. It would call `this.write(data, encoding)`. This meant that nock could wrap only the `write` method when recording and gather all the chunks even if the last chunk was sent to `end`. But, the above changed that to call an internal function dual used by `end` and `write`. * fix: request.end accepted arguments. Fixes #1549 The method now correctly accepts all the permutations allowed. request.end(data, encoding, callback) request.end(data, callback) request.end(data, encoding) request.end(data) request.end(callback) request.end() And a few tests were added to ensure all cases are explicitly covered.
1 parent e200e83 commit ad34222

File tree

4 files changed

+152
-31
lines changed

4 files changed

+152
-31
lines changed

lib/recorder.js

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -378,39 +378,41 @@ function record(recOptions) {
378378
}
379379
})
380380

381+
const recordChunk = (chunk, encoding) => {
382+
debug(thisRecordingId, 'new', proto, 'body chunk')
383+
if (!Buffer.isBuffer(chunk)) {
384+
chunk = Buffer.from(chunk, encoding)
385+
}
386+
bodyChunks.push(chunk)
387+
}
388+
381389
const oldWrite = req.write
382-
req.write = function(data, encoding) {
383-
if (typeof data !== 'undefined') {
384-
debug(thisRecordingId, 'new', proto, 'body chunk')
385-
if (!Buffer.isBuffer(data)) {
386-
data = Buffer.from(data, encoding)
387-
}
388-
bodyChunks.push(data)
390+
req.write = function(chunk, encoding) {
391+
if (typeof chunk !== 'undefined') {
392+
recordChunk(chunk, encoding)
389393
oldWrite.apply(req, arguments)
390394
} else {
391395
throw new Error('Data was undefined.')
392396
}
393397
}
394398

395-
// Starting in Node 8, `res.end()` does not call `res.write()` directly.
396-
// TODO: This is `req.end()`; is that a typo? ^^
399+
// Starting in Node 8, `OutgoingMessage.end()` directly calls an internal `write_` function instead
400+
// of proxying to the public `OutgoingMessage.write()` method, so we have to wrap `end` too.
397401
const oldEnd = req.end
398-
req.end = function(data, encoding, callback) {
399-
// TODO Shuffle the arguments for parity with the real `req.end()`.
400-
// https://github.com/nock/nock/issues/1549
401-
if (_.isFunction(data) && arguments.length === 1) {
402-
callback = data
403-
data = null
402+
req.end = function(chunk, encoding, callback) {
403+
debug('req.end')
404+
if (typeof chunk === 'function') {
405+
callback = chunk
406+
chunk = null
407+
} else if (typeof encoding === 'function') {
408+
callback = encoding
409+
encoding = null
404410
}
405-
if (data) {
406-
debug(thisRecordingId, 'new', proto, 'body chunk')
407-
if (!Buffer.isBuffer(data)) {
408-
// TODO-coverage: Add a test.
409-
data = Buffer.from(data, encoding)
410-
}
411-
bodyChunks.push(data)
411+
412+
if (chunk) {
413+
recordChunk(chunk, encoding)
412414
}
413-
oldEnd.apply(req, arguments)
415+
oldEnd.call(req, chunk, encoding, callback)
414416
}
415417

416418
return req

lib/request_overrider.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,18 @@ function RequestOverrider(req, options, interceptors, remove, cb) {
129129
return false
130130
}
131131

132-
req.end = function(data, encoding, callback) {
132+
req.end = function(chunk, encoding, callback) {
133133
debug('req.end')
134-
// TODO Shuffle the arguments for parity with the real `req.end()`.
135-
// https://github.com/nock/nock/issues/1549
136-
if (_.isFunction(data) && arguments.length === 1) {
137-
callback = data
138-
data = null
134+
if (typeof chunk === 'function') {
135+
callback = chunk
136+
chunk = null
137+
} else if (typeof encoding === 'function') {
138+
callback = encoding
139+
encoding = null
139140
}
141+
140142
if (!req.aborted && !ended) {
141-
req.write(data, encoding, function() {
143+
req.write(chunk, encoding, () => {
142144
if (typeof callback === 'function') {
143145
callback()
144146
}

tests/test_recorder.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,45 @@ test('records nonstandard ports', t => {
444444
})
445445
})
446446

447-
test('rec() throws when reenvoked with already recorder requests', t => {
447+
test('req.end accepts and calls a callback when recording', t => {
448+
const server = http.createServer((request, response) => {
449+
response.writeHead(200)
450+
response.end()
451+
})
452+
t.once('end', () => server.close())
453+
454+
nock.restore()
455+
nock.recorder.clear()
456+
t.equal(nock.recorder.play().length, 0)
457+
458+
server.listen(() => {
459+
nock.recorder.rec({ dont_print: true })
460+
461+
let callbackCalled = false
462+
const req = http.request(
463+
{
464+
hostname: 'localhost',
465+
port: server.address().port,
466+
path: '/',
467+
method: 'GET',
468+
},
469+
res => {
470+
t.true(callbackCalled)
471+
t.equal(res.statusCode, 200)
472+
res.on('end', () => {
473+
t.end()
474+
})
475+
res.resume()
476+
}
477+
)
478+
479+
req.end(() => {
480+
callbackCalled = true
481+
})
482+
})
483+
})
484+
485+
test('rec() throws when reinvoked with already recorder requests', t => {
448486
nock.restore()
449487
nock.recorder.clear()
450488
t.equal(nock.recorder.play().length, 0)

tests/test_request_overrider.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,85 @@ test('end callback called when end has callback, but no buffer', t => {
135135
})
136136
})
137137

138+
test('request.end called with all three arguments', t => {
139+
const scope = nock('http://example.test')
140+
.post('/', 'foobar')
141+
.reply()
142+
143+
let callbackCalled = false
144+
const req = http.request(
145+
{
146+
host: 'example.test',
147+
method: 'POST',
148+
path: '/',
149+
},
150+
res => {
151+
t.true(callbackCalled)
152+
res.on('end', () => {
153+
scope.done()
154+
t.end()
155+
})
156+
res.resume()
157+
}
158+
)
159+
160+
// hex(foobar) == 666F6F626172
161+
req.end('666F6F626172', 'hex', () => {
162+
callbackCalled = true
163+
})
164+
})
165+
166+
test('request.end called with only data and encoding', t => {
167+
const scope = nock('http://example.test')
168+
.post('/', 'foobar')
169+
.reply()
170+
171+
const req = http.request(
172+
{
173+
host: 'example.test',
174+
method: 'POST',
175+
path: '/',
176+
},
177+
res => {
178+
res.on('end', () => {
179+
scope.done()
180+
t.end()
181+
})
182+
res.resume()
183+
}
184+
)
185+
186+
// hex(foobar) == 666F6F626172
187+
req.end('666F6F626172', 'hex')
188+
})
189+
190+
test('request.end called with only data and a callback', t => {
191+
const scope = nock('http://example.test')
192+
.post('/', 'foobar')
193+
.reply()
194+
195+
let callbackCalled = false
196+
const req = http.request(
197+
{
198+
host: 'example.test',
199+
method: 'POST',
200+
path: '/',
201+
},
202+
res => {
203+
t.true(callbackCalled)
204+
res.on('end', () => {
205+
scope.done()
206+
t.end()
207+
})
208+
res.resume()
209+
}
210+
)
211+
212+
req.end('foobar', () => {
213+
callbackCalled = true
214+
})
215+
})
216+
138217
// http://github.com/nock/nock/issues/139
139218
test('finish event fired before end event', t => {
140219
const scope = nock('http://example.test')

0 commit comments

Comments
 (0)