UNPKG

nock

Version:

HTTP server mocking and expectations library for Node.js

372 lines (271 loc) • 9.18 kB
import fs from 'node:fs' import assert from 'node:assert' import * as recorder from './recorder.js' import { activate, disableNetConnect, enableNetConnect, removeAll as cleanAll, } from './intercept.js' import { loadDefs, define } from './scope.js' import { back as debug } from './debug.js' import { format } from 'node:util' import path from 'node:path' let _mode = null function Back( fixtureName , options , nockedFn , ) { if (!Back.fixtures) { throw new Error( 'Back requires nock.back.fixtures to be set\n' + 'Ex:\n' + "\trequire(nock).back.fixtures = '/path/to/fixtures/'", ) } if (typeof fixtureName !== 'string') { throw new Error('Parameter fixtureName must be a string') } if (arguments.length === 1) { options = {} } else if (arguments.length === 2) { // If 2nd parameter is a function then `options` has been omitted // otherwise `options` haven't been omitted but `nockedFn` was. if (typeof options === 'function') { nockedFn = options options = {} } } ;(_mode ).setup() const fixture = path.join(Back.fixtures , fixtureName) const context = (_mode ).start(fixture, options) const nockDone = function () { ;(_mode ).finish(fixture, options, context) } debug('context:', context) // If nockedFn is a function then invoke it, otherwise return a promise resolving to nockDone. if (typeof nockedFn === 'function') { nockedFn.call(context, nockDone) } else { return Promise.resolve({ nockDone, context }) } } /******************************************************************************* * Modes * *******************************************************************************/ const wild = { setup: function () { cleanAll() recorder.restore() activate() enableNetConnect() }, start: function () { return load(undefined, undefined) // don't load anything but get correct context }, finish: function () { // nothing to do }, } const dryrun = { setup: function () { recorder.restore() cleanAll() activate() // We have to explicitly enable net connectivity as by default it's off. enableNetConnect() }, start: function (fixture , options ) { const contexts = load(fixture, options) enableNetConnect() return contexts }, finish: function () { // nothing to do }, } const record = { setup: function () { recorder.restore() recorder.clear() cleanAll() activate() disableNetConnect() }, start: function (fixture , options ) { if (!fs) { throw new Error('no fs') } const context = load(fixture, options) if (!context.isLoaded) { recorder.record({ dont_print: true, output_objects: true, ...options.recorder, }) context.isRecording = true } return context }, finish: function ( fixture , options , context , ) { if (context.isRecording) { let outputs = recorder.outputs() if (typeof options.afterRecord === 'function') { outputs = options.afterRecord(outputs) } const data = typeof outputs === 'string' ? outputs : JSON.stringify(outputs, null, 4) debug('recorder outputs:', data) fs.mkdirSync(path.dirname(fixture), { recursive: true }) fs.writeFileSync(fixture, data) } }, } 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 ) { let outputs = recorder.outputs() if (typeof options.afterRecord === 'function') { outputs = options.afterRecord(outputs) } const data = typeof outputs === 'string' ? outputs : JSON.stringify(outputs, null, 4) debug('recorder outputs:', data) fs.mkdirSync(path.dirname(fixture), { recursive: true }) fs.writeFileSync(fixture, data) }, } const lockdown = { setup: function () { recorder.restore() recorder.clear() cleanAll() activate() disableNetConnect() }, start: function (fixture , options ) { return load(fixture, options) }, finish: function () { // nothing to do }, } function load(fixture , options ) { const context = { isLoaded: false, isRecording: false, scopes: [] , assertScopesFinished: function () { assertScopes(this.scopes, fixture) }, query: function () { return this.scopes.flatMap((scope ) => scope.interceptors.map((interceptor ) => ({ method: interceptor.method, uri: interceptor.uri, basePath: interceptor.basePath, path: interceptor.path, queries: interceptor.queries, counter: interceptor.counter, body: interceptor.body, statusCode: interceptor.statusCode, optional: interceptor.optional, })), ) }, } if (fixture && fixtureExists(fixture)) { let scopes = loadDefs(fixture) applyHook(scopes, options?.before) scopes = define(scopes) applyHook(scopes, options?.after) context.scopes = scopes context.isLoaded = true } return context } function removeFixture(fixture ) { const context = { scopes: [] , assertScopesFinished: () => {}, isLoaded: false, isRecording: false, } if (fixture && fixtureExists(fixture)) { /* istanbul ignore next - fs.unlinkSync is for node 10 support */ fs.rmSync ? fs.rmSync(fixture) : fs.unlinkSync(fixture) } return context } function applyHook(items , fn ) { if (!fn) { return } if (typeof fn !== 'function') { throw new Error('processing hooks must be a function') } items.forEach(fn ) } function fixtureExists(fixture ) { if (!fs) { throw new Error('no fs') } return fs.existsSync(fixture) } function assertScopes(scopes , fixture ) { const pending = scopes .filter((scope ) => !scope.isDone()) .map((scope ) => scope.pendingMocks()) if (pending.length) { assert.fail( format( '%j was not used, consider removing %s to rerecord fixture', ([] ).concat(...pending), fixture, ), ) } } const Modes = { wild, // all requests go out to the internet, dont replay anything, doesnt record anything dryrun, // use recorded nocks, allow http calls, doesnt record anything, useful for writing new tests (default) 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 } Back.setMode = function (mode ) { if (!(mode in Modes)) { throw new Error(`Unknown mode: ${mode}`) } Back.currentMode = mode debug('New nock back mode:', Back.currentMode) _mode = Modes[mode ] _mode.setup() } Back.fixtures = null Back.currentMode = '' export default Back