From e22d734a29f6772f3617a84757e2fa824b796d13 Mon Sep 17 00:00:00 2001 From: "Matt R. Wilson" Date: Tue, 2 Mar 2021 13:23:44 -0700 Subject: [PATCH] fix(recorder): don't automatically resume the response (#2160) --- lib/recorder.js | 2 - tests/test_recorder.js | 1991 ++++++++++++++++++++-------------------- 2 files changed, 980 insertions(+), 1013 deletions(-) diff --git a/lib/recorder.js b/lib/recorder.js index 6903d8d20..85a7441e4 100644 --- a/lib/recorder.js +++ b/lib/recorder.js @@ -309,8 +309,6 @@ function record(recOptions) { if (callback) { callback(res, options, callback) - } else { - res.resume() } debug('finished setting up intercepting') diff --git a/tests/test_recorder.js b/tests/test_recorder.js index a52d1f260..3fc0f5311 100644 --- a/tests/test_recorder.js +++ b/tests/test_recorder.js @@ -11,554 +11,401 @@ const nock = require('..') const got = require('./got_client') const servers = require('./servers') -// TODO: the guts of this file should be wrapped in a `describe`. -// These before and afters run for every test in the repo under Mocha. -let globalCount -beforeEach(() => { - globalCount = Object.keys(global).length -}) -afterEach(() => { - let leaks = Object.keys(global).splice(globalCount, Number.MAX_VALUE) - if (leaks.length === 1 && leaks[0] === '_key') { - leaks = [] - } - expect(leaks).to.be.empty() -}) - -it('does not record requests from previous sessions', async () => { - const { origin } = await servers.startHttpServer((req, res) => res.end()) - - nock.restore() - nock.recorder.clear() - nock.recorder.rec(true) - - const req1 = http.get(`${origin}/foo`) - const req1Promise = new Promise(resolve => { - req1.on('response', res => res.on('end', resolve)) +describe('Recorder', () => { + let globalCount + beforeEach(() => { + globalCount = Object.keys(global).length }) - - // start a new recording session while the first request is still in flight - nock.restore() - nock.recorder.rec(true) - await got.post(`${origin}/bar`) - - // wait for the first request to end - await req1Promise - - // validate only the request from the second session is in the outputs - const outputs = nock.recorder.play() - expect(outputs).to.have.lengthOf(1) - expect(outputs[0]).to.match(/\.post\('\/bar'\)/) -}) - -it('when request port is different, use the alternate port', async () => { - nock.restore() - nock.recorder.clear() - nock.recorder.rec(true) - - const { origin, port } = await servers.startHttpServer((request, response) => - response.end() - ) - - expect(port).not.to.equal(80) - - await got.post(origin) - - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.include(`nock('http://localhost:${port}',`) -}) - -it('recording turns off nock interception (backward compatibility behavior)', () => { - // We ensure that there are no overrides. - nock.restore() - expect(nock.isActive()).to.be.false() - // We active the nock overriding - as it's done by merely loading nock. - nock.activate() - expect(nock.isActive()).to.be.true() - // We start recording. - nock.recorder.rec() - // Nothing happens (nothing has been thrown) - which was the original behavior - - // and mocking has been deactivated. - expect(nock.isActive()).to.be.false() -}) - -it('records', async () => { - const gotRequest = sinon.spy() - - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() - - const { origin, port } = await servers.startHttpServer( - (request, response) => { - gotRequest() - response.writeHead(200) - response.end() + afterEach(() => { + let leaks = Object.keys(global).splice(globalCount, Number.MAX_VALUE) + if (leaks.length === 1 && leaks[0] === '_key') { + leaks = [] } - ) - - nock.recorder.rec(true) - - await got.post(origin) - - expect(gotRequest).to.have.been.calledOnce() - - nock.restore() - - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.be.a('string') - // TODO: Use chai-string? - expect( - recorded[0].startsWith( - `\nnock('http://localhost:${port}', {"encodedQueryParams":true})\n .post('/')` - ) - ).to.be.true() -}) - -it('records objects', async () => { - const gotRequest = sinon.spy() - - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() - - const { origin } = await servers.startHttpServer((request, response) => { - gotRequest() - response.writeHead(200) - response.end() + expect(leaks).to.be.empty() }) - nock.recorder.rec({ - dont_print: true, - output_objects: true, - }) + it('does not record requests from previous sessions', async () => { + const { origin } = await servers.startHttpServer() - const requestBody = '0123455' - await got.post(origin, { body: requestBody }) - - expect(gotRequest).to.have.been.calledOnce() - nock.restore() - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.include({ - scope: origin, - method: 'POST', - body: requestBody, - }) -}) + nock.restore() + nock.recorder.clear() + nock.recorder.rec(true) -it('logs recorded objects', async () => { - const gotRequest = sinon.spy() - const loggingFn = sinon.spy() + const req1 = http.get(`${origin}/foo`) + const req1Promise = new Promise(resolve => { + req1.on('response', res => { + res.on('end', resolve) + res.resume() + }) + }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + // start a new recording session while the first request is still in flight + nock.restore() + nock.recorder.rec(true) + await got.post(`${origin}/bar`) - const { origin } = await servers.startHttpServer((request, response) => { - gotRequest() - response.writeHead(200) - response.end() - }) + // wait for the first request to end + await req1Promise - nock.recorder.rec({ - logging: loggingFn, - output_objects: true, + // validate only the request from the second session is in the outputs + const outputs = nock.recorder.play() + expect(outputs).to.have.lengthOf(1) + expect(outputs[0]).to.match(/\.post\('\/bar'\)/) }) - await got.post(origin) + it('when request port is different, use the alternate port', async () => { + nock.restore() + nock.recorder.clear() + nock.recorder.rec(true) - expect(gotRequest).to.have.been.calledOnce() - expect(loggingFn).to.have.been.calledOnce() - expect( - loggingFn - .getCall(0) - .args[0].startsWith( - '\n<<<<<<-- cut here -->>>>>>\n{\n "scope": "http://localhost:' - ) - ).to.be.true() -}) + const { origin, port } = await servers.startHttpServer() -it('records objects and correctly stores JSON object in body', async () => { - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + expect(port).not.to.equal(80) - const { origin } = await servers.startHttpServer((request, response) => - response.end() - ) + await got.post(origin) - nock.recorder.rec({ - dont_print: true, - output_objects: true, + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.include(`nock('http://localhost:${port}',`) }) - const exampleBody = { foo: 123 } - - await got.post(origin, { - headers: { 'content-type': 'application/json' }, - body: JSON.stringify(exampleBody), + it('recording turns off nock interception (backward compatibility behavior)', () => { + // We ensure that there are no overrides. + nock.restore() + expect(nock.isActive()).to.be.false() + // We active the nock overriding - as it's done by merely loading nock. + nock.activate() + expect(nock.isActive()).to.be.true() + // We start recording. + nock.recorder.rec() + // Nothing happens (nothing has been thrown) - which was the original behavior - + // and mocking has been deactivated. + expect(nock.isActive()).to.be.false() }) - nock.restore() - const recorded = nock.recorder.play() - nock.recorder.clear() - nock.activate() + it('records', async () => { + const gotRequest = sinon.spy() - expect(recorded).to.have.lengthOf(1) + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - // TODO See https://github.com/nock/nock/issues/1229 + const { origin, port } = await servers.startHttpServer( + (request, response) => { + gotRequest() + response.writeHead(200) + response.end() + } + ) - // This is the current behavior: store body as decoded JSON. - expect(recorded[0]).to.deep.include({ body: exampleBody }) + nock.recorder.rec(true) - // This is the desired behavior: store the body as encoded JSON. The second - // test shows desired behavior: store body as encoded JSON so that JSON - // strings can be correctly matched at runtime. Because headers are not - // stored in the recorder output, it is impossible for the loader to - // differentiate a stored JSON string from a non-JSON body. - // expect(recorded[0]).to.include({ body: JSON.stringify(exampleBody) }) -}) + await got.post(origin) -it('records and replays objects correctly', async () => { - const exampleText = 'example' - - const { origin } = await servers.startHttpServer((request, response) => { - switch (require('url').parse(request.url).pathname) { - case '/': - response.writeHead(302, { Location: '/abc' }) - break - case '/abc': - response.write(exampleText) - break - } - response.end() - }) + expect(gotRequest).to.have.been.calledOnce() - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + nock.restore() - nock.recorder.rec({ - dont_print: true, - output_objects: true, + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.be.a('string') + // TODO: Use chai-string? + expect( + recorded[0].startsWith( + `\nnock('http://localhost:${port}', {"encodedQueryParams":true})\n .post('/')` + ) + ).to.be.true() }) - const response1 = await got(origin) - expect(response1.body).to.equal(exampleText) + it('records objects', async () => { + const gotRequest = sinon.spy() + + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - nock.restore() - const recorded = nock.recorder.play() - nock.recorder.clear() - nock.activate() + const { origin } = await servers.startHttpServer((request, response) => { + gotRequest() + response.writeHead(200) + response.end() + }) - expect(recorded).to.have.lengthOf(2) - const nocks = nock.define(recorded) + nock.recorder.rec({ + dont_print: true, + output_objects: true, + }) - const response2 = await got(origin) - expect(response2.body).to.equal(exampleText) - nocks.forEach(nock => nock.done()) -}) + const requestBody = '0123455' + await got.post(origin, { body: requestBody }) -it('records and replays correctly with filteringRequestBody', async () => { - const responseBody = 'example' - const { origin } = await servers.startHttpServer((request, response) => { - response.write(responseBody) - response.end() + expect(gotRequest).to.have.been.calledOnce() + nock.restore() + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.include({ + scope: origin, + method: 'POST', + body: requestBody, + }) }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + it('logs recorded objects', async () => { + const gotRequest = sinon.spy() + const loggingFn = sinon.spy() - nock.recorder.rec({ - dont_print: true, - output_objects: true, - }) + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - const response1 = await got(origin) - expect(response1.body).to.equal(responseBody) - expect(response1.headers).to.be.ok() - - nock.restore() - const recorded = nock.recorder.play() - nock.recorder.clear() - nock.activate() - - expect(recorded).to.have.lengthOf(1) - const onFilteringRequestBody = sinon.spy() - const [definition] = recorded - definition.filteringRequestBody = (body, aRecodedBody) => { - onFilteringRequestBody() - expect(body).to.equal(aRecodedBody) - return body - } - const nocks = nock.define([definition]) - - const response2 = await got(origin) - expect(response2.body).to.equal(responseBody) - nocks.forEach(nock => nock.done()) - expect(onFilteringRequestBody).to.have.been.calledOnce() -}) + const { origin } = await servers.startHttpServer((request, response) => { + gotRequest() + response.writeHead(200) + response.end() + }) -// https://github.com/nock/nock/issues/29 -it('http request without callback should not crash', done => { - const serverFinished = sinon.spy() + nock.recorder.rec({ + logging: loggingFn, + output_objects: true, + }) - const requestListener = (request, response) => { - response.write('example') - response.end() - } + await got.post(origin) + + expect(gotRequest).to.have.been.calledOnce() + expect(loggingFn).to.have.been.calledOnce() + expect( + loggingFn + .getCall(0) + .args[0].startsWith( + '\n<<<<<<-- cut here -->>>>>>\n{\n "scope": "http://localhost:' + ) + ).to.be.true() + }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + it('records objects and correctly stores JSON object in body', async () => { + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - servers.startHttpServer(requestListener).then(({ port }) => { - nock.recorder.rec(true) - const req = http.request({ - host: 'localhost', - port, - method: 'GET', - path: '/', - }) + const { origin } = await servers.startHttpServer() - req.on('response', res => { - res.once('end', () => { - expect(serverFinished).to.have.been.calledOnce() - done() - }) + nock.recorder.rec({ + dont_print: true, + output_objects: true, }) - req.end() - serverFinished() - }) -}) + const exampleBody = { foo: 123 } -it('checks that data is specified', () => { - nock.restore() - nock.recorder.clear() - nock.recorder.rec(true) - - const req = http.request({ - method: 'POST', - host: 'localhost', - path: '/', - port: '80', - body: undefined, - }) + await got.post(origin, { + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(exampleBody), + }) - expect(() => req.write()).to.throw(Error, 'Data was undefined.') - req.abort() -}) + nock.restore() + const recorded = nock.recorder.play() + nock.recorder.clear() + nock.activate() -it('when request body is json, it goes unstringified', async () => { - const { origin } = await servers.startHttpServer((request, response) => - response.end() - ) + expect(recorded).to.have.lengthOf(1) - nock.restore() - nock.recorder.clear() - nock.recorder.rec(true) + // TODO See https://github.com/nock/nock/issues/1229 - const payload = { a: 1, b: true } + // This is the current behavior: store body as decoded JSON. + expect(recorded[0]).to.deep.include({ body: exampleBody }) - await got.post(origin, { body: JSON.stringify(payload) }) + // This is the desired behavior: store the body as encoded JSON. The second + // test shows desired behavior: store body as encoded JSON so that JSON + // strings can be correctly matched at runtime. Because headers are not + // stored in the recorder output, it is impossible for the loader to + // differentiate a stored JSON string from a non-JSON body. + // expect(recorded[0]).to.include({ body: JSON.stringify(exampleBody) }) + }) - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.include('.post(\'/\', {"a":1,"b":true})') -}) + it('records and replays objects correctly', async () => { + const exampleText = 'example' + + const { origin } = await servers.startHttpServer((request, response) => { + switch (require('url').parse(request.url).pathname) { + case '/': + response.writeHead(302, { Location: '/abc' }) + break + case '/abc': + response.write(exampleText) + break + } + response.end() + }) -it('when request body is json, it goes unstringified in objects', async () => { - const { origin } = await servers.startHttpServer((request, response) => - response.end() - ) + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - nock.restore() - nock.recorder.clear() - nock.recorder.rec({ dont_print: true, output_objects: true }) + nock.recorder.rec({ + dont_print: true, + output_objects: true, + }) - const payload = { a: 1, b: true } + const response1 = await got(origin) + expect(response1.body).to.equal(exampleText) - await got.post(origin, { body: JSON.stringify(payload) }) + nock.restore() + const recorded = nock.recorder.play() + nock.recorder.clear() + nock.activate() - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.be.an('object') - expect(recorded[0].body).to.be.an('object').and.deep.equal(payload) -}) + expect(recorded).to.have.lengthOf(2) + const nocks = nock.define(recorded) -it('records nonstandard ports', done => { - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + const response2 = await got(origin) + expect(response2.body).to.equal(exampleText) + nocks.forEach(nock => nock.done()) + }) - const requestBody = 'ABCDEF' - const responseBody = '012345' + it('records and replays correctly with filteringRequestBody', async () => { + const responseBody = 'example' + const { origin } = await servers.startHttpServer((request, response) => { + response.write(responseBody) + response.end() + }) - const requestListener = (req, res) => { - res.end(responseBody) - } + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - servers.startHttpServer(requestListener).then(({ origin, port }) => { nock.recorder.rec({ dont_print: true, output_objects: true, }) - // Confidence check that we have a non-standard port. - expect(port).to.be.greaterThan(8000) - const req = http.request( - { - host: 'localhost', - port, - path: '/', - }, - res => { - res.resume() - res.once('end', () => { - nock.restore() - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.be.an('object').and.include({ - scope: origin, - method: 'GET', - body: requestBody, - status: 200, - response: responseBody, - }) - done() - }) - } - ) - - req.end(requestBody) - }) -}) + const response1 = await got(origin) + expect(response1.body).to.equal(responseBody) + expect(response1.headers).to.be.ok() -it('`req.end()` accepts and calls a callback when recording', done => { - const onEnd = sinon.spy() + nock.restore() + const recorded = nock.recorder.play() + nock.recorder.clear() + nock.activate() + + expect(recorded).to.have.lengthOf(1) + const onFilteringRequestBody = sinon.spy() + const [definition] = recorded + definition.filteringRequestBody = (body, aRecodedBody) => { + onFilteringRequestBody() + expect(body).to.equal(aRecodedBody) + return body + } + const nocks = nock.define([definition]) - const requestListener = (request, response) => { - response.writeHead(200) - response.end() - } + const response2 = await got(origin) + expect(response2.body).to.equal(responseBody) + nocks.forEach(nock => nock.done()) + expect(onFilteringRequestBody).to.have.been.calledOnce() + }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + // https://github.com/nock/nock/issues/29 + it('http request without callback should not crash', done => { + const serverFinished = sinon.spy() - servers.startHttpServer(requestListener).then(({ port }) => { - nock.recorder.rec({ dont_print: true }) + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - const req = http.request( - { - hostname: 'localhost', + servers.startHttpServer().then(({ port }) => { + nock.recorder.rec(true) + const req = http.request({ + host: 'localhost', port, - path: '/', method: 'GET', - }, - res => { - expect(onEnd).to.have.been.calledOnce() - expect(res.statusCode).to.equal(200) - res.on('end', () => { + path: '/', + }) + + req.on('response', res => { + res.once('end', () => { + expect(serverFinished).to.have.been.calledOnce() done() }) res.resume() - } - ) + }) - req.end(onEnd) + req.end() + serverFinished() + }) }) -}) -// https://nodejs.org/api/http.html#http_request_end_data_encoding_callback -it('when recording, when `req.end()` is called with only data and a callback, the callback is invoked and the data is sent', done => { - const onEnd = sinon.spy() + it('checks that data is specified', () => { + nock.restore() + nock.recorder.clear() + nock.recorder.rec(true) - let requestBody = '' - const requestListener = (request, response) => { - request.on('data', data => { - requestBody += data - }) - request.on('end', () => { - response.writeHead(200) - response.end() + const req = http.request({ + method: 'POST', + host: 'localhost', + path: '/', + port: '80', + body: undefined, }) - } - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + expect(() => req.write()).to.throw(Error, 'Data was undefined.') + req.abort() + }) - servers.startHttpServer(requestListener).then(({ port }) => { - nock.recorder.rec({ dont_print: true }) + it('when request body is json, it goes unstringified', async () => { + const { origin } = await servers.startHttpServer() - const req = http.request( - { - hostname: 'localhost', - port, - path: '/', - method: 'POST', - }, - res => { - expect(onEnd).to.have.been.calledOnce() - expect(res.statusCode).to.equal(200) - - res.on('end', () => { - expect(requestBody).to.equal('foobar') - done() - }) - res.resume() - } - ) + nock.restore() + nock.recorder.clear() + nock.recorder.rec(true) - req.end('foobar', onEnd) - }) -}) + const payload = { a: 1, b: true } -it('`rec()` throws when reinvoked with already recorder requests', () => { - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + await got.post(origin, { body: JSON.stringify(payload) }) - nock.recorder.rec() - expect(() => nock.recorder.rec()).to.throw( - Error, - 'Nock recording already in progress' - ) -}) + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.include('.post(\'/\', {"a":1,"b":true})') + }) -it('records https correctly', done => { - const requestBody = '012345' - const responseBody = 'example' + it('when request body is json, it goes unstringified in objects', async () => { + const { origin } = await servers.startHttpServer() + + nock.restore() + nock.recorder.clear() + nock.recorder.rec({ dont_print: true, output_objects: true }) - const requestListener = (request, response) => { - response.write(responseBody) - response.end() - } + const payload = { a: 1, b: true } - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + await got.post(origin, { body: JSON.stringify(payload) }) - nock.recorder.rec({ - dont_print: true, - output_objects: true, + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.be.an('object') + expect(recorded[0].body).to.be.an('object').and.deep.equal(payload) }) - servers.startHttpsServer(requestListener).then(({ origin, port }) => { - https - .request( + it('records nonstandard ports', done => { + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() + + const requestBody = 'ABCDEF' + const responseBody = '012345' + + const requestListener = (req, res) => { + res.end(responseBody) + } + + servers.startHttpServer(requestListener).then(({ origin, port }) => { + nock.recorder.rec({ + dont_print: true, + output_objects: true, + }) + + // Confidence check that we have a non-standard port. + expect(port).to.be.greaterThan(8000) + const req = http.request( { - method: 'POST', host: 'localhost', port, path: '/', - rejectUnauthorized: false, }, res => { res.resume() @@ -566,9 +413,9 @@ it('records https correctly', done => { nock.restore() const recorded = nock.recorder.play() expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.be.an('object').and.to.include({ + expect(recorded[0]).to.be.an('object').and.include({ scope: origin, - method: 'POST', + method: 'GET', body: requestBody, status: 200, response: responseBody, @@ -577,719 +424,841 @@ it('records https correctly', done => { }) } ) - .end(requestBody) + + req.end(requestBody) + }) }) -}) -it('records request headers correctly as an object', done => { - const requestListener = (request, response) => response.end() + it('`req.end()` accepts and calls a callback when recording', done => { + const onEnd = sinon.spy() - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - servers.startHttpServer(requestListener).then(({ port }) => { - nock.recorder.rec({ - dont_print: true, - output_objects: true, - enable_reqheaders_recording: true, - }) + servers.startHttpServer().then(({ port }) => { + nock.recorder.rec({ dont_print: true }) - http - .request( + const req = http.request( { hostname: 'localhost', port, path: '/', method: 'GET', - auth: 'foo:bar', }, res => { - res.resume() - res.once('end', () => { - nock.restore() - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]) - .to.be.an('object') - .and.deep.include({ - reqheaders: { - host: `localhost:${port}`, - authorization: `Basic ${Buffer.from('foo:bar').toString( - 'base64' - )}`, - }, - }) + expect(onEnd).to.have.been.calledOnce() + expect(res.statusCode).to.equal(200) + res.on('end', () => { done() }) + res.resume() } ) - .end() - }) -}) -it('records request headers correctly when not outputting objects', async () => { - const gotRequest = sinon.spy() - - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() - - const { origin } = await servers.startHttpServer((request, response) => { - gotRequest() - response.writeHead(200) - response.end() + req.end(onEnd) + }) }) - nock.recorder.rec({ - dont_print: true, - enable_reqheaders_recording: true, - }) + // https://nodejs.org/api/http.html#http_request_end_data_encoding_callback + it('when recording, when `req.end()` is called with only data and a callback, the callback is invoked and the data is sent', done => { + const onEnd = sinon.spy() + + let requestBody = '' + const requestListener = (request, response) => { + request.on('data', data => { + requestBody += data + }) + request.on('end', () => { + response.writeHead(200) + response.end() + }) + } - await got.post(origin, { headers: { 'X-Foo': 'bar' } }) - expect(gotRequest).to.have.been.calledOnce() + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - nock.restore() + servers.startHttpServer(requestListener).then(({ port }) => { + nock.recorder.rec({ dont_print: true }) - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]) - .to.be.a('string') - .and.include(' .matchHeader("x-foo", "bar")') -}) + const req = http.request( + { + hostname: 'localhost', + port, + path: '/', + method: 'POST', + }, + res => { + expect(onEnd).to.have.been.calledOnce() + expect(res.statusCode).to.equal(200) -it('records and replays gzipped nocks correctly', async () => { - const exampleText = 'example' + res.on('end', () => { + expect(requestBody).to.equal('foobar') + done() + }) + res.resume() + } + ) - const { origin } = await servers.startHttpServer((request, response) => { - zlib.gzip(exampleText, (err, result) => { - expect(err).to.be.null() - response.writeHead(200, { 'content-encoding': 'gzip' }) - response.end(result) + req.end('foobar', onEnd) }) }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + it('`rec()` throws when reinvoked with already recorder requests', () => { + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - nock.recorder.rec({ - dont_print: true, - output_objects: true, + nock.recorder.rec() + expect(() => nock.recorder.rec()).to.throw( + Error, + 'Nock recording already in progress' + ) }) - const response1 = await got(origin) - expect(response1.body).to.equal(exampleText) - expect(response1.headers).to.include({ 'content-encoding': 'gzip' }) - - nock.restore() - const recorded = nock.recorder.play() - nock.recorder.clear() - nock.activate() - - expect(recorded).to.have.lengthOf(1) - const nocks = nock.define(recorded) + it('records https correctly', done => { + const requestBody = '012345' + const responseBody = 'example' - const response2 = await got(origin) - expect(response2.body).to.equal(exampleText) - expect(response2.headers).to.include({ 'content-encoding': 'gzip' }) - - nocks.forEach(nock => nock.done()) -}) - -it('records and replays the response body', async () => { - const exampleBody = 'example' - - const { origin } = await servers.startHttpServer((request, response) => { - switch (require('url').parse(request.url).pathname) { - case '/': - response.writeHead(302, { Location: '/abc' }) - break - case '/abc': - response.write(exampleBody) - break + const requestListener = (request, response) => { + response.write(responseBody) + response.end() } - response.end() - }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - nock.recorder.rec({ - dont_print: true, - output_objects: true, - }) + nock.recorder.rec({ + dont_print: true, + output_objects: true, + }) - const response1 = await got(origin) - expect(response1.body).to.equal(exampleBody) + servers.startHttpsServer(requestListener).then(({ origin, port }) => { + https + .request( + { + method: 'POST', + host: 'localhost', + port, + path: '/', + rejectUnauthorized: false, + }, + res => { + res.resume() + res.once('end', () => { + nock.restore() + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.be.an('object').and.to.include({ + scope: origin, + method: 'POST', + body: requestBody, + status: 200, + response: responseBody, + }) + done() + }) + } + ) + .end(requestBody) + }) + }) - nock.restore() - const recorded = nock.recorder.play() - nock.recorder.clear() - nock.activate() + it('records request headers correctly as an object', done => { + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - // Two requests, on account of the redirect. - expect(recorded).to.have.lengthOf(2) - const nocks = nock.define(recorded) + servers.startHttpServer().then(({ port }) => { + nock.recorder.rec({ + dont_print: true, + output_objects: true, + enable_reqheaders_recording: true, + }) - const response2 = await got(origin) - expect(response2.body).to.equal(exampleBody) - nocks.forEach(nock => nock.done()) -}) + http + .request( + { + hostname: 'localhost', + port, + path: '/', + method: 'GET', + auth: 'foo:bar', + }, + res => { + res.resume() + res.once('end', () => { + nock.restore() + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]) + .to.be.an('object') + .and.deep.include({ + reqheaders: { + host: `localhost:${port}`, + authorization: `Basic ${Buffer.from('foo:bar').toString( + 'base64' + )}`, + }, + }) + done() + }) + } + ) + .end() + }) + }) -it('when encoding is set during recording, body is still recorded correctly', done => { - const responseBody = 'example' + it('records request headers correctly when not outputting objects', async () => { + const gotRequest = sinon.spy() - const requestListener = (request, response) => { - response.write(responseBody) - response.end() - } + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + const { origin } = await servers.startHttpServer((request, response) => { + gotRequest() + response.writeHead(200) + response.end() + }) - servers.startHttpServer(requestListener).then(({ port }) => { nock.recorder.rec({ dont_print: true, - output_objects: true, + enable_reqheaders_recording: true, }) - const req = http.request( - { host: 'localhost', port, path: '/', method: 'POST' }, - res => { - res.setEncoding('hex') - - const hexChunks = [] - res.on('data', data => { - hexChunks.push(data) - }) - - res.on('end', () => { - nock.restore() - const recorded = nock.recorder.play() - nock.recorder.clear() - nock.activate() + await got.post(origin, { headers: { 'X-Foo': 'bar' } }) + expect(gotRequest).to.have.been.calledOnce() - // Confidence check: we're getting hex. - expect(hexChunks.join('')).to.equal( - Buffer.from(responseBody, 'utf8').toString('hex') - ) - - // Assert: we're recording utf-8. - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.include({ response: responseBody }) + nock.restore() - done() - }) - } - ) - req.end() + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]) + .to.be.a('string') + .and.include(' .matchHeader("x-foo", "bar")') }) -}) -it("doesn't record request headers by default", done => { - const requestListener = (request, response) => { - response.writeHead(200) - response.end() - } + it('records and replays gzipped nocks correctly', async () => { + const exampleText = 'example' - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + const { origin } = await servers.startHttpServer((request, response) => { + zlib.gzip(exampleText, (err, result) => { + expect(err).to.be.null() + response.writeHead(200, { 'content-encoding': 'gzip' }) + response.end(result) + }) + }) + + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - servers.startHttpServer(requestListener).then(({ port }) => { nock.recorder.rec({ dont_print: true, output_objects: true, }) - http - .request( - { - hostname: 'localhost', - port, - path: '/', - method: 'GET', - auth: 'foo:bar', - }, - res => { - res.resume() - res.once('end', () => { - nock.restore() - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.be.an('object') - expect(recorded[0].reqheaders).to.be.undefined() - done() - }) - } - ) - .end() - }) -}) + const response1 = await got(origin) + expect(response1.body).to.equal(exampleText) + expect(response1.headers).to.include({ 'content-encoding': 'gzip' }) -it('will call a custom logging function', done => { - const requestListener = (request, response) => { - response.writeHead(200) - response.end() - } - - // This also tests that use_separator is on by default. - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + nock.restore() + const recorded = nock.recorder.play() + nock.recorder.clear() + nock.activate() - servers.startHttpServer(requestListener).then(({ port }) => { - const loggingFn = sinon.spy() - nock.recorder.rec({ logging: loggingFn }) + expect(recorded).to.have.lengthOf(1) + const nocks = nock.define(recorded) - http - .request( - { - hostname: 'localhost', - port, - path: '/', - method: 'GET', - auth: 'foo:bar', - }, - res => { - res.resume() - res.once('end', () => { - nock.restore() + const response2 = await got(origin) + expect(response2.body).to.equal(exampleText) + expect(response2.headers).to.include({ 'content-encoding': 'gzip' }) - expect(loggingFn).to.have.been.calledOnce() - expect(loggingFn.getCall(0).args[0]).to.be.a('string') - done() - }) - } - ) - .end() + nocks.forEach(nock => nock.done()) }) -}) -it('use_separator:false is respected', done => { - const requestListener = (request, response) => { - response.writeHead(200) - response.end() - } + it('records and replays the response body', async () => { + const exampleBody = 'example' + + const { origin } = await servers.startHttpServer((request, response) => { + switch (require('url').parse(request.url).pathname) { + case '/': + response.writeHead(302, { Location: '/abc' }) + break + case '/abc': + response.write(exampleBody) + break + } + response.end() + }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - servers.startHttpServer(requestListener).then(({ port }) => { - const loggingFn = sinon.spy() nock.recorder.rec({ - logging: loggingFn, + dont_print: true, output_objects: true, - use_separator: false, }) - http - .request( - { - hostname: 'localhost', - port, - path: '/', - method: 'GET', - auth: 'foo:bar', - }, - res => { - res.resume() - res.once('end', () => { - nock.restore() - expect(loggingFn).to.have.been.calledOnce() - // This is still an object, because the "cut here" strings have not - // been appended. - expect(loggingFn.getCall(0).args[0]).to.be.an('object') - done() - }) - } - ) - .end() + const response1 = await got(origin) + expect(response1.body).to.equal(exampleBody) + + nock.restore() + const recorded = nock.recorder.play() + nock.recorder.clear() + nock.activate() + + // Two requests, on account of the redirect. + expect(recorded).to.have.lengthOf(2) + const nocks = nock.define(recorded) + + const response2 = await got(origin) + expect(response2.body).to.equal(exampleBody) + nocks.forEach(nock => nock.done()) }) -}) -it('records request headers except user-agent if enable_reqheaders_recording is set to true', done => { - const requestListener = (request, response) => { - response.writeHead(200) - response.end() - } + it('when encoding is set during recording, body is still recorded correctly', done => { + const responseBody = 'example' - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + const requestListener = (request, response) => { + response.write(responseBody) + response.end() + } - servers.startHttpServer(requestListener).then(({ port }) => { - nock.recorder.rec({ - dont_print: true, - output_objects: true, - enable_reqheaders_recording: true, - }) + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - http - .request( - { - hostname: 'localhost', - port, - path: '/', - method: 'GET', - auth: 'foo:bar', - }, + servers.startHttpServer(requestListener).then(({ port }) => { + nock.recorder.rec({ + dont_print: true, + output_objects: true, + }) + + const req = http.request( + { host: 'localhost', port, path: '/', method: 'POST' }, res => { - res.resume() - res.once('end', () => { + res.setEncoding('hex') + + const hexChunks = [] + res.on('data', data => { + hexChunks.push(data) + }) + + res.on('end', () => { nock.restore() const recorded = nock.recorder.play() + nock.recorder.clear() + nock.activate() + + // Confidence check: we're getting hex. + expect(hexChunks.join('')).to.equal( + Buffer.from(responseBody, 'utf8').toString('hex') + ) + + // Assert: we're recording utf-8. expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.be.an('object') - expect(recorded[0].reqheaders).to.be.an('object') - expect(recorded[0].reqheaders['user-agent']).to.be.undefined() + expect(recorded[0]).to.include({ response: responseBody }) + done() }) } ) - .end() + req.end() + }) }) -}) -it('records query parameters', async () => { - const { origin } = await servers.startHttpServer((request, response) => { - response.writeHead(200) - response.end() - }) + it("doesn't record request headers by default", done => { + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + servers.startHttpServer().then(({ port }) => { + nock.recorder.rec({ + dont_print: true, + output_objects: true, + }) - nock.recorder.rec({ - dont_print: true, - output_objects: true, + http + .request( + { + hostname: 'localhost', + port, + path: '/', + method: 'GET', + auth: 'foo:bar', + }, + res => { + res.resume() + res.once('end', () => { + nock.restore() + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.be.an('object') + expect(recorded[0].reqheaders).to.be.undefined() + done() + }) + } + ) + .end() + }) }) - await got(origin, { - searchParams: { q: 'test search' }, + it('will call a custom logging function', done => { + // This also tests that use_separator is on by default. + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() + + servers.startHttpServer().then(({ port }) => { + const loggingFn = sinon.spy() + nock.recorder.rec({ logging: loggingFn }) + + http + .request( + { + hostname: 'localhost', + port, + path: '/', + method: 'GET', + auth: 'foo:bar', + }, + res => { + res.resume() + res.once('end', () => { + nock.restore() + + expect(loggingFn).to.have.been.calledOnce() + expect(loggingFn.getCall(0).args[0]).to.be.a('string') + done() + }) + } + ) + .end() + }) }) - nock.restore() - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.include({ path: '/?q=test+search' }) -}) + it('use_separator:false is respected', done => { + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() + + servers.startHttpServer().then(({ port }) => { + const loggingFn = sinon.spy() + nock.recorder.rec({ + logging: loggingFn, + output_objects: true, + use_separator: false, + }) -it('encodes the query parameters when not outputting objects', async () => { - const { origin } = await servers.startHttpServer((request, response) => { - response.writeHead(200) - response.end() + http + .request( + { + hostname: 'localhost', + port, + path: '/', + method: 'GET', + auth: 'foo:bar', + }, + res => { + res.resume() + res.once('end', () => { + nock.restore() + expect(loggingFn).to.have.been.calledOnce() + // This is still an object, because the "cut here" strings have not + // been appended. + expect(loggingFn.getCall(0).args[0]).to.be.an('object') + done() + }) + } + ) + .end() + }) }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + it('records request headers except user-agent if enable_reqheaders_recording is set to true', done => { + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() + + servers.startHttpServer().then(({ port }) => { + nock.recorder.rec({ + dont_print: true, + output_objects: true, + enable_reqheaders_recording: true, + }) - nock.recorder.rec({ - dont_print: true, - output_objects: false, + http + .request( + { + hostname: 'localhost', + port, + path: '/', + method: 'GET', + auth: 'foo:bar', + }, + res => { + res.resume() + res.once('end', () => { + nock.restore() + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.be.an('object') + expect(recorded[0].reqheaders).to.be.an('object') + expect(recorded[0].reqheaders['user-agent']).to.be.undefined() + done() + }) + } + ) + .end() + }) }) - await got(origin, { - searchParams: { q: 'test search++' }, + it('records query parameters', async () => { + const { origin } = await servers.startHttpServer() + + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() + + nock.recorder.rec({ + dont_print: true, + output_objects: true, + }) + + await got(origin, { + searchParams: { q: 'test search' }, + }) + + nock.restore() + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.include({ path: '/?q=test+search' }) }) - nock.restore() - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.include('test%20search%2B%2B') -}) + it('encodes the query parameters when not outputting objects', async () => { + const { origin } = await servers.startHttpServer() -// https://github.com/nock/nock/issues/193 -it('works with clients listening for readable', done => { - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - const requestBody = 'ABCDEF' - const responseBody = '012345' + nock.recorder.rec({ + dont_print: true, + output_objects: false, + }) - const requestListener = (req, res) => { - res.end(responseBody) - } + await got(origin, { + searchParams: { q: 'test search++' }, + }) - servers.startHttpServer(requestListener).then(({ origin, port }) => { - nock.recorder.rec({ dont_print: true, output_objects: true }) + nock.restore() + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.include('test%20search%2B%2B') + }) - http - .request( - { - host: 'localhost', - port, - path: '/', - }, - res => { - let readableCount = 0 - let chunkCount = 0 - - res.on('readable', () => { - ++readableCount - let chunk - while ((chunk = res.read()) !== null) { - expect(chunk.toString()).to.equal(responseBody) - ++chunkCount - } - }) + // https://github.com/nock/nock/issues/193 + it('works with clients listening for readable', done => { + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - res.once('end', () => { - expect(readableCount).to.equal(1) - expect(chunkCount).to.equal(1) + const requestBody = 'ABCDEF' + const responseBody = '012345' - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.be.an('object').and.include({ - scope: origin, - method: 'GET', - body: requestBody, - status: 200, - response: responseBody, + const requestListener = (req, res) => { + res.end(responseBody) + } + + servers.startHttpServer(requestListener).then(({ origin, port }) => { + nock.recorder.rec({ dont_print: true, output_objects: true }) + + http + .request( + { + host: 'localhost', + port, + path: '/', + }, + res => { + let readableCount = 0 + let chunkCount = 0 + + res.on('readable', () => { + ++readableCount + let chunk + while ((chunk = res.read()) !== null) { + expect(chunk.toString()).to.equal(responseBody) + ++chunkCount + } }) - done() - }) - } - ) - .end(requestBody) - }) -}) -it('outputs query string parameters using query()', async () => { - const { origin } = await servers.startHttpServer((request, response) => { - response.writeHead(200) - response.end() + res.once('end', () => { + expect(readableCount).to.equal(1) + expect(chunkCount).to.equal(1) + + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]).to.be.an('object').and.include({ + scope: origin, + method: 'GET', + body: requestBody, + status: 200, + response: responseBody, + }) + done() + }) + } + ) + .end(requestBody) + }) }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + it('outputs query string parameters using query()', async () => { + const { origin } = await servers.startHttpServer() - nock.recorder.rec(true) + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - await got(origin, { - searchParams: { param1: 1, param2: 2 }, - }) + nock.recorder.rec(true) - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]) - .to.be.a('string') - .and.include(`.query({"param1":"1","param2":"2"})`) -}) + await got(origin, { + searchParams: { param1: 1, param2: 2 }, + }) -it('outputs query string arrays correctly', async () => { - const { origin } = await servers.startHttpServer((request, response) => { - response.writeHead(200) - response.end() + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]) + .to.be.a('string') + .and.include(`.query({"param1":"1","param2":"2"})`) }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + it('outputs query string arrays correctly', async () => { + const { origin } = await servers.startHttpServer() - nock.recorder.rec(true) + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() + + nock.recorder.rec(true) - await got(origin, { - searchParams: new URLSearchParams([ - ['foo', 'bar'], - ['foo', 'baz'], - ]), + await got(origin, { + searchParams: new URLSearchParams([ + ['foo', 'bar'], + ['foo', 'baz'], + ]), + }) + + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]) + .to.be.a('string') + .and.include(`.query({"foo":["bar","baz"]})`) }) - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]) - .to.be.a('string') - .and.include(`.query({"foo":["bar","baz"]})`) -}) + it('removes query params from the path and puts them in query()', done => { + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() + + servers.startHttpServer().then(({ port }) => { + nock.recorder.rec(true) + http + .request( + { + method: 'POST', + host: 'localhost', + port, + path: '/?param1=1¶m2=2', + }, + res => { + res.resume() + res.once('end', () => { + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]) + .to.be.a('string') + .and.include(`nock('http://localhost:${port}',`) + .and.include(`.query({"param1":"1","param2":"2"})`) + done() + }) + } + ) + .end('ABCDEF') + }) + }) -it('removes query params from the path and puts them in query()', done => { - const requestListener = (request, response) => { - response.writeHead(200) - response.end() - } + // https://github.com/nock/nock/issues/2136 + it('escapes single quotes in the path', async () => { + const { origin } = await servers.startHttpServer() - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() + nock.restore() + nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() - servers.startHttpServer(requestListener).then(({ port }) => { nock.recorder.rec(true) - http - .request( + + await got(`${origin}/foo'bar'baz`) + + const recorded = nock.recorder.play() + expect(recorded).to.have.lengthOf(1) + expect(recorded[0]) + .to.be.a('string') + .and.include(`.get('/foo\\'bar\\'baz')`) + }) + + it('respects http.request() consumers', done => { + const requestListener = (req, res) => { + res.write('foo') + setTimeout(() => { + res.end('bar') + }, 25) + } + + servers.startHttpServer(requestListener).then(({ port }) => { + nock.restore() + nock.recorder.clear() + nock.recorder.rec({ + dont_print: true, + output_objects: true, + }) + + const req = http.request( { - method: 'POST', host: 'localhost', port, - path: '/?param1=1¶m2=2', + path: '/', }, res => { - res.resume() - res.once('end', () => { - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]) - .to.be.a('string') - .and.include(`nock('http://localhost:${port}',`) - .and.include(`.query({"param1":"1","param2":"2"})`) - done() + let buffer = Buffer.from('') + + setTimeout(() => { + res + .on('data', data => { + buffer = Buffer.concat([buffer, data]) + }) + .on('end', () => { + nock.restore() + expect(buffer.toString()).to.equal('foobar') + done() + }) }) - } + }, + 50 ) - .end('ABCDEF') - }) -}) -// https://github.com/nock/nock/issues/2136 -it('escapes single quotes in the path', async () => { - const { origin } = await servers.startHttpServer((request, response) => { - response.writeHead(200) - response.end() + req.end() + }) }) - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() - - nock.recorder.rec(true) - - await got(`${origin}/foo'bar'baz`) - - const recorded = nock.recorder.play() - expect(recorded).to.have.lengthOf(1) - expect(recorded[0]).to.be.a('string').and.include(`.get('/foo\\'bar\\'baz')`) -}) - -it('respects http.request() consumers', done => { - const requestListener = (req, res) => { - res.write('foo') - setTimeout(() => { - res.end('bar') - }, 25) - } - - servers.startHttpServer(requestListener).then(({ port }) => { + it('records and replays binary response correctly', done => { nock.restore() nock.recorder.clear() + expect(nock.recorder.play()).to.be.empty() + nock.recorder.rec({ - dont_print: true, output_objects: true, + dont_print: true, }) - const req = http.request( - { - host: 'localhost', - port, - path: '/', - }, - res => { - let buffer = Buffer.from('') - - setTimeout(() => { - res - .on('data', data => { - buffer = Buffer.concat([buffer, data]) - }) - .on('end', () => { - nock.restore() - expect(buffer.toString()).to.equal('foobar') - done() - }) - }) - }, - 50 - ) - - req.end() - }) -}) - -it('records and replays binary response correctly', done => { - nock.restore() - nock.recorder.clear() - expect(nock.recorder.play()).to.be.empty() - - nock.recorder.rec({ - output_objects: true, - dont_print: true, - }) - - const transparentGifHex = - '47494638396101000100800000000000ffffff21f90401000000002c000000000100010000020144003b' - const transparentGifBuffer = Buffer.from(transparentGifHex, 'hex') + const transparentGifHex = + '47494638396101000100800000000000ffffff21f90401000000002c000000000100010000020144003b' + const transparentGifBuffer = Buffer.from(transparentGifHex, 'hex') - const requestListener = (request, response) => { - response.writeHead(201, { - 'Content-Type': 'image/gif', - 'Content-Length': transparentGifBuffer.length, - }) - response.write(transparentGifBuffer, 'binary') - response.end() - } - - servers.startHttpServer(requestListener).then(server => { - const options = { - method: 'PUT', - host: 'localhost', - port: server.port, - path: '/clear.gif', - headers: { + const requestListener = (request, response) => { + response.writeHead(201, { 'Content-Type': 'image/gif', 'Content-Length': transparentGifBuffer.length, - }, + }) + response.write(transparentGifBuffer, 'binary') + response.end() } - const postRequest1 = http.request(options, response => { - const data = [] + servers.startHttpServer(requestListener).then(server => { + const options = { + method: 'PUT', + host: 'localhost', + port: server.port, + path: '/clear.gif', + headers: { + 'Content-Type': 'image/gif', + 'Content-Length': transparentGifBuffer.length, + }, + } - response.on('data', chunk => { - data.push(chunk) - }) + const postRequest1 = http.request(options, response => { + const data = [] - response.on('end', () => { - expect(Buffer.concat(data).toString('hex')).to.equal(transparentGifHex) + response.on('data', chunk => { + data.push(chunk) + }) - const recordedFixtures = nock.recorder.play() + response.on('end', () => { + expect(Buffer.concat(data).toString('hex')).to.equal( + transparentGifHex + ) - server.close(error => { - server = undefined - expect(error).to.be.undefined() + const recordedFixtures = nock.recorder.play() - nock.restore() - nock.activate() - nock.define(recordedFixtures) + server.close(error => { + server = undefined + expect(error).to.be.undefined() - // Send same post request again. - const postRequest2 = http.request(options, response => { - const data = [] + nock.restore() + nock.activate() + nock.define(recordedFixtures) - response.on('data', chunk => { - data.push(chunk) - }) + // Send same post request again. + const postRequest2 = http.request(options, response => { + const data = [] - response.on('end', () => { - expect(Buffer.concat(data).toString('hex')).to.equal( - transparentGifHex - ) - done() + response.on('data', chunk => { + data.push(chunk) + }) + + response.on('end', () => { + expect(Buffer.concat(data).toString('hex')).to.equal( + transparentGifHex + ) + done() + }) }) - }) - postRequest2.write(transparentGifBuffer) - postRequest2.end() + postRequest2.write(transparentGifBuffer) + postRequest2.end() + }) }) }) + + postRequest1.write(transparentGifBuffer) + postRequest1.end() }) + }) + + // https://github.com/nock/nock/issues/2086 + it('should not resume the response stream', done => { + nock.recorder.rec(true) - postRequest1.write(transparentGifBuffer) - postRequest1.end() + servers.startHttpServer().then(({ origin }) => { + const req = http.request(origin) + + req.on('response', res => { + // wait for an iteration of the event loop to prove that the `end` + // listener is being added after a delay. We want to show that callers + // have time to register listeners before they manually call `resume`. + setImmediate(() => { + res.on('end', () => done()) + res.resume() + }) + }) + + req.end() + }) }) })