From 968e124856314127e30bfd7ca4ceecba97eadb30 Mon Sep 17 00:00:00 2001 From: Luke Karrys Date: Wed, 30 Nov 2022 14:40:21 -0700 Subject: [PATCH] chore: add option for strict mock registry (#5902) --- mock-registry/lib/index.js | 81 ++++++++++++++++++++++-------- smoke-tests/test/fixtures/setup.js | 7 +-- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/mock-registry/lib/index.js b/mock-registry/lib/index.js index 1652e3344f0a8..24666dde24a5d 100644 --- a/mock-registry/lib/index.js +++ b/mock-registry/lib/index.js @@ -10,6 +10,7 @@ class MockRegistry { #authorization #basic #debug + #strict constructor (opts) { if (!opts.registry) { @@ -19,20 +20,48 @@ class MockRegistry { this.#authorization = opts.authorization this.#basic = opts.basic this.#debug = opts.debug + this.#strict = opts.strict // Required for this.package this.#tap = opts.tap + if (this.#tap) { + this.startNock() + } } - static tnock (t, host, opts, { debug = false } = {}) { - if (debug) { - Nock.emitter.on('no match', req => console.error('NO MATCH', req.options)) + static tnock (t, host, opts, { debug = false, strict = false } = {}) { + const noMatch = (req) => { + if (strict) { + // There are network requests that get caught regardless of error code. + // Turning on strict mode requires that those requests get explicitly + // mocked with a 404, 500, etc. + // XXX: this is opt-in currently because it breaks some existing CLI + // tests. We should work towards making this the default for all tests. + t.fail(`Unmatched request: ${JSON.stringify(req.options, null, 2)}`) + } + if (debug) { + console.error('NO MATCH', t.name, req.options) + } } + + Nock.emitter.on('no match', noMatch) Nock.disableNetConnect() const server = Nock(host, opts) + + if (strict) { + // this requires that mocks not be shared between sub tests but it helps + // find mistakes quicker instead of waiting for the entire test to end + t.afterEach((t) => { + t.strictSame(server.pendingMocks(), [], 'no pending mocks after each') + t.strictSame(server.activeMocks(), [], 'no active mocks after each') + }) + } + t.teardown(() => { Nock.enableNetConnect() server.done() + Nock.emitter.off('no match', noMatch) }) + return server } @@ -41,24 +70,6 @@ class MockRegistry { } get nock () { - if (!this.#nock) { - if (!this.#tap) { - throw new Error('cannot mock packages without a tap fixture') - } - const reqheaders = {} - if (this.#authorization) { - reqheaders.authorization = `Bearer ${this.#authorization}` - } - if (this.#basic) { - reqheaders.authorization = `Basic ${this.#basic}` - } - this.#nock = MockRegistry.tnock( - this.#tap, - this.#registry, - { reqheaders }, - { debug: this.#debug } - ) - } return this.#nock } @@ -66,6 +77,31 @@ class MockRegistry { this.#nock = nock } + startNock () { + if (this.nock) { + return + } + + if (!this.#tap) { + throw new Error('cannot mock packages without a tap fixture') + } + + const reqheaders = {} + if (this.#authorization) { + reqheaders.authorization = `Bearer ${this.#authorization}` + } + if (this.#basic) { + reqheaders.authorization = `Basic ${this.#basic}` + } + + this.nock = MockRegistry.tnock( + this.#tap, + this.#registry, + { reqheaders }, + { debug: this.#debug, strict: this.#strict } + ) + } + search ({ responseCode = 200, results = [], error }) { // the flags, score, and searchScore parts of the response are never used // by npm, only package is used @@ -296,13 +332,14 @@ class MockRegistry { manifest.users = users } for (const packument of packuments) { + const unscoped = name.includes('/') ? name.split('/')[1] : name manifest.versions[packument.version] = { _id: `${name}@${packument.version}`, name, description: 'test package mock manifest', dependencies: {}, dist: { - tarball: `${this.#registry}/${name}/-/${name}-${packument.version}.tgz`, + tarball: `${this.#registry}/${name}/-/${unscoped}-${packument.version}.tgz`, }, maintainers: [], ...packument, diff --git a/smoke-tests/test/fixtures/setup.js b/smoke-tests/test/fixtures/setup.js index 4291006b435e0..f59c2cae60ff7 100644 --- a/smoke-tests/test/fixtures/setup.js +++ b/smoke-tests/test/fixtures/setup.js @@ -84,6 +84,7 @@ module.exports = async (t, { testdir = {}, debug } = {}) => { tap: t, registry: 'http://smoke-test-registry.club/', debug, + strict: true, }) const httpProxyRegistry = `http://localhost:${PORT}` const proxy = httpProxy.createProxyServer({}) @@ -92,12 +93,8 @@ module.exports = async (t, { testdir = {}, debug } = {}) => { t.teardown(() => server.close()) // update notifier should never be written - t.afterEach(async (t) => { + t.afterEach((t) => { t.equal(existsSync(join(paths.cache, '_update-notifier-last-checked')), false) - // this requires that mocks not be shared between sub tests but it helps - // find mistakes quicker instead of waiting for the entire test to end - t.strictSame(registry.nock.pendingMocks(), [], 'no pending mocks after each') - t.strictSame(registry.nock.activeMocks(), [], 'no active mocks after each') }) const debugLog = debug || CI ? (...a) => console.error(...a) : () => {}