diff --git a/README.md b/README.md index b233c13a1..8435fffe9 100644 --- a/README.md +++ b/README.md @@ -1471,6 +1471,8 @@ To set the mode call `nockBack.setMode(mode)` or run the tests with the `NOCK_BA - record: use recorded nocks, record new nocks +- update: remove recorded nocks, record nocks + - lockdown: use recorded nocks, disables all http calls even when not nocked, doesn't record ## Common issues diff --git a/lib/back.js b/lib/back.js index dc1e72fbe..191f88d1e 100644 --- a/lib/back.js +++ b/lib/back.js @@ -175,6 +175,47 @@ const record = { }, } +const update = { + setup: function () { + recorder.restore() + recorder.clear() + cleanAll() + activate() + disableNetConnect() + }, + + start: function (fixture, options) { + if (!fs) { + throw new Error('no fs') + } + const context = removeFixture(fixture) + recorder.record({ + dont_print: true, + output_objects: true, + ...options.recorder, + }) + + context.isRecording = true + + return context + }, + + finish: function (fixture, options, context) { + let outputs = recorder.outputs() + + if (typeof options.afterRecord === 'function') { + outputs = options.afterRecord(outputs) + } + + outputs = + typeof outputs === 'string' ? outputs : JSON.stringify(outputs, null, 4) + debug('recorder outputs:', outputs) + + fs.mkdirSync(path.dirname(fixture), { recursive: true }) + fs.writeFileSync(fixture, outputs) + }, +} + const lockdown = { setup: function () { recorder.restore() @@ -215,6 +256,19 @@ function load(fixture, options) { return context } +function removeFixture(fixture, options) { + const context = { + scopes: [], + assertScopesFinished: function () {}, + } + + if (fixture && fixtureExists(fixture)) { + fs.rmSync ? fs.rmSync(fixture) : fs.unlinkSync(fixture) + } + context.isLoaded = false + return context +} + function applyHook(scopes, fn) { if (!fn) { return @@ -258,6 +312,8 @@ const Modes = { record, // use recorded nocks, record new nocks + update, // allow http calls, record all nocks, don't use recorded nocks + lockdown, // use recorded nocks, disables all http calls even when not nocked, doesnt record } diff --git a/package.json b/package.json index e74b89c34..e7d965aba 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "lint:js": "eslint --cache --cache-location './.cache/eslint' '**/*.js'", "lint:js:fix": "eslint --cache --cache-location './.cache/eslint' --fix '**/*.js'", "lint:ts": "dtslint types", - "test": "nyc mocha tests", + "test": "nyc --reporter=lcov --reporter=text mocha tests", "test:coverage": "open coverage/lcov-report/index.html" }, "license": "MIT", diff --git a/tests/test_back.js b/tests/test_back.js index 479da233d..9685bf0b3 100644 --- a/tests/test_back.js +++ b/tests/test_back.js @@ -482,6 +482,270 @@ describe('Nock Back', () => { }) }) + describe('update mode', () => { + let fixture + let fixtureLoc + let fixturePath + + beforeEach(() => { + // random fixture file so tests don't interfere with each other + const token = crypto.randomBytes(4).toString('hex') + fixture = `temp_${token}.json` + fixtureLoc = path.resolve(__dirname, 'fixtures', fixture) + fixturePath = path.resolve(__dirname, 'fixtures') + nockBack.setMode('update') + fs.copyFileSync( + path.resolve(fixturePath, 'wrong_uri.json'), + path.resolve(fixturePath, 'temp_wrong_uri.json') + ) + }) + + after(() => { + rimraf.sync(path.resolve(__dirname, 'fixtures', 'temp_*.json')) + }) + + it('should record when configured correctly', done => { + expect(fs.existsSync(fixtureLoc)).to.be.false() + + nockBack(fixture, nockDone => { + startHttpServer(requestListener).then(server => { + const request = http.request( + { + host: 'localhost', + path: '/', + port: server.address().port, + }, + response => { + nockDone() + + expect(response.statusCode).to.equal(217) + expect(fs.existsSync(fixtureLoc)).to.be.true() + done() + } + ) + + request.on('error', () => expect.fail()) + request.end() + }) + }) + }) + + it('should record the expected data', done => { + nockBack(fixture, nockDone => { + startHttpServer(requestListener).then(server => { + const request = http.request( + { + host: 'localhost', + path: '/', + port: server.address().port, + method: 'GET', + }, + response => { + response.once('end', () => { + nockDone() + + const fixtureContent = JSON.parse( + fs.readFileSync(fixtureLoc).toString('utf8') + ) + expect(fixtureContent).to.have.length(1) + + const [firstFixture] = fixtureContent + expect(firstFixture).to.include({ + method: 'GET', + path: '/', + status: 217, + }) + + done() + }) + + response.resume() + } + ) + + request.on('error', err => expect.fail(err.message)) + request.end() + }) + }) + }) + + // Adding this test because there was an issue when not calling + // nock.activate() after calling nock.restore(). + it('can record twice', done => { + expect(fs.existsSync(fixtureLoc)).to.be.false() + + nockBack(fixture, function (nockDone) { + startHttpServer(requestListener).then(server => { + const request = http.request( + { + host: 'localhost', + path: '/', + port: server.address().port, + }, + response => { + nockDone() + + expect(response.statusCode).to.equal(217) + expect(fs.existsSync(fixtureLoc)).to.be.true() + done() + } + ) + + request.on('error', () => expect.fail()) + request.end() + }) + }) + }) + + it('should allow outside calls', done => { + nockBack('temp_wrong_uri.json', nockDone => { + startHttpServer(requestListener).then(server => { + const request = http.request( + { + host: 'localhost', + path: '/', + port: server.address().port, + }, + response => { + nockDone() + expect(response.statusCode).to.equal(217) + expect( + fs.existsSync(`${fixturePath}/temp_wrong_uri.json`) + ).to.be.true() + done() + } + ) + + request.on('error', () => expect.fail()) + request.end() + }) + }) + }) + + it("shouldn't load recorded tests", done => { + fs.copyFileSync( + path.resolve(fixturePath, 'good_request.json'), + path.resolve(fixturePath, 'temp_good_request.json') + ) + nockBack('temp_good_request.json', function (nockDone) { + expect(this.scopes).to.have.lengthOf.at.least(0) + http + .get('http://www.example.test/', () => { + expect.fail() + }) + .on('error', () => { + nockDone() + done() + }) + }) + }) + + it('should filter after recording', done => { + expect(fs.existsSync(fixtureLoc)).to.be.false() + + // You would do some filtering here, but for this test we'll just return + // an empty array. + const afterRecord = () => [] + + nockBack(fixture, { afterRecord }, function (nockDone) { + startHttpServer(requestListener).then(server => { + const request = http.request( + { + host: 'localhost', + path: '/', + port: server.address().port, + }, + response => { + nockDone() + + expect(response.statusCode).to.equal(217) + expect(fs.existsSync(fixtureLoc)).to.be.true() + expect(this.scopes).to.be.empty() + done() + } + ) + request.on('error', () => expect.fail()) + request.end() + }) + }) + }) + + it('should format after recording', done => { + expect(fs.existsSync(fixtureLoc)).to.be.false() + + const afterRecord = () => 'string-response' + + nockBack(fixture, { afterRecord }, function (nockDone) { + startHttpServer(requestListener).then(server => { + const request = http.request( + { + host: 'localhost', + path: '/', + port: server.address().port, + }, + response => { + nockDone() + + expect(response.statusCode).to.equal(217) + expect(fs.existsSync(fixtureLoc)).to.be.true() + expect(fs.readFileSync(fixtureLoc, 'utf8')).to.equal( + 'string-response' + ) + done() + } + ) + request.on('error', () => expect.fail()) + request.end() + }) + }) + }) + + it('should pass custom options to recorder', done => { + nockBack( + fixture, + { recorder: { enable_reqheaders_recording: true } }, + nockDone => { + startHttpServer(requestListener).then(server => { + const request = http.request( + { + host: 'localhost', + path: '/', + port: server.address().port, + method: 'GET', + }, + response => { + response.once('end', () => { + nockDone() + + const fixtureContent = JSON.parse( + fs.readFileSync(fixtureLoc).toString('utf8') + ) + + expect(fixtureContent).to.have.length(1) + expect(fixtureContent[0].reqheaders).to.be.ok() + + done() + }) + response.resume() + } + ) + + request.on('error', () => expect.fail()) + request.end() + }) + } + ) + }) + + it('should throw the expected exception when fs is not available', () => { + const nockBackWithoutFs = proxyquire('../lib/back', { fs: null }) + nockBackWithoutFs.setMode('update') + + nockBackWithoutFs.fixtures = path.resolve(__dirname, 'fixtures') + expect(() => nockBackWithoutFs('good_request.json')).to.throw('no fs') + }) + }) + describe('lockdown mode', () => { beforeEach(() => { nockBack.setMode('lockdown')