diff options
Diffstat (limited to 'test/lib')
70 files changed, 3072 insertions, 2669 deletions
diff --git a/test/lib/auth/legacy.js b/test/lib/auth/legacy.js index 7b61e9f6e..0c23f8ba6 100644 --- a/test/lib/auth/legacy.js +++ b/test/lib/auth/legacy.js @@ -6,7 +6,7 @@ const token = '24528a24f240' const profile = {} const read = {} const legacy = t.mock('../../../lib/auth/legacy.js', { - npmlog: { + 'proc-log': { info: (...msgs) => { log += msgs.join(' ') }, diff --git a/test/lib/auth/sso.js b/test/lib/auth/sso.js index d59220559..473c8cc24 100644 --- a/test/lib/auth/sso.js +++ b/test/lib/auth/sso.js @@ -11,7 +11,7 @@ const SSO_URL = 'https://registry.npmjs.org/{SSO_URL}' const profile = {} const npmFetch = {} const sso = t.mock('../../../lib/auth/sso.js', { - npmlog: { + 'proc-log': { info: (...msgs) => { log += msgs.join(' ') + '\n' }, diff --git a/test/lib/cli.js b/test/lib/cli.js index d762943b4..f02c57d8c 100644 --- a/test/lib/cli.js +++ b/test/lib/cli.js @@ -1,176 +1,153 @@ const t = require('tap') -const { real: mockNpm } = require('../fixtures/mock-npm.js') - -const unsupportedMock = { - checkForBrokenNode: () => {}, - checkForUnsupportedNode: () => {}, -} - -let exitHandlerCalled = null -let exitHandlerNpm = null -let exitHandlerCb -const exitHandlerMock = (...args) => { - exitHandlerCalled = args - if (exitHandlerCb) { - exitHandlerCb() +const mockGlobals = require('../fixtures/mock-globals.js') +const { load: loadMockNpm } = require('../fixtures/mock-npm.js') + +const cliMock = async (t, mocks) => { + let exitHandlerArgs = null + let npm = null + const exitHandlerMock = (...args) => { + exitHandlerArgs = args + npm.unload() } -} -exitHandlerMock.setNpm = npm => { - exitHandlerNpm = npm -} - -const logs = [] -const npmlogMock = { - pause: () => logs.push('pause'), - verbose: (...msg) => logs.push(['verbose', ...msg]), - info: (...msg) => logs.push(['info', ...msg]), -} + exitHandlerMock.setNpm = _npm => npm = _npm -const cliMock = Npm => - t.mock('../../lib/cli.js', { + const { Npm, outputs, logMocks, logs } = await loadMockNpm(t, { mocks, init: false }) + const cli = t.mock('../../lib/cli.js', { '../../lib/npm.js': Npm, '../../lib/utils/update-notifier.js': async () => null, - '../../lib/utils/unsupported.js': unsupportedMock, + '../../lib/utils/unsupported.js': { + checkForBrokenNode: () => {}, + checkForUnsupportedNode: () => {}, + }, '../../lib/utils/exit-handler.js': exitHandlerMock, - npmlog: npmlogMock, + ...logMocks, }) -const processMock = proc => { - const mocked = { - ...process, - on: () => {}, - ...proc, + return { + Npm, + cli, + outputs, + exitHandlerCalled: () => exitHandlerArgs, + exitHandlerNpm: () => npm, + logs, } - // nopt looks at process directly - process.argv = mocked.argv - return mocked } -const { argv } = process - t.afterEach(() => { - logs.length = 0 - process.argv = argv - exitHandlerCalled = null - exitHandlerNpm = null + delete process.exitCode }) t.test('print the version, and treat npm_g as npm -g', async t => { - const proc = processMock({ - argv: ['node', 'npm_g', '-v'], - version: process.version, + mockGlobals(t, { + 'process.argv': ['node', 'npm_g', '-v'], }) - const { Npm, outputs } = mockNpm(t) - const cli = cliMock(Npm) - await cli(proc) + const { logs, cli, Npm, outputs, exitHandlerCalled } = await cliMock(t) + await cli(process) - t.strictSame(proc.argv, ['node', 'npm', '-g', '-v'], 'npm process.argv was rewritten') t.strictSame(process.argv, ['node', 'npm', '-g', '-v'], 'system process.argv was rewritten') - t.strictSame(logs, [ - 'pause', - ['verbose', 'cli', proc.argv], - ['info', 'using', 'npm@%s', Npm.version], - ['info', 'using', 'node@%s', process.version], + t.strictSame(logs.verbose.filter(([p]) => p !== 'logfile'), [ + ['cli', process.argv], + ]) + t.strictSame(logs.info, [ + ['using', 'npm@%s', Npm.version], + ['using', 'node@%s', process.version], ]) t.strictSame(outputs, [[Npm.version]]) - t.strictSame(exitHandlerCalled, []) + t.strictSame(exitHandlerCalled(), []) }) t.test('calling with --versions calls npm version with no args', async t => { - t.plan(5) - const proc = processMock({ - argv: ['node', 'npm', 'install', 'or', 'whatever', '--versions'], + t.plan(6) + mockGlobals(t, { + 'process.argv': ['node', 'npm', 'install', 'or', 'whatever', '--versions'], }) - const { Npm, outputs } = mockNpm(t, { + const { logs, cli, Npm, outputs, exitHandlerCalled } = await cliMock(t, { '../../lib/commands/version.js': class Version { async exec (args) { t.strictSame(args, []) } }, }) - const cli = cliMock(Npm) - await cli(proc) - t.equal(proc.title, 'npm') - t.strictSame(logs, [ - 'pause', - ['verbose', 'cli', proc.argv], - ['info', 'using', 'npm@%s', Npm.version], - ['info', 'using', 'node@%s', process.version], + + await cli(process) + t.equal(process.title, 'npm install or whatever') + t.strictSame(logs.verbose.filter(([p]) => p !== 'logfile'), [ + ['cli', process.argv], + ]) + t.strictSame(logs.info, [ + ['using', 'npm@%s', Npm.version], + ['using', 'node@%s', process.version], ]) t.strictSame(outputs, []) - t.strictSame(exitHandlerCalled, []) + t.strictSame(exitHandlerCalled(), []) }) t.test('logged argv is sanitized', async t => { - const proc = processMock({ - argv: [ + mockGlobals(t, { + 'process.argv': [ 'node', 'npm', 'version', 'https://username:password@npmjs.org/test_url_with_a_password', ], }) - const { Npm } = mockNpm(t, { + const { logs, cli, Npm } = await cliMock(t, { '../../lib/commands/version.js': class Version { async exec (args) {} }, }) - const cli = cliMock(Npm) - - await cli(proc) - t.equal(proc.title, 'npm') - t.strictSame(logs, [ - 'pause', + await cli(process) + t.ok(process.title.startsWith('npm version https://username:***@npmjs.org')) + t.strictSame(logs.verbose.filter(([p]) => p !== 'logfile'), [ [ - 'verbose', 'cli', ['node', 'npm', 'version', 'https://username:***@npmjs.org/test_url_with_a_password'], ], - ['info', 'using', 'npm@%s', Npm.version], - ['info', 'using', 'node@%s', process.version], + ]) + t.strictSame(logs.info, [ + ['using', 'npm@%s', Npm.version], + ['using', 'node@%s', process.version], ]) }) t.test('print usage if no params provided', async t => { - const proc = processMock({ - argv: ['node', 'npm'], + mockGlobals(t, { + 'process.argv': ['node', 'npm'], }) - const { Npm, outputs } = mockNpm(t) - const cli = cliMock(Npm) - await cli(proc) + const { cli, outputs, exitHandlerCalled, exitHandlerNpm } = await cliMock(t) + await cli(process) t.match(outputs[0][0], 'Usage:', 'outputs npm usage') - t.match(exitHandlerCalled, [], 'should call exitHandler with no args') - t.ok(exitHandlerNpm, 'exitHandler npm is set') - t.match(proc.exitCode, 1) + t.match(exitHandlerCalled(), [], 'should call exitHandler with no args') + t.ok(exitHandlerNpm(), 'exitHandler npm is set') + t.match(process.exitCode, 1) }) t.test('print usage if non-command param provided', async t => { - const proc = processMock({ - argv: ['node', 'npm', 'tset'], + mockGlobals(t, { + 'process.argv': ['node', 'npm', 'tset'], }) - const { Npm, outputs } = mockNpm(t) - const cli = cliMock(Npm) - await cli(proc) + const { cli, outputs, exitHandlerCalled, exitHandlerNpm } = await cliMock(t) + await cli(process) t.match(outputs[0][0], 'Unknown command: "tset"') t.match(outputs[0][0], 'Did you mean this?') - t.match(exitHandlerCalled, [], 'should call exitHandler with no args') - t.ok(exitHandlerNpm, 'exitHandler npm is set') - t.match(proc.exitCode, 1) + t.match(exitHandlerCalled(), [], 'should call exitHandler with no args') + t.ok(exitHandlerNpm(), 'exitHandler npm is set') + t.match(process.exitCode, 1) }) t.test('load error calls error handler', async t => { - const proc = processMock({ - argv: ['node', 'npm', 'asdf'], + mockGlobals(t, { + 'process.argv': ['node', 'npm', 'asdf'], }) const err = new Error('test load error') - const { Npm } = mockNpm(t, { + const { cli, exitHandlerCalled } = await cliMock(t, { '../../lib/utils/config/index.js': { definitions: null, flatten: null, @@ -182,7 +159,6 @@ t.test('load error calls error handler', async t => { } }, }) - const cli = cliMock(Npm) - await cli(proc) - t.strictSame(exitHandlerCalled, [err]) + await cli(process) + t.strictSame(exitHandlerCalled(), [err]) }) diff --git a/test/lib/commands/access.js b/test/lib/commands/access.js index fdf132aff..298897e4f 100644 --- a/test/lib/commands/access.js +++ b/test/lib/commands/access.js @@ -1,18 +1,9 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm.js') - -const { Npm } = mockNpm(t) -const npm = new Npm() - -const prefix = t.testdir({}) - -t.before(async () => { - await npm.load() - npm.prefix = prefix -}) +const { load: loadMockNpm } = require('../../fixtures/mock-npm.js') t.test('completion', async t => { + const { npm } = await loadMockNpm(t) const access = await npm.cmd('access') const testComp = (argv, expect) => { const res = access.completion({ conf: { argv: { remain: argv } } }) @@ -42,6 +33,7 @@ t.test('completion', async t => { }) t.test('subcommand required', async t => { + const { npm } = await loadMockNpm(t) const access = await npm.cmd('access') await t.rejects( npm.exec('access', []), @@ -50,6 +42,7 @@ t.test('subcommand required', async t => { }) t.test('unrecognized subcommand', async t => { + const { npm } = await loadMockNpm(t) await t.rejects( npm.exec('access', ['blerg']), /Usage: blerg is not a recognized subcommand/, @@ -58,6 +51,7 @@ t.test('unrecognized subcommand', async t => { }) t.test('edit', async t => { + const { npm } = await loadMockNpm(t) await t.rejects( npm.exec('access', ['edit', '@scoped/another']), /edit subcommand is not implemented yet/, @@ -66,15 +60,13 @@ t.test('edit', async t => { }) t.test('access public on unscoped package', async t => { - t.teardown(() => { - npm.prefix = prefix - }) - const testdir = t.testdir({ - 'package.json': JSON.stringify({ - name: 'npm-access-public-pkg', - }), + const { npm } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: 'npm-access-public-pkg', + }), + }, }) - npm.prefix = testdir await t.rejects( npm.exec('access', ['public']), /Usage: This command is only available for scoped packages/, @@ -84,30 +76,30 @@ t.test('access public on unscoped package', async t => { t.test('access public on scoped package', async t => { t.plan(2) - const { Npm } = mockNpm(t, { - libnpmaccess: { - public: (pkg, { registry }) => { - t.equal(pkg, name, 'should use pkg name ref') - t.equal( - registry, - 'https://registry.npmjs.org/', - 'should forward correct options' - ) - return true + const name = '@scoped/npm-access-public-pkg' + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + public: (pkg, { registry }) => { + t.equal(pkg, name, 'should use pkg name ref') + t.equal( + registry, + 'https://registry.npmjs.org/', + 'should forward correct options' + ) + return true + }, }, }, + testdir: { + 'package.json': JSON.stringify({ name }), + }, }) - const npm = new Npm() - await npm.load() - const name = '@scoped/npm-access-public-pkg' - const testdir = t.testdir({ - 'package.json': JSON.stringify({ name }), - }) - npm.prefix = testdir await npm.exec('access', ['public']) }) t.test('access public on missing package.json', async t => { + const { npm } = await loadMockNpm(t) await t.rejects( npm.exec('access', ['public']), /no package name passed to command and no package.json found/, @@ -116,14 +108,12 @@ t.test('access public on missing package.json', async t => { }) t.test('access public on invalid package.json', async t => { - t.teardown(() => { - npm.prefix = prefix - }) - const testdir = t.testdir({ - 'package.json': '{\n', - node_modules: {}, + const { npm } = await loadMockNpm(t, { + testdir: { + 'package.json': '{\n', + node_modules: {}, + }, }) - npm.prefix = testdir await t.rejects( npm.exec('access', ['public']), { code: 'EJSONPARSE' }, @@ -132,15 +122,13 @@ t.test('access public on invalid package.json', async t => { }) t.test('access restricted on unscoped package', async t => { - t.teardown(() => { - npm.prefix = prefix - }) - const testdir = t.testdir({ - 'package.json': JSON.stringify({ - name: 'npm-access-restricted-pkg', - }), + const { npm } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: 'npm-access-restricted-pkg', + }), + }, }) - npm.prefix = testdir await t.rejects( npm.exec('access', ['public']), /Usage: This command is only available for scoped packages/, @@ -150,30 +138,30 @@ t.test('access restricted on unscoped package', async t => { t.test('access restricted on scoped package', async t => { t.plan(2) - const { Npm } = mockNpm(t, { - libnpmaccess: { - restricted: (pkg, { registry }) => { - t.equal(pkg, name, 'should use pkg name ref') - t.equal( - registry, - 'https://registry.npmjs.org/', - 'should forward correct options' - ) - return true + const name = '@scoped/npm-access-restricted-pkg' + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + restricted: (pkg, { registry }) => { + t.equal(pkg, name, 'should use pkg name ref') + t.equal( + registry, + 'https://registry.npmjs.org/', + 'should forward correct options' + ) + return true + }, }, }, + testdir: { + 'package.json': JSON.stringify({ name }), + }, }) - const npm = new Npm() - await npm.load() - const name = '@scoped/npm-access-restricted-pkg' - const testdir = t.testdir({ - 'package.json': JSON.stringify({ name }), - }) - npm.prefix = testdir await npm.exec('access', ['restricted']) }) t.test('access restricted on missing package.json', async t => { + const { npm } = await loadMockNpm(t) await t.rejects( npm.exec('access', ['restricted']), /no package name passed to command and no package.json found/, @@ -182,14 +170,12 @@ t.test('access restricted on missing package.json', async t => { }) t.test('access restricted on invalid package.json', async t => { - t.teardown(() => { - npm.prefix = prefix - }) - const testdir = t.testdir({ - 'package.json': '{\n', - node_modules: {}, + const { npm } = await loadMockNpm(t, { + testdir: { + 'package.json': '{\n', + node_modules: {}, + }, }) - npm.prefix = testdir await t.rejects( npm.exec('access', ['restricted']), { code: 'EJSONPARSE' }, @@ -199,17 +185,18 @@ t.test('access restricted on invalid package.json', async t => { t.test('access grant read-only', async t => { t.plan(3) - const { Npm } = mockNpm(t, { - libnpmaccess: { - grant: (spec, team, permissions) => { - t.equal(spec, '@scoped/another', 'should use expected spec') - t.equal(team, 'myorg:myteam', 'should use expected team') - t.equal(permissions, 'read-only', 'should forward permissions') - return true + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + grant: (spec, team, permissions) => { + t.equal(spec, '@scoped/another', 'should use expected spec') + t.equal(team, 'myorg:myteam', 'should use expected team') + t.equal(permissions, 'read-only', 'should forward permissions') + return true + }, }, }, }) - const npm = new Npm() await npm.exec('access', [ 'grant', 'read-only', @@ -220,17 +207,18 @@ t.test('access grant read-only', async t => { t.test('access grant read-write', async t => { t.plan(3) - const { Npm } = mockNpm(t, { - libnpmaccess: { - grant: (spec, team, permissions) => { - t.equal(spec, '@scoped/another', 'should use expected spec') - t.equal(team, 'myorg:myteam', 'should use expected team') - t.equal(permissions, 'read-write', 'should forward permissions') - return true + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + grant: (spec, team, permissions) => { + t.equal(spec, '@scoped/another', 'should use expected spec') + t.equal(team, 'myorg:myteam', 'should use expected team') + t.equal(permissions, 'read-write', 'should forward permissions') + return true + }, }, }, }) - const npm = new Npm() await npm.exec('access', [ 'grant', 'read-write', @@ -241,24 +229,23 @@ t.test('access grant read-write', async t => { t.test('access grant current cwd', async t => { t.plan(3) - const testdir = t.testdir({ - 'package.json': JSON.stringify({ - name: 'yargs', - }), - }) - const { Npm } = mockNpm(t, { - libnpmaccess: { - grant: (spec, team, permissions) => { - t.equal(spec, 'yargs', 'should use expected spec') - t.equal(team, 'myorg:myteam', 'should use expected team') - t.equal(permissions, 'read-write', 'should forward permissions') - return true + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + grant: (spec, team, permissions) => { + t.equal(spec, 'yargs', 'should use expected spec') + t.equal(team, 'myorg:myteam', 'should use expected team') + t.equal(permissions, 'read-write', 'should forward permissions') + return true + }, }, }, + testdir: { + 'package.json': JSON.stringify({ + name: 'yargs', + }), + }, }) - const npm = new Npm() - await npm.load() - npm.prefix = testdir await npm.exec('access', [ 'grant', 'read-write', @@ -267,6 +254,7 @@ t.test('access grant current cwd', async t => { }) t.test('access grant others', async t => { + const { npm } = await loadMockNpm(t) await t.rejects( npm.exec('access', [ 'grant', @@ -280,6 +268,7 @@ t.test('access grant others', async t => { }) t.test('access grant missing team args', async t => { + const { npm } = await loadMockNpm(t) await t.rejects( npm.exec('access', [ 'grant', @@ -293,6 +282,7 @@ t.test('access grant missing team args', async t => { }) t.test('access grant malformed team arg', async t => { + const { npm } = await loadMockNpm(t) await t.rejects( npm.exec('access', [ 'grant', @@ -307,36 +297,37 @@ t.test('access grant malformed team arg', async t => { t.test('access 2fa-required/2fa-not-required', async t => { t.plan(2) - const { Npm } = mockNpm(t, { - libnpmaccess: { - tfaRequired: (spec) => { - t.equal(spec, '@scope/pkg', 'should use expected spec') - return true - }, - tfaNotRequired: (spec) => { - t.equal(spec, 'unscoped-pkg', 'should use expected spec') - return true + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + tfaRequired: (spec) => { + t.equal(spec, '@scope/pkg', 'should use expected spec') + return true + }, + tfaNotRequired: (spec) => { + t.equal(spec, 'unscoped-pkg', 'should use expected spec') + return true + }, }, }, }) - const npm = new Npm() - await npm.exec('access', ['2fa-required', '@scope/pkg']) await npm.exec('access', ['2fa-not-required', 'unscoped-pkg']) }) t.test('access revoke', async t => { t.plan(2) - const { Npm } = mockNpm(t, { - libnpmaccess: { - revoke: (spec, team) => { - t.equal(spec, '@scoped/another', 'should use expected spec') - t.equal(team, 'myorg:myteam', 'should use expected team') - return true + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + revoke: (spec, team) => { + t.equal(spec, '@scoped/another', 'should use expected spec') + t.equal(team, 'myorg:myteam', 'should use expected team') + return true + }, }, }, }) - const npm = new Npm() await npm.exec('access', [ 'revoke', 'myorg:myteam', @@ -345,6 +336,7 @@ t.test('access revoke', async t => { }) t.test('access revoke missing team args', async t => { + const { npm } = await loadMockNpm(t) await t.rejects( npm.exec('access', [ 'revoke', @@ -357,6 +349,7 @@ t.test('access revoke missing team args', async t => { }) t.test('access revoke malformed team arg', async t => { + const { npm } = await loadMockNpm(t) await t.rejects( npm.exec('access', [ 'revoke', @@ -370,30 +363,32 @@ t.test('access revoke malformed team arg', async t => { t.test('npm access ls-packages with no team', async t => { t.plan(1) - const { Npm } = mockNpm(t, { - libnpmaccess: { - lsPackages: (entity) => { - t.equal(entity, 'foo', 'should use expected entity') - return {} + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + lsPackages: (entity) => { + t.equal(entity, 'foo', 'should use expected entity') + return {} + }, }, + '../../lib/utils/get-identity.js': () => Promise.resolve('foo'), }, - '../../lib/utils/get-identity.js': () => Promise.resolve('foo'), }) - const npm = new Npm() await npm.exec('access', ['ls-packages']) }) t.test('access ls-packages on team', async t => { t.plan(1) - const { Npm } = mockNpm(t, { - libnpmaccess: { - lsPackages: (entity) => { - t.equal(entity, 'myorg:myteam', 'should use expected entity') - return {} + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + lsPackages: (entity) => { + t.equal(entity, 'myorg:myteam', 'should use expected entity') + return {} + }, }, }, }) - const npm = new Npm() await npm.exec('access', [ 'ls-packages', 'myorg:myteam', @@ -402,36 +397,36 @@ t.test('access ls-packages on team', async t => { t.test('access ls-collaborators on current', async t => { t.plan(1) - const testdir = t.testdir({ - 'package.json': JSON.stringify({ - name: 'yargs', - }), - }) - const { Npm } = mockNpm(t, { - libnpmaccess: { - lsCollaborators: (spec) => { - t.equal(spec, 'yargs', 'should use expected spec') - return {} + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + lsCollaborators: (spec) => { + t.equal(spec, 'yargs', 'should use expected spec') + return {} + }, }, }, + testdir: { + 'package.json': JSON.stringify({ + name: 'yargs', + }), + }, }) - const npm = new Npm() - await npm.load() - npm.prefix = testdir await npm.exec('access', ['ls-collaborators']) }) t.test('access ls-collaborators on spec', async t => { t.plan(1) - const { Npm } = mockNpm(t, { - libnpmaccess: { - lsCollaborators: (spec) => { - t.equal(spec, 'yargs', 'should use expected spec') - return {} + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmaccess: { + lsCollaborators: (spec) => { + t.equal(spec, 'yargs', 'should use expected spec') + return {} + }, }, }, }) - const npm = new Npm() await npm.exec('access', [ 'ls-collaborators', 'yargs', diff --git a/test/lib/commands/adduser.js b/test/lib/commands/adduser.js index 71d79ea93..8a9358f9a 100644 --- a/test/lib/commands/adduser.js +++ b/test/lib/commands/adduser.js @@ -20,6 +20,13 @@ const authDummy = (npm, options) => { throw new Error('did not pass full flatOptions to auth function') } + if (!options.log) { + // A quick to test to make sure a log gets passed to auth + // XXX: should be refactored with change to real mock npm + // https://github.com/npm/statusboard/issues/411 + throw new Error('pass log to auth') + } + return Promise.resolve({ message: 'success', newCreds: { @@ -71,6 +78,8 @@ const AddUser = t.mock('../../../lib/commands/adduser.js', { npmlog: { clearProgress: () => null, disableProgress: () => null, + }, + 'proc-log': { notice: (_, msg) => { registryOutput = msg }, diff --git a/test/lib/commands/audit.js b/test/lib/commands/audit.js index 3c87c76a8..05f268d6b 100644 --- a/test/lib/commands/audit.js +++ b/test/lib/commands/audit.js @@ -1,5 +1,5 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: _loadMockNpm } = require('../../fixtures/mock-npm') t.test('should audit using Arborist', async t => { let ARB_ARGS = null @@ -8,36 +8,35 @@ t.test('should audit using Arborist', async t => { let AUDIT_REPORT_CALLED = false let ARB_OBJ = null - const { Npm, outputs } = mockNpm(t, { - 'npm-audit-report': () => { - AUDIT_REPORT_CALLED = true - return { - report: 'there are vulnerabilities', - exitCode: 0, - } - }, - '@npmcli/arborist': function (args) { - ARB_ARGS = args - ARB_OBJ = this - this.audit = () => { - AUDIT_CALLED = true - this.auditReport = {} - } - }, - '../../lib/utils/reify-finish.js': (npm, arb) => { - if (arb !== ARB_OBJ) { - throw new Error('got wrong object passed to reify-output') - } + const loadMockNpm = (t) => _loadMockNpm(t, { + mocks: { + 'npm-audit-report': () => { + AUDIT_REPORT_CALLED = true + return { + report: 'there are vulnerabilities', + exitCode: 0, + } + }, + '@npmcli/arborist': function (args) { + ARB_ARGS = args + ARB_OBJ = this + this.audit = () => { + AUDIT_CALLED = true + this.auditReport = {} + } + }, + '../../lib/utils/reify-finish.js': (npm, arb) => { + if (arb !== ARB_OBJ) { + throw new Error('got wrong object passed to reify-output') + } - REIFY_FINISH_CALLED = true + REIFY_FINISH_CALLED = true + }, }, }) - const npm = new Npm() - await npm.load() - npm.prefix = t.testdir() - t.test('audit', async t => { + const { npm, outputs } = await loadMockNpm(t) await npm.exec('audit', []) t.match(ARB_ARGS, { audit: true, path: npm.prefix }) t.equal(AUDIT_CALLED, true, 'called audit') @@ -46,6 +45,7 @@ t.test('should audit using Arborist', async t => { }) t.test('audit fix', async t => { + const { npm } = await loadMockNpm(t) await npm.exec('audit', ['fix']) t.equal(REIFY_FINISH_CALLED, true, 'called reify output') }) @@ -53,69 +53,67 @@ t.test('should audit using Arborist', async t => { t.test('should audit - json', async t => { t.plan(1) - const { Npm } = mockNpm(t, { - 'npm-audit-report': (_, opts) => { - t.match(opts.reporter, 'json') - return { - report: 'there are vulnerabilities', - exitCode: 0, - } + const { npm } = await _loadMockNpm(t, { + mocks: { + 'npm-audit-report': (_, opts) => { + t.match(opts.reporter, 'json') + return { + report: 'there are vulnerabilities', + exitCode: 0, + } + }, + '@npmcli/arborist': function () { + this.audit = () => { + this.auditReport = {} + } + }, + '../../lib/utils/reify-output.js': () => {}, }, - '@npmcli/arborist': function () { - this.audit = () => { - this.auditReport = {} - } + config: { + json: true, }, - '../../lib/utils/reify-output.js': () => {}, }) - const npm = new Npm() - await npm.load() - npm.prefix = t.testdir() - npm.config.set('json', true) await npm.exec('audit', []) }) t.test('report endpoint error', async t => { - const { Npm, outputs, filteredLogs } = mockNpm(t, { - 'npm-audit-report': () => { - throw new Error('should not call audit report when there are errors') - }, - '@npmcli/arborist': function () { - this.audit = () => { - this.auditReport = { - error: { - message: 'hello, this didnt work', - method: 'POST', - uri: 'https://example.com/', - headers: { - head: ['ers'], + const loadMockNpm = (t, options) => _loadMockNpm(t, { + mocks: { + 'npm-audit-report': () => { + throw new Error('should not call audit report when there are errors') + }, + '@npmcli/arborist': function () { + this.audit = () => { + this.auditReport = { + error: { + message: 'hello, this didnt work', + method: 'POST', + uri: 'https://example.com/', + headers: { + head: ['ers'], + }, + statusCode: 420, + body: 'this is a string', }, - statusCode: 420, - body: 'this is a string', - // body: json ? { nope: 'lol' } : Buffer.from('i had a vuln but i eated it lol'), - }, + } } - } + }, + '../../lib/utils/reify-output.js': () => {}, }, - '../../lib/utils/reify-output.js': () => {}, + ...options, }) - const npm = new Npm() - await npm.load() - npm.prefix = t.testdir() - // npm.config.set('json', ) + t.test('json=false', async t => { + const { npm, outputs, logs } = await loadMockNpm(t, { config: { json: false } }) await t.rejects(npm.exec('audit', []), 'audit endpoint returned an error') - t.match(filteredLogs('warn'), ['hello, this didnt work']) + t.match(logs.warn, [['audit', 'hello, this didnt work']]) t.strictSame(outputs, [['this is a string']]) }) t.test('json=true', async t => { - t.teardown(() => { - npm.config.set('json', false) - }) - npm.config.set('json', true) + const { npm, outputs, logs } = await loadMockNpm(t, { config: { json: true } }) await t.rejects(npm.exec('audit', []), 'audit endpoint returned an error') - t.match(filteredLogs('warn'), ['hello, this didnt work']) + t.match(logs.warn, [['audit', 'hello, this didnt work']]) t.strictSame(outputs, [[ '{\n' + ' "message": "hello, this didnt work",\n' + @@ -135,8 +133,7 @@ t.test('report endpoint error', async t => { }) t.test('completion', async t => { - const { Npm } = mockNpm(t) - const npm = new Npm() + const { npm } = await _loadMockNpm(t) const audit = await npm.cmd('audit') t.test('fix', async t => { await t.resolveMatch( diff --git a/test/lib/commands/birthday.js b/test/lib/commands/birthday.js index 8c95dd57b..9156d3df0 100644 --- a/test/lib/commands/birthday.js +++ b/test/lib/commands/birthday.js @@ -1,14 +1,15 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') t.test('birthday', async t => { t.plan(2) - const { Npm } = mockNpm(t, { - libnpmexec: ({ args, yes }) => { - t.ok(yes) - t.match(args, ['@npmcli/npm-birthday']) + const { npm } = await loadMockNpm(t, { + mocks: { + libnpmexec: ({ args, yes }) => { + t.ok(yes) + t.match(args, ['@npmcli/npm-birthday']) + }, }, }) - const npm = new Npm() await npm.exec('birthday', []) }) diff --git a/test/lib/commands/cache.js b/test/lib/commands/cache.js index 70a8ba1b2..fc92facff 100644 --- a/test/lib/commands/cache.js +++ b/test/lib/commands/cache.js @@ -12,11 +12,6 @@ const rimraf = (path, cb) => { } let logOutput = [] -const npmlog = { - silly: (...args) => { - logOutput.push(['silly', ...args]) - }, -} let tarballStreamSpec = '' let tarballStreamOpts = {} @@ -141,9 +136,16 @@ const cacache = { const Cache = t.mock('../../../lib/commands/cache.js', { cacache, - npmlog, pacote, rimraf, + 'proc-log': { + silly: (...args) => { + logOutput.push(['silly', ...args]) + }, + warn: (...args) => { + logOutput.push(['warn', ...args]) + }, + }, }) const npm = mockNpm({ @@ -153,11 +155,6 @@ const npm = mockNpm({ output: (msg) => { outputOutput.push(msg) }, - log: { - warn: (...args) => { - logOutput.push(['warn', ...args]) - }, - }, }) const cache = new Cache(npm) diff --git a/test/lib/commands/ci.js b/test/lib/commands/ci.js index 1091f9125..537d0784f 100644 --- a/test/lib/commands/ci.js +++ b/test/lib/commands/ci.js @@ -159,7 +159,7 @@ t.test('should throw if package-lock.json or npm-shrinkwrap missing', async t => const CI = t.mock('../../../lib/commands/ci.js', { '@npmcli/run-script': opts => {}, '../../../lib/utils/reify-finish.js': async () => {}, - npmlog: { + 'proc-log': { verbose: () => { t.ok(true, 'log fn called') }, diff --git a/test/lib/commands/completion.js b/test/lib/commands/completion.js index 51212f06d..dd571baf7 100644 --- a/test/lib/commands/completion.js +++ b/test/lib/commands/completion.js @@ -6,189 +6,153 @@ const completionScript = fs .readFileSync(path.resolve(__dirname, '../../../lib/utils/completion.sh'), { encoding: 'utf8' }) .replace(/^#!.*?\n/, '') -const { real: mockNpm } = require('../../fixtures/mock-npm') - -const { Npm, outputs } = mockNpm(t, { - '../../lib/utils/is-windows-shell.js': false, -}) -const npm = new Npm() +const { load: _loadMockNpm } = require('../../fixtures/mock-npm') +const mockGlobals = require('../../fixtures/mock-globals') + +const loadMockCompletion = async (t, o = {}) => { + const { globals, windows, ...options } = o + let resetGlobals = {} + if (globals) { + resetGlobals = mockGlobals(t, globals).reset + } + const res = await _loadMockNpm(t, { + mocks: { + '../../lib/utils/is-windows-shell.js': !!windows, + ...options.mocks, + }, + ...options, + }) + const completion = await res.npm.cmd('completion') + return { + resetGlobals, + completion, + ...res, + } +} + +const loadMockCompletionComp = async (t, word, line) => + loadMockCompletion(t, { + globals: { + 'process.env.COMP_CWORD': word, + 'process.env.COMP_LINE': line, + 'process.env.COMP_POINT': line.length, + }, + }) t.test('completion', async t => { - const completion = await npm.cmd('completion') t.test('completion completion', async t => { - const home = process.env.HOME - t.teardown(() => { - process.env.HOME = home - }) - - process.env.HOME = t.testdir({ - '.bashrc': '', - '.zshrc': '', + const { outputs, completion, prefix } = await loadMockCompletion(t, { + testdir: { + '.bashrc': 'aaa', + '.zshrc': 'aaa', + }, }) + mockGlobals(t, { 'process.env.HOME': prefix }) await completion.completion({ w: 2 }) t.matchSnapshot(outputs, 'both shells') }) t.test('completion completion no known shells', async t => { - const home = process.env.HOME - t.teardown(() => { - process.env.HOME = home - }) - - process.env.HOME = t.testdir() + const { outputs, completion, prefix } = await loadMockCompletion(t) + mockGlobals(t, { 'process.env.HOME': prefix }) await completion.completion({ w: 2 }) t.matchSnapshot(outputs, 'no responses') }) t.test('completion completion wrong word count', async t => { + const { outputs, completion } = await loadMockCompletion(t) + await completion.completion({ w: 3 }) t.matchSnapshot(outputs, 'no responses') }) t.test('dump script when completion is not being attempted', async t => { - const _write = process.stdout.write - const _on = process.stdout.on - t.teardown(() => { - process.stdout.write = _write - process.stdout.on = _on + let errorHandler, data + const { completion, resetGlobals } = await loadMockCompletion(t, { + globals: { + 'process.stdout.on': (event, handler) => { + errorHandler = handler + resetGlobals['process.stdout.on']() + }, + 'process.stdout.write': (chunk, callback) => { + data = chunk + process.nextTick(() => { + callback() + errorHandler({ errno: 'EPIPE' }) + }) + resetGlobals['process.stdout.write']() + }, + }, }) - let errorHandler - process.stdout.on = (event, handler) => { - errorHandler = handler - process.stdout.on = _on - } - - let data - process.stdout.write = (chunk, callback) => { - data = chunk - process.stdout.write = _write - process.nextTick(() => { - callback() - errorHandler({ errno: 'EPIPE' }) - }) - } - await completion.exec({}) - t.equal(data, completionScript, 'wrote the completion script') }) t.test('dump script exits correctly when EPIPE is emitted on stdout', async t => { - const _write = process.stdout.write - const _on = process.stdout.on - t.teardown(() => { - process.stdout.write = _write - process.stdout.on = _on + let errorHandler, data + const { completion, resetGlobals } = await loadMockCompletion(t, { + globals: { + 'process.stdout.on': (event, handler) => { + if (event === 'error') { + errorHandler = handler + } + resetGlobals['process.stdout.on']() + }, + 'process.stdout.write': (chunk, callback) => { + data = chunk + process.nextTick(() => { + errorHandler({ errno: 'EPIPE' }) + callback() + }) + resetGlobals['process.stdout.write']() + }, + }, }) - let errorHandler - process.stdout.on = (event, handler) => { - errorHandler = handler - process.stdout.on = _on - } - - let data - process.stdout.write = (chunk, callback) => { - data = chunk - process.stdout.write = _write - process.nextTick(() => { - errorHandler({ errno: 'EPIPE' }) - callback() - }) - } - await completion.exec({}) t.equal(data, completionScript, 'wrote the completion script') }) t.test('single command name', async t => { - process.env.COMP_CWORD = 1 - process.env.COMP_LINE = 'npm conf' - process.env.COMP_POINT = process.env.COMP_LINE.length - - t.teardown(() => { - delete process.env.COMP_CWORD - delete process.env.COMP_LINE - delete process.env.COMP_POINT - }) + const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm conf') await completion.exec(['npm', 'conf']) t.matchSnapshot(outputs, 'single command name') }) t.test('multiple command names', async t => { - process.env.COMP_CWORD = 1 - process.env.COMP_LINE = 'npm a' - process.env.COMP_POINT = process.env.COMP_LINE.length - - t.teardown(() => { - delete process.env.COMP_CWORD - delete process.env.COMP_LINE - delete process.env.COMP_POINT - }) + const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm a') await completion.exec(['npm', 'a']) t.matchSnapshot(outputs, 'multiple command names') }) t.test('completion of invalid command name does nothing', async t => { - process.env.COMP_CWORD = 1 - process.env.COMP_LINE = 'npm compute' - process.env.COMP_POINT = process.env.COMP_LINE.length - - t.teardown(() => { - delete process.env.COMP_CWORD - delete process.env.COMP_LINE - delete process.env.COMP_POINT - }) + const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm compute') await completion.exec(['npm', 'compute']) t.matchSnapshot(outputs, 'no results') }) t.test('subcommand completion', async t => { - process.env.COMP_CWORD = 2 - process.env.COMP_LINE = 'npm access ' - process.env.COMP_POINT = process.env.COMP_LINE.length - - t.teardown(() => { - delete process.env.COMP_CWORD - delete process.env.COMP_LINE - delete process.env.COMP_POINT - }) + const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm access ') await completion.exec(['npm', 'access', '']) t.matchSnapshot(outputs, 'subcommands') }) t.test('filtered subcommands', async t => { - process.env.COMP_CWORD = 2 - process.env.COMP_LINE = 'npm access p' - process.env.COMP_POINT = process.env.COMP_LINE.length - - t.teardown(() => { - delete process.env.COMP_CWORD - delete process.env.COMP_LINE - delete process.env.COMP_POINT - }) + const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm access p') await completion.exec(['npm', 'access', 'p']) t.matchSnapshot(outputs, 'filtered subcommands') }) t.test('commands with no completion', async t => { - process.env.COMP_CWORD = 2 - process.env.COMP_LINE = 'npm adduser ' - process.env.COMP_POINT = process.env.COMP_LINE.length - - t.teardown(() => { - delete process.env.COMP_CWORD - delete process.env.COMP_LINE - delete process.env.COMP_POINT - }) + const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm adduser ') // quotes around adduser are to ensure coverage when unescaping commands await completion.exec(['npm', "'adduser'", '']) @@ -196,63 +160,28 @@ t.test('completion', async t => { }) t.test('flags', async t => { - process.env.COMP_CWORD = 2 - process.env.COMP_LINE = 'npm install --v' - process.env.COMP_POINT = process.env.COMP_LINE.length - - t.teardown(() => { - delete process.env.COMP_CWORD - delete process.env.COMP_LINE - delete process.env.COMP_POINT - }) + const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm install --v') await completion.exec(['npm', 'install', '--v']) - t.matchSnapshot(outputs, 'flags') }) t.test('--no- flags', async t => { - process.env.COMP_CWORD = 2 - process.env.COMP_LINE = 'npm install --no-v' - process.env.COMP_POINT = process.env.COMP_LINE.length - - t.teardown(() => { - delete process.env.COMP_CWORD - delete process.env.COMP_LINE - delete process.env.COMP_POINT - }) + const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm install --no-v') await completion.exec(['npm', 'install', '--no-v']) - t.matchSnapshot(outputs, 'flags') }) t.test('double dashes escape from flag completion', async t => { - process.env.COMP_CWORD = 2 - process.env.COMP_LINE = 'npm -- install --' - process.env.COMP_POINT = process.env.COMP_LINE.length - - t.teardown(() => { - delete process.env.COMP_CWORD - delete process.env.COMP_LINE - delete process.env.COMP_POINT - }) + const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm -- install --') await completion.exec(['npm', '--', 'install', '--']) - t.matchSnapshot(outputs, 'full command list') }) t.test('completion cannot complete options that take a value in mid-command', async t => { - process.env.COMP_CWORD = 2 - process.env.COMP_LINE = 'npm --registry install' - process.env.COMP_POINT = process.env.COMP_LINE.length - - t.teardown(() => { - delete process.env.COMP_CWORD - delete process.env.COMP_LINE - delete process.env.COMP_POINT - }) + const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm --registry install') await completion.exec(['npm', '--registry', 'install']) t.matchSnapshot(outputs, 'does not try to complete option arguments in the middle of a command') @@ -260,11 +189,7 @@ t.test('completion', async t => { }) t.test('windows without bash', async t => { - const { Npm, outputs } = mockNpm(t, { - '../../lib/utils/is-windows-shell.js': true, - }) - const npm = new Npm() - const completion = await npm.cmd('completion') + const { outputs, completion } = await loadMockCompletion(t, { windows: true }) await t.rejects( completion.exec({}), { code: 'ENOTSUP', message: /completion supported only in MINGW/ }, diff --git a/test/lib/commands/dedupe.js b/test/lib/commands/dedupe.js index 8fc0be061..2e2fae238 100644 --- a/test/lib/commands/dedupe.js +++ b/test/lib/commands/dedupe.js @@ -1,11 +1,12 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') t.test('should throw in global mode', async (t) => { - const { Npm } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.config.set('global', true) + const { npm } = await loadMockNpm(t, { + config: { + global: true, + }, + }) t.rejects( npm.exec('dedupe', []), { code: 'EDEDUPEGLOBAL' }, @@ -15,39 +16,41 @@ t.test('should throw in global mode', async (t) => { t.test('should remove dupes using Arborist', async (t) => { t.plan(5) - const { Npm } = mockNpm(t, { - '@npmcli/arborist': function (args) { - t.ok(args, 'gets options object') - t.ok(args.path, 'gets path option') - t.ok(args.dryRun, 'gets dryRun from user') - this.dedupe = () => { - t.ok(true, 'dedupe is called') - } + const { npm } = await loadMockNpm(t, { + mocks: { + '@npmcli/arborist': function (args) { + t.ok(args, 'gets options object') + t.ok(args.path, 'gets path option') + t.ok(args.dryRun, 'gets dryRun from user') + this.dedupe = () => { + t.ok(true, 'dedupe is called') + } + }, + '../../lib/utils/reify-finish.js': (npm, arb) => { + t.ok(arb, 'gets arborist tree') + }, }, - '../../lib/utils/reify-finish.js': (npm, arb) => { - t.ok(arb, 'gets arborist tree') + config: { + 'dry-run': 'true', }, }) - const npm = new Npm() - await npm.load() - npm.config.set('prefix', 'foo') - npm.config.set('dry-run', 'true') await npm.exec('dedupe', []) }) t.test('should remove dupes using Arborist - no arguments', async (t) => { t.plan(1) - const { Npm } = mockNpm(t, { - '@npmcli/arborist': function (args) { - t.ok(args.dryRun, 'gets dryRun from config') - this.dedupe = () => {} + const { npm } = await loadMockNpm(t, { + mocks: { + '@npmcli/arborist': function (args) { + t.ok(args.dryRun, 'gets dryRun from config') + this.dedupe = () => {} + }, + '../../lib/utils/reify-output.js': () => {}, + '../../lib/utils/reify-finish.js': () => {}, + }, + config: { + 'dry-run': true, }, - '../../lib/utils/reify-output.js': () => {}, - '../../lib/utils/reify-finish.js': () => {}, }) - const npm = new Npm() - await npm.load() - npm.config.set('prefix', 'foo') - npm.config.set('dry-run', true) await npm.exec('dedupe', []) }) diff --git a/test/lib/commands/diff.js b/test/lib/commands/diff.js index 811936fe6..ed0702e37 100644 --- a/test/lib/commands/diff.js +++ b/test/lib/commands/diff.js @@ -31,7 +31,7 @@ const npm = mockNpm({ }) const mocks = { - npmlog: { info: noop, verbose: noop }, + 'proc-log': { info: noop, verbose: noop }, libnpmdiff: (...args) => libnpmdiff(...args), 'npm-registry-fetch': async () => ({}), '../../../lib/utils/usage.js': () => 'usage instructions', diff --git a/test/lib/commands/dist-tag.js b/test/lib/commands/dist-tag.js index 6b45dc116..756a09d7d 100644 --- a/test/lib/commands/dist-tag.js +++ b/test/lib/commands/dist-tag.js @@ -61,7 +61,7 @@ const logger = (...msgs) => { } const DistTag = t.mock('../../../lib/commands/dist-tag.js', { - npmlog: { + 'proc-log': { error: logger, info: logger, verbose: logger, diff --git a/test/lib/commands/doctor.js b/test/lib/commands/doctor.js index e3ad5cc72..51b6111a0 100644 --- a/test/lib/commands/doctor.js +++ b/test/lib/commands/doctor.js @@ -50,13 +50,13 @@ const logs = { info: [], } -const clearLogs = (obj = logs) => { +const clearLogs = () => { output.length = 0 - for (const key in obj) { - if (Array.isArray(obj[key])) { - obj[key].length = 0 + for (const key in logs) { + if (Array.isArray(logs[key])) { + logs[key].length = 0 } else { - delete obj[key] + delete logs[key] } } } @@ -65,13 +65,41 @@ const npm = { flatOptions: { registry: 'https://registry.npmjs.org/', }, - log: { + version: '7.1.0', + output: data => { + output.push(data) + }, +} + +let latestNpm = npm.version +const pacote = { + manifest: async () => { + return { version: latestNpm } + }, +} + +let verifyResponse = { verifiedCount: 1, verifiedContent: 1 } +const cacache = { + verify: async () => { + return verifyResponse + }, +} + +const mocks = { + '../../../lib/utils/is-windows.js': false, + '../../../lib/utils/ping.js': ping, + cacache, + pacote, + 'make-fetch-happen': fetch, + which, + 'proc-log': { info: msg => { logs.info.push(msg) }, + }, + npmlog: { newItem: name => { logs[name] = {} - return { info: (_, msg) => { if (!logs[name].info) { @@ -109,33 +137,11 @@ const npm = { error: 0, }, }, - version: '7.1.0', - output: data => { - output.push(data) - }, -} -let latestNpm = npm.version -const pacote = { - manifest: async () => { - return { version: latestNpm } - }, -} - -let verifyResponse = { verifiedCount: 1, verifiedContent: 1 } -const cacache = { - verify: async () => { - return verifyResponse - }, } const Doctor = t.mock('../../../lib/commands/doctor.js', { - '../../../lib/utils/is-windows.js': false, - '../../../lib/utils/ping.js': ping, - cacache, - pacote, - 'make-fetch-happen': fetch, - which, + ...mocks, }) const doctor = new Doctor(npm) @@ -205,7 +211,7 @@ t.test('node versions', t => { npm.globalDir = dir npm.localBin = dir npm.globalBin = dir - npm.log.level = 'info' + mocks.npmlog.level = 'info' st.teardown(() => { delete npm.cache @@ -214,7 +220,7 @@ t.test('node versions', t => { delete npm.globalDir delete npm.localBin delete npm.globalBin - npm.log.level = 'error' + mocks.npmlog.level = 'error' clearLogs() }) @@ -293,12 +299,8 @@ t.test('node versions', t => { vt.test('npm doctor skips some tests in windows', async st => { const WinDoctor = t.mock('../../../lib/commands/doctor.js', { + ...mocks, '../../../lib/utils/is-windows.js': true, - '../../../lib/utils/ping.js': ping, - cacache, - pacote, - 'make-fetch-happen': fetch, - which, }) const winDoctor = new WinDoctor(npm) @@ -592,12 +594,7 @@ t.test('node versions', t => { } const Doctor = t.mock('../../../lib/commands/doctor.js', { - '../../../lib/utils/is-windows.js': false, - '../../../lib/utils/ping.js': ping, - cacache, - pacote, - 'make-fetch-happen': fetch, - which, + ...mocks, fs, }) const doctor = new Doctor(npm) diff --git a/test/lib/commands/exec.js b/test/lib/commands/exec.js index 4ab26568f..3c75c1d8d 100644 --- a/test/lib/commands/exec.js +++ b/test/lib/commands/exec.js @@ -44,17 +44,6 @@ const npm = mockNpm({ localPrefix: 'local-prefix', localBin: 'local-bin', globalBin: 'global-bin', - log: { - disableProgress: () => { - PROGRESS_ENABLED = false - }, - enableProgress: () => { - PROGRESS_ENABLED = true - }, - warn: (...args) => { - LOG_WARN.push(args) - }, - }, }) const RUN_SCRIPTS = [] @@ -87,6 +76,23 @@ const PATH = require('../../../lib/utils/path.js') let CI_NAME = 'travis-ci' +const log = { + 'proc-log': { + warn: (...args) => { + LOG_WARN.push(args) + }, + }, + npmlog: { + disableProgress: () => { + PROGRESS_ENABLED = false + }, + enableProgress: () => { + PROGRESS_ENABLED = true + }, + clearProgress: () => {}, + }, +} + const mocks = { libnpmexec: t.mock('libnpmexec', { '@npmcli/arborist': Arborist, @@ -95,7 +101,9 @@ const mocks = { pacote, read, 'mkdirp-infer-owner': mkdirp, + ...log, }), + ...log, } const Exec = t.mock('../../../lib/commands/exec.js', mocks) const exec = new Exec(npm) diff --git a/test/lib/commands/explore.js b/test/lib/commands/explore.js index b2e7be213..d1355d767 100644 --- a/test/lib/commands/explore.js +++ b/test/lib/commands/explore.js @@ -51,14 +51,17 @@ const getExplore = (windows) => { path: require('path')[windows ? 'win32' : 'posix'], 'read-package-json-fast': mockRPJ, '@npmcli/run-script': mockRunScript, - }) - const npm = { - dir: windows ? 'c:\\npm\\dir' : '/npm/dir', - log: { + 'proc-log': { error: (...msg) => logs.push(msg), + warn: () => {}, + }, + npmlog: { disableProgress: () => {}, enableProgress: () => {}, }, + }) + const npm = { + dir: windows ? 'c:\\npm\\dir' : '/npm/dir', flatOptions: { shell: 'shell-command', }, diff --git a/test/lib/commands/find-dupes.js b/test/lib/commands/find-dupes.js index c1b9c71df..06bd097b6 100644 --- a/test/lib/commands/find-dupes.js +++ b/test/lib/commands/find-dupes.js @@ -1,27 +1,28 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') t.test('should run dedupe in dryRun mode', async (t) => { t.plan(5) - const { Npm } = mockNpm(t, { - '@npmcli/arborist': function (args) { - t.ok(args, 'gets options object') - t.ok(args.path, 'gets path option') - t.ok(args.dryRun, 'is called in dryRun mode') - this.dedupe = () => { - t.ok(true, 'dedupe is called') - } + const { npm } = await loadMockNpm(t, { + mocks: { + '@npmcli/arborist': function (args) { + t.ok(args, 'gets options object') + t.ok(args.path, 'gets path option') + t.ok(args.dryRun, 'is called in dryRun mode') + this.dedupe = () => { + t.ok(true, 'dedupe is called') + } + }, + '../../lib/utils/reify-finish.js': (npm, arb) => { + t.ok(arb, 'gets arborist tree') + }, }, - '../../lib/utils/reify-finish.js': (npm, arb) => { - t.ok(arb, 'gets arborist tree') + config: { + // explicitly set to false so we can be 100% sure it's always true when it + // hits arborist + 'dry-run': false, }, }) - const npm = new Npm() - await npm.load() - // explicitly set to false so we can be 100% sure it's always true when it - // hits arborist - npm.config.set('dry-run', false) - npm.config.set('prefix', 'foo') await npm.exec('find-dupes', []) }) diff --git a/test/lib/commands/get.js b/test/lib/commands/get.js index ba9e770e3..597cccc3f 100644 --- a/test/lib/commands/get.js +++ b/test/lib/commands/get.js @@ -1,12 +1,10 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') t.test('should retrieve values from config', async t => { - const { joinedOutput, Npm } = mockNpm(t) - const npm = new Npm() + const { joinedOutput, npm } = await loadMockNpm(t) const name = 'editor' const value = 'vigor' - await npm.load() npm.config.set(name, value) await npm.exec('get', [name]) t.equal( diff --git a/test/lib/commands/init.js b/test/lib/commands/init.js index 74b33168a..215ebc581 100644 --- a/test/lib/commands/init.js +++ b/test/lib/commands/init.js @@ -3,14 +3,6 @@ const fs = require('fs') const { resolve } = require('path') const { fake: mockNpm } = require('../../fixtures/mock-npm') -const npmLog = { - disableProgress: () => null, - enableProgress: () => null, - info: () => null, - pause: () => null, - resume: () => null, - silly: () => null, -} const config = { cache: 'bad-cache-dir', 'init-module': '~/.npm-init.js', @@ -23,10 +15,19 @@ const flatOptions = { const npm = mockNpm({ flatOptions, config, - log: npmLog, }) const mocks = { '../../../lib/utils/usage.js': () => 'usage instructions', + npmlog: { + disableProgress: () => null, + enableProgress: () => null, + }, + 'proc-log': { + info: () => null, + pause: () => null, + resume: () => null, + silly: () => null, + }, } const Init = t.mock('../../../lib/commands/init.js', mocks) const init = new Init(npm) @@ -37,7 +38,6 @@ const noop = () => {} t.afterEach(() => { config.yes = true config.package = undefined - npm.log = npmLog process.chdir(_cwd) console.log = _consolelog }) @@ -251,13 +251,15 @@ t.test('npm init cancel', async t => { 'init-package-json': (dir, initFile, config, cb) => cb( new Error('canceled') ), + 'proc-log': { + ...mocks['proc-log'], + warn: (title, msg) => { + t.equal(title, 'init', 'should have init title') + t.equal(msg, 'canceled', 'should log canceled') + }, + }, }) const init = new Init(npm) - npm.log = { ...npm.log } - npm.log.warn = (title, msg) => { - t.equal(title, 'init', 'should have init title') - t.equal(msg, 'canceled', 'should log canceled') - } process.chdir(npm.localPrefix) await init.exec([]) diff --git a/test/lib/commands/install.js b/test/lib/commands/install.js index 994684596..d5db3af67 100644 --- a/test/lib/commands/install.js +++ b/test/lib/commands/install.js @@ -1,7 +1,10 @@ const t = require('tap') const path = require('path') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: _loadMockNpm } = require('../../fixtures/mock-npm') + +// Make less churn in the test to pass in mocks only signature +const loadMockNpm = (t, mocks) => _loadMockNpm(t, { mocks }) t.test('with args, dev=true', async t => { const SCRIPTS = [] @@ -9,7 +12,7 @@ t.test('with args, dev=true', async t => { let REIFY_CALLED = false let ARB_OBJ = null - const { Npm, filteredLogs } = mockNpm(t, { + const { npm, logs } = await loadMockNpm(t, { '@npmcli/run-script': ({ event }) => { SCRIPTS.push(event) }, @@ -27,8 +30,6 @@ t.test('with args, dev=true', async t => { }, }) - const npm = new Npm() - await npm.load() // This is here because CI calls tests with `--ignore-scripts`, which config // picks up from argv npm.config.set('ignore-scripts', false) @@ -41,8 +42,8 @@ t.test('with args, dev=true', async t => { await npm.exec('install', ['fizzbuzz']) t.match( - filteredLogs('warn'), - ['Usage of the `--dev` option is deprecated. Use `--include=dev` instead.'] + logs.warn, + [['install', 'Usage of the `--dev` option is deprecated. Use `--include=dev` instead.']] ) t.match( ARB_ARGS, @@ -59,7 +60,7 @@ t.test('without args', async t => { let REIFY_CALLED = false let ARB_OBJ = null - const { Npm } = mockNpm(t, { + const { npm } = await loadMockNpm(t, { '@npmcli/run-script': ({ event }) => { SCRIPTS.push(event) }, @@ -77,8 +78,6 @@ t.test('without args', async t => { }, }) - const npm = new Npm() - await npm.load() npm.prefix = path.resolve(t.testdir({})) npm.config.set('ignore-scripts', false) await npm.exec('install', []) @@ -98,7 +97,7 @@ t.test('without args', async t => { t.test('should ignore scripts with --ignore-scripts', async t => { const SCRIPTS = [] let REIFY_CALLED = false - const { Npm } = mockNpm(t, { + const { npm } = await loadMockNpm(t, { '../../lib/utils/reify-finish.js': async () => {}, '@npmcli/run-script': ({ event }) => { SCRIPTS.push(event) @@ -109,8 +108,6 @@ t.test('should ignore scripts with --ignore-scripts', async t => { } }, }) - const npm = new Npm() - await npm.load() npm.config.set('ignore-scripts', true) npm.prefix = path.resolve(t.testdir({})) await npm.exec('install', []) @@ -122,7 +119,7 @@ t.test('should install globally using Arborist', async t => { const SCRIPTS = [] let ARB_ARGS = null let REIFY_CALLED - const { Npm } = mockNpm(t, { + const { npm } = await loadMockNpm(t, { '@npmcli/run-script': ({ event }) => { SCRIPTS.push(event) }, @@ -134,8 +131,6 @@ t.test('should install globally using Arborist', async t => { } }, }) - const npm = new Npm() - await npm.load() npm.config.set('global', true) npm.globalPrefix = path.resolve(t.testdir({})) await npm.exec('install', []) @@ -148,7 +143,7 @@ t.test('should install globally using Arborist', async t => { }) t.test('npm i -g npm engines check success', async t => { - const { Npm } = mockNpm(t, { + const { npm } = await loadMockNpm(t, { '../../lib/utils/reify-finish.js': async () => {}, '@npmcli/arborist': function () { this.reify = () => {} @@ -164,8 +159,6 @@ t.test('npm i -g npm engines check success', async t => { }, }, }) - const npm = new Npm() - await npm.load() npm.globalDir = t.testdir({}) npm.config.set('global', true) await npm.exec('install', ['npm']) @@ -173,7 +166,7 @@ t.test('npm i -g npm engines check success', async t => { }) t.test('npm i -g npm engines check failure', async t => { - const { Npm } = mockNpm(t, { + const { npm } = await loadMockNpm(t, { pacote: { manifest: () => { return { @@ -186,8 +179,6 @@ t.test('npm i -g npm engines check failure', async t => { }, }, }) - const npm = new Npm() - await npm.load() npm.globalDir = t.testdir({}) npm.config.set('global', true) await t.rejects( @@ -208,7 +199,7 @@ t.test('npm i -g npm engines check failure', async t => { }) t.test('npm i -g npm engines check failure forced override', async t => { - const { Npm } = mockNpm(t, { + const { npm } = await loadMockNpm(t, { '../../lib/utils/reify-finish.js': async () => {}, '@npmcli/arborist': function () { this.reify = () => {} @@ -225,8 +216,6 @@ t.test('npm i -g npm engines check failure forced override', async t => { }, }, }) - const npm = new Npm() - await npm.load() npm.globalDir = t.testdir({}) npm.config.set('global', true) npm.config.set('force', true) @@ -235,7 +224,7 @@ t.test('npm i -g npm engines check failure forced override', async t => { }) t.test('npm i -g npm@version engines check failure', async t => { - const { Npm } = mockNpm(t, { + const { npm } = await loadMockNpm(t, { pacote: { manifest: () => { return { @@ -248,8 +237,6 @@ t.test('npm i -g npm@version engines check failure', async t => { }, }, }) - const npm = new Npm() - await npm.load() npm.globalDir = t.testdir({}) npm.config.set('global', true) await t.rejects( @@ -283,8 +270,7 @@ t.test('completion', async t => { }) t.test('completion to folder - has a match', async t => { - const { Npm } = mockNpm(t) - const npm = new Npm() + const { npm } = await _loadMockNpm(t, { load: false }) const install = await npm.cmd('install') process.chdir(testdir) const res = await install.completion({ partialWord: './ar' }) @@ -292,16 +278,14 @@ t.test('completion', async t => { }) t.test('completion to folder - invalid dir', async t => { - const { Npm } = mockNpm(t) - const npm = new Npm() + const { npm } = await _loadMockNpm(t, { load: false }) const install = await npm.cmd('install') const res = await install.completion({ partialWord: '/does/not/exist' }) t.strictSame(res, [], 'invalid dir: no matching') }) t.test('completion to folder - no matches', async t => { - const { Npm } = mockNpm(t) - const npm = new Npm() + const { npm } = await _loadMockNpm(t, { load: false }) const install = await npm.cmd('install') process.chdir(testdir) const res = await install.completion({ partialWord: './pa' }) @@ -309,8 +293,7 @@ t.test('completion', async t => { }) t.test('completion to folder - match is not a package', async t => { - const { Npm } = mockNpm(t) - const npm = new Npm() + const { npm } = await _loadMockNpm(t, { load: false }) const install = await npm.cmd('install') process.chdir(testdir) const res = await install.completion({ partialWord: './othe' }) @@ -318,8 +301,7 @@ t.test('completion', async t => { }) t.test('completion to url', async t => { - const { Npm } = mockNpm(t) - const npm = new Npm() + const { npm } = await _loadMockNpm(t, { load: false }) const install = await npm.cmd('install') process.chdir(testdir) const res = await install.completion({ partialWord: 'http://path/to/url' }) @@ -327,8 +309,7 @@ t.test('completion', async t => { }) t.test('no /', async t => { - const { Npm } = mockNpm(t) - const npm = new Npm() + const { npm } = await _loadMockNpm(t, { load: false }) const install = await npm.cmd('install') process.chdir(testdir) const res = await install.completion({ partialWord: 'toto' }) @@ -336,8 +317,7 @@ t.test('completion', async t => { }) t.test('only /', async t => { - const { Npm } = mockNpm(t) - const npm = new Npm() + const { npm } = await _loadMockNpm(t, { load: false }) const install = await npm.cmd('install') process.chdir(testdir) const res = await install.completion({ partialWord: '/' }) diff --git a/test/lib/commands/logout.js b/test/lib/commands/logout.js index 39ef86c84..ee01e7500 100644 --- a/test/lib/commands/logout.js +++ b/test/lib/commands/logout.js @@ -10,45 +10,31 @@ const flatOptions = { scope: '', } const npm = mockNpm({ config, flatOptions }) - -const npmlog = {} - let result = null -const npmFetch = (url, opts) => { - result = { url, opts } -} -const mocks = { - npmlog, - 'npm-registry-fetch': npmFetch, +const mockLogout = (otherMocks) => { + const Logout = t.mock('../../../lib/commands/logout.js', { + 'npm-registry-fetch': (url, opts) => { + result = { url, opts } + }, + ...otherMocks, + }) + return new Logout(npm) } -const Logout = t.mock('../../../lib/commands/logout.js', mocks) -const logout = new Logout(npm) +t.afterEach(() => { + delete flatOptions.token + result = null + config.clearCredentialsByURI = null + config.delete = null + config.save = null +}) t.test('token logout', async t => { - t.teardown(() => { - delete flatOptions.token - result = null - mocks['npm-registry-fetch'] = null - config.clearCredentialsByURI = null - config.delete = null - config.save = null - npmlog.verbose = null - }) t.plan(5) flatOptions['//registry.npmjs.org/:_authToken'] = '@foo/' - npmlog.verbose = (title, msg) => { - t.equal(title, 'logout', 'should have correcct log prefix') - t.equal( - msg, - 'clearing token for https://registry.npmjs.org/', - 'should log message with correct registry' - ) - } - npm.config.clearCredentialsByURI = registry => { t.equal( registry, @@ -61,6 +47,19 @@ t.test('token logout', async t => { t.equal(type, 'user', 'should save to user config') } + const logout = mockLogout({ + 'proc-log': { + verbose: (title, msg) => { + t.equal(title, 'logout', 'should have correcct log prefix') + t.equal( + msg, + 'clearing token for https://registry.npmjs.org/', + 'should log message with correct registry' + ) + }, + }, + }) + await logout.exec([]) t.same( @@ -87,12 +86,11 @@ t.test('token scoped logout', async t => { delete config['@myscope:registry'] delete flatOptions.scope result = null - mocks['npm-registry-fetch'] = null config.clearCredentialsByURI = null config.delete = null config.save = null - npmlog.verbose = null }) + t.plan(7) flatOptions['//diff-registry.npmjs.com/:_authToken'] = '@bar/' @@ -102,15 +100,6 @@ t.test('token scoped logout', async t => { flatOptions.scope = '@myscope' flatOptions['@myscope:registry'] = 'https://diff-registry.npmjs.com/' - npmlog.verbose = (title, msg) => { - t.equal(title, 'logout', 'should have correcct log prefix') - t.equal( - msg, - 'clearing token for https://diff-registry.npmjs.com/', - 'should log message with correct registry' - ) - } - npm.config.clearCredentialsByURI = registry => { t.equal( registry, @@ -128,6 +117,19 @@ t.test('token scoped logout', async t => { t.equal(type, 'user', 'should save to user config') } + const logout = mockLogout({ + 'proc-log': { + verbose: (title, msg) => { + t.equal(title, 'logout', 'should have correcct log prefix') + t.equal( + msg, + 'clearing token for https://diff-registry.npmjs.com/', + 'should log message with correct registry' + ) + }, + }, + }) + await logout.exec([]) t.same( @@ -154,29 +156,34 @@ t.test('user/pass logout', async t => { delete flatOptions['//registry.npmjs.org/:_password'] npm.config.clearCredentialsByURI = null npm.config.save = null - npmlog.verbose = null }) t.plan(2) flatOptions['//registry.npmjs.org/:username'] = 'foo' flatOptions['//registry.npmjs.org/:_password'] = 'bar' - npmlog.verbose = (title, msg) => { - t.equal(title, 'logout', 'should have correct log prefix') - t.equal( - msg, - 'clearing user credentials for https://registry.npmjs.org/', - 'should log message with correct registry' - ) - } - npm.config.clearCredentialsByURI = () => null npm.config.save = () => null + const logout = mockLogout({ + 'proc-log': { + verbose: (title, msg) => { + t.equal(title, 'logout', 'should have correct log prefix') + t.equal( + msg, + 'clearing user credentials for https://registry.npmjs.org/', + 'should log message with correct registry' + ) + }, + }, + }) + await logout.exec([]) }) t.test('missing credentials', async t => { + const logout = mockLogout() + await t.rejects( logout.exec([]), { @@ -191,11 +198,9 @@ t.test('ignore invalid scoped registry config', async t => { t.teardown(() => { delete flatOptions.token result = null - mocks['npm-registry-fetch'] = null config.clearCredentialsByURI = null config.delete = null config.save = null - npmlog.verbose = null }) t.plan(4) @@ -203,15 +208,6 @@ t.test('ignore invalid scoped registry config', async t => { config.scope = '@myscope' flatOptions['@myscope:registry'] = '' - npmlog.verbose = (title, msg) => { - t.equal(title, 'logout', 'should have correcct log prefix') - t.equal( - msg, - 'clearing token for https://registry.npmjs.org/', - 'should log message with correct registry' - ) - } - npm.config.clearCredentialsByURI = registry => { t.equal( registry, @@ -223,6 +219,19 @@ t.test('ignore invalid scoped registry config', async t => { npm.config.delete = () => null npm.config.save = () => null + const logout = mockLogout({ + 'proc-log': { + verbose: (title, msg) => { + t.equal(title, 'logout', 'should have correcct log prefix') + t.equal( + msg, + 'clearing token for https://registry.npmjs.org/', + 'should log message with correct registry' + ) + }, + }, + }) + await logout.exec([]) t.same( diff --git a/test/lib/commands/owner.js b/test/lib/commands/owner.js index 8645b349f..b5d4d1584 100644 --- a/test/lib/commands/owner.js +++ b/test/lib/commands/owner.js @@ -14,11 +14,11 @@ const npm = mockNpm({ }) const npmFetch = { json: noop } -const npmlog = { error: noop, info: noop, verbose: noop } +const log = { error: noop, info: noop, verbose: noop } const pacote = { packument: noop } const mocks = { - npmlog, + 'proc-log': log, 'npm-registry-fetch': npmFetch, pacote, '../../../lib/utils/otplease.js': async (opts, fn) => fn({ otp: '123456', opts }), @@ -97,7 +97,7 @@ t.test('owner ls no args no cwd package', async t => { result = '' t.teardown(() => { result = '' - npmlog.error = noop + log.error = noop }) await t.rejects( @@ -114,14 +114,14 @@ t.test('owner ls fails to retrieve packument', async t => { pacote.packument = () => { throw new Error('ERR') } - npmlog.error = (title, msg, pkgName) => { + log.error = (title, msg, pkgName) => { t.equal(title, 'owner ls', 'should list npm owner ls title') t.equal(msg, "Couldn't get owner data", 'should use expected msg') t.equal(pkgName, '@npmcli/map-workspaces', 'should use pkg name') } t.teardown(() => { result = '' - npmlog.error = noop + log.error = noop pacote.packument = noop }) @@ -276,7 +276,7 @@ t.test('owner add <user> <pkg> already an owner', async t => { t.plan(2) result = '' - npmlog.info = (title, msg) => { + log.info = (title, msg) => { t.equal(title, 'owner add', 'should use expected title') t.equal( msg, @@ -304,7 +304,7 @@ t.test('owner add <user> <pkg> already an owner', async t => { } t.teardown(() => { result = '' - npmlog.info = noop + log.info = noop npmFetch.json = noop pacote.packument = noop }) @@ -385,7 +385,7 @@ t.test('owner add <user> <pkg> fails to retrieve user info', async t => { t.plan(3) result = '' - npmlog.error = (title, msg) => { + log.error = (title, msg) => { t.equal(title, 'owner mutate', 'should use expected title') t.equal(msg, 'Error getting user data for foo') } @@ -406,7 +406,7 @@ t.test('owner add <user> <pkg> fails to retrieve user info', async t => { }) t.teardown(() => { result = '' - npmlog.error = noop + log.error = noop npmFetch.json = noop pacote.packument = noop }) @@ -552,7 +552,7 @@ t.test('owner rm <user> <pkg> not a current owner', async t => { t.plan(2) result = '' - npmlog.info = (title, msg) => { + log.info = (title, msg) => { t.equal(title, 'owner rm', 'should log expected title') t.equal(msg, 'Not a package owner: foo', 'should log.info not a package owner msg') } @@ -578,7 +578,7 @@ t.test('owner rm <user> <pkg> not a current owner', async t => { } t.teardown(() => { result = '' - npmlog.info = noop + log.info = noop npmFetch.json = noop pacote.packument = noop }) diff --git a/test/lib/commands/pack.js b/test/lib/commands/pack.js index bc8877208..21057e207 100644 --- a/test/lib/commands/pack.js +++ b/test/lib/commands/pack.js @@ -1,5 +1,5 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') const path = require('path') const fs = require('fs') @@ -9,33 +9,31 @@ t.afterEach(t => { }) t.test('should pack current directory with no arguments', async t => { - const { Npm, outputs, filteredLogs } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.prefix = t.testdir({ - 'package.json': JSON.stringify({ - name: 'test-package', - version: '1.0.0', - }), + const { npm, outputs, logs } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: 'test-package', + version: '1.0.0', + }), + }, }) process.chdir(npm.prefix) await npm.exec('pack', []) const filename = 'test-package-1.0.0.tgz' t.strictSame(outputs, [[filename]]) - t.matchSnapshot(filteredLogs('notice'), 'logs pack contents') + t.matchSnapshot(logs.notice.map(([, m]) => m), 'logs pack contents') t.ok(fs.statSync(path.resolve(npm.prefix, filename))) }) t.test('follows pack-destination config', async t => { - const { Npm, outputs } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.prefix = t.testdir({ - 'package.json': JSON.stringify({ - name: 'test-package', - version: '1.0.0', - }), - 'tar-destination': {}, + const { npm, outputs } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: 'test-package', + version: '1.0.0', + }), + 'tar-destination': {}, + }, }) process.chdir(npm.prefix) npm.config.set('pack-destination', path.join(npm.prefix, 'tar-destination')) @@ -46,14 +44,13 @@ t.test('follows pack-destination config', async t => { }) t.test('should pack given directory for scoped package', async t => { - const { Npm, outputs } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.prefix = t.testdir({ - 'package.json': JSON.stringify({ - name: '@npm/test-package', - version: '1.0.0', - }), + const { npm, outputs } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: '@npm/test-package', + version: '1.0.0', + }), + }, }) process.chdir(npm.prefix) await npm.exec('pack', []) @@ -63,49 +60,46 @@ t.test('should pack given directory for scoped package', async t => { }) t.test('should log output as valid json', async t => { - const { Npm, outputs, filteredLogs } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.prefix = t.testdir({ - 'package.json': JSON.stringify({ - name: 'test-package', - version: '1.0.0', - }), + const { npm, outputs, logs } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: 'test-package', + version: '1.0.0', + }), + }, }) process.chdir(npm.prefix) npm.config.set('json', true) await npm.exec('pack', []) const filename = 'test-package-1.0.0.tgz' t.matchSnapshot(outputs.map(JSON.parse), 'outputs as json') - t.matchSnapshot(filteredLogs('notice'), 'logs pack contents') + t.matchSnapshot(logs.notice.map(([, m]) => m), 'logs pack contents') t.ok(fs.statSync(path.resolve(npm.prefix, filename))) }) t.test('dry run', async t => { - const { Npm, outputs, filteredLogs } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.prefix = t.testdir({ - 'package.json': JSON.stringify({ - name: 'test-package', - version: '1.0.0', - }), + const { npm, outputs, logs } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: 'test-package', + version: '1.0.0', + }), + }, }) npm.config.set('dry-run', true) process.chdir(npm.prefix) await npm.exec('pack', []) const filename = 'test-package-1.0.0.tgz' t.strictSame(outputs, [[filename]]) - t.matchSnapshot(filteredLogs('notice'), 'logs pack contents') + t.matchSnapshot(logs.notice.map(([, m]) => m), 'logs pack contents') t.throws(() => fs.statSync(path.resolve(npm.prefix, filename))) }) t.test('invalid packument', async t => { - const { Npm, outputs } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.prefix = t.testdir({ - 'package.json': '{}', + const { npm, outputs } = await loadMockNpm(t, { + testdir: { + 'package.json': '{}', + }, }) process.chdir(npm.prefix) await t.rejects( @@ -116,52 +110,58 @@ t.test('invalid packument', async t => { }) t.test('workspaces', async t => { - const { Npm, outputs } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.prefix = t.testdir({ - 'package.json': JSON.stringify( - { - name: 'workspaces-test', - version: '1.0.0', - workspaces: ['workspace-a', 'workspace-b'], + const loadWorkspaces = (t) => loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify( + { + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + }, + null, + 2 + ), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.0.0', + }), }, - null, - 2 - ), - 'workspace-a': { - 'package.json': JSON.stringify({ - name: 'workspace-a', - version: '1.0.0', - }), }, - 'workspace-b': { - 'package.json': JSON.stringify({ - name: 'workspace-b', - version: '1.0.0', - }), + config: { + workspaces: true, }, }) - npm.config.set('workspaces', true) + t.test('all workspaces', async t => { + const { npm, outputs } = await loadWorkspaces(t) process.chdir(npm.prefix) await npm.exec('pack', []) t.strictSame(outputs, [['workspace-a-1.0.0.tgz'], ['workspace-b-1.0.0.tgz']]) }) t.test('all workspaces, `.` first arg', async t => { + const { npm, outputs } = await loadWorkspaces(t) process.chdir(npm.prefix) await npm.exec('pack', ['.']) t.strictSame(outputs, [['workspace-a-1.0.0.tgz'], ['workspace-b-1.0.0.tgz']]) }) t.test('one workspace', async t => { + const { npm, outputs } = await loadWorkspaces(t) process.chdir(npm.prefix) await npm.exec('pack', ['workspace-a']) t.strictSame(outputs, [['workspace-a-1.0.0.tgz']]) }) t.test('specific package', async t => { + const { npm, outputs } = await loadWorkspaces(t) process.chdir(npm.prefix) await npm.exec('pack', [npm.prefix]) t.strictSame(outputs, [['workspaces-test-1.0.0.tgz']]) diff --git a/test/lib/commands/ping.js b/test/lib/commands/ping.js index 7011c709b..f808e0ac3 100644 --- a/test/lib/commands/ping.js +++ b/test/lib/commands/ping.js @@ -11,7 +11,7 @@ t.test('pings', async t => { t.equal(spec.registry, registry, 'passes flatOptions') return {} }, - npmlog: { + 'proc-log': { notice: (type, spec) => { ++noticeCalls if (noticeCalls === 1) { @@ -45,7 +45,7 @@ t.test('pings and logs details', async t => { t.equal(spec.registry, registry, 'passes flatOptions') return details }, - npmlog: { + 'proc-log': { notice: (type, spec) => { ++noticeCalls if (noticeCalls === 1) { @@ -83,7 +83,7 @@ t.test('pings and returns json', async t => { t.equal(spec.registry, registry, 'passes flatOptions') return details }, - npmlog: { + 'proc-log': { notice: (type, spec) => { ++noticeCalls if (noticeCalls === 1) { diff --git a/test/lib/commands/prefix.js b/test/lib/commands/prefix.js index 6f059e73a..e8295cf6a 100644 --- a/test/lib/commands/prefix.js +++ b/test/lib/commands/prefix.js @@ -1,9 +1,8 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') t.test('prefix', async t => { - const { joinedOutput, Npm } = mockNpm(t) - const npm = new Npm() + const { joinedOutput, npm } = await loadMockNpm(t, { load: false }) await npm.exec('prefix', []) t.equal( joinedOutput(), diff --git a/test/lib/commands/profile.js b/test/lib/commands/profile.js index 6554ca89e..0f16c1db1 100644 --- a/test/lib/commands/profile.js +++ b/test/lib/commands/profile.js @@ -22,6 +22,8 @@ const mocks = { ansistyles: { bright: a => a }, npmlog: { gauge: { show () {} }, + }, + 'proc-log': { info () {}, notice () {}, warn () {}, @@ -489,23 +491,23 @@ t.test('profile set <key> <value>', t => { }, } - const npmlog = { - gauge: { - show () {}, - }, - warn (title, msg) { - t.equal(title, 'profile', 'should use expected profile') - t.equal( - msg, - 'Passwords do not match, please try again.', - 'should log password mismatch message' - ) - }, - } - const Profile = t.mock('../../../lib/commands/profile.js', { ...mocks, - npmlog, + npmlog: { + gauge: { + show () {}, + }, + }, + 'proc-log': { + warn (title, msg) { + t.equal(title, 'profile', 'should use expected profile') + t.equal( + msg, + 'Passwords do not match, please try again.', + 'should log password mismatch message' + ) + }, + }, 'npm-profile': npmProfile, '../../../lib/utils/read-user-info.js': readUserInfo, }) diff --git a/test/lib/commands/prune.js b/test/lib/commands/prune.js index 49d5ab9be..a7f56547b 100644 --- a/test/lib/commands/prune.js +++ b/test/lib/commands/prune.js @@ -1,20 +1,22 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') t.test('should prune using Arborist', async (t) => { t.plan(4) - const { Npm } = mockNpm(t, { - '@npmcli/arborist': function (args) { - t.ok(args, 'gets options object') - t.ok(args.path, 'gets path option') - this.prune = () => { - t.ok(true, 'prune is called') - } - }, - '../../lib/utils/reify-finish.js': (arb) => { - t.ok(arb, 'gets arborist tree') + const { npm } = await loadMockNpm(t, { + load: false, + mocks: { + '@npmcli/arborist': function (args) { + t.ok(args, 'gets options object') + t.ok(args.path, 'gets path option') + this.prune = () => { + t.ok(true, 'prune is called') + } + }, + '../../lib/utils/reify-finish.js': (arb) => { + t.ok(arb, 'gets arborist tree') + }, }, }) - const npm = new Npm() await npm.exec('prune', []) }) diff --git a/test/lib/commands/publish.js b/test/lib/commands/publish.js index 5f4fb4010..1178cd6ee 100644 --- a/test/lib/commands/publish.js +++ b/test/lib/commands/publish.js @@ -1,13 +1,15 @@ const t = require('tap') const { fake: mockNpm } = require('../../fixtures/mock-npm') const fs = require('fs') +const log = require('../../../lib/utils/log-shim') // The way we set loglevel is kind of convoluted, and there is no way to affect // it from these tests, which only interact with lib/publish.js, which assumes // that the code that is requiring and calling lib/publish.js has already // taken care of the loglevel -const log = require('npmlog') -log.level = 'silent' +const _level = log.level +t.beforeEach(() => (log.level = 'silent')) +t.teardown(() => (log.level = _level)) t.cleanSnapshot = data => { return data.replace(/^ *"gitHead": .*$\n/gm, '') @@ -19,8 +21,6 @@ const defaults = Object.entries(definitions).reduce((defaults, [key, def]) => { return defaults }, {}) -t.afterEach(() => (log.level = 'silent')) - t.test( /* eslint-disable-next-line max-len */ 'should publish with libnpmpublish, passing through flatOptions and respecting publishConfig.registry', @@ -147,7 +147,7 @@ t.test('if loglevel=info and json, should not output package contents', async t id: 'someid', }), logTar: () => { - t.pass('logTar is called') + t.fail('logTar is not called in json mode') }, }, libnpmpublish: { @@ -188,7 +188,6 @@ t.test( ), }) - log.level = 'silent' const Publish = t.mock('../../../lib/commands/publish.js', { '../../../lib/utils/tar.js': { getContents: () => ({ @@ -681,9 +680,12 @@ t.test('private workspaces', async t => { } t.test('with color', async t => { + t.plan(4) + + log.level = 'info' const Publish = t.mock('../../../lib/commands/publish.js', { ...mocks, - npmlog: { + 'proc-log': { notice () {}, verbose () {}, warn (title, msg) { @@ -707,9 +709,12 @@ t.test('private workspaces', async t => { }) t.test('colorless', async t => { + t.plan(4) + + log.level = 'info' const Publish = t.mock('../../../lib/commands/publish.js', { ...mocks, - npmlog: { + 'proc-log': { notice () {}, verbose () {}, warn (title, msg) { @@ -730,6 +735,8 @@ t.test('private workspaces', async t => { }) t.test('unexpected error', async t => { + t.plan(1) + const Publish = t.mock('../../../lib/commands/publish.js', { ...mocks, libnpmpublish: { @@ -741,7 +748,7 @@ t.test('private workspaces', async t => { publishes.push(manifest) }, }, - npmlog: { + 'proc-log': { notice () {}, verbose () {}, }, @@ -755,6 +762,8 @@ t.test('private workspaces', async t => { }) t.test('runs correct lifecycle scripts', async t => { + t.plan(5) + const testDir = t.testdir({ 'package.json': JSON.stringify( { @@ -773,6 +782,7 @@ t.test('runs correct lifecycle scripts', async t => { }) const scripts = [] + log.level = 'info' const Publish = t.mock('../../../lib/commands/publish.js', { '@npmcli/run-script': args => { scripts.push(args) @@ -810,6 +820,8 @@ t.test('runs correct lifecycle scripts', async t => { }) t.test('does not run scripts on --ignore-scripts', async t => { + t.plan(4) + const testDir = t.testdir({ 'package.json': JSON.stringify( { @@ -821,6 +833,7 @@ t.test('does not run scripts on --ignore-scripts', async t => { ), }) + log.level = 'info' const Publish = t.mock('../../../lib/commands/publish.js', { '@npmcli/run-script': () => { t.fail('should not call run-script') diff --git a/test/lib/commands/repo.js b/test/lib/commands/repo.js index 4e61047b4..93eb6d031 100644 --- a/test/lib/commands/repo.js +++ b/test/lib/commands/repo.js @@ -1,8 +1,8 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm.js') -const { join, sep } = require('path') +const { load: _loadMockNpm } = require('../../fixtures/mock-npm.js') +const { sep } = require('path') -const pkgDirs = t.testdir({ +const fixture = { 'package.json': JSON.stringify({ name: 'thispkg', version: '1.2.3', @@ -149,35 +149,36 @@ const pkgDirs = t.testdir({ }, }), }, - workspaces: { +} + +const workspaceFixture = { + 'package.json': JSON.stringify({ + name: 'workspaces-test', + version: '1.2.3-test', + workspaces: ['workspace-a', 'workspace-b', 'workspace-c'], + repository: 'https://github.com/npm/workspaces-test', + }), + 'workspace-a': { 'package.json': JSON.stringify({ - name: 'workspaces-test', - version: '1.2.3-test', - workspaces: ['workspace-a', 'workspace-b', 'workspace-c'], - repository: 'https://github.com/npm/workspaces-test', + name: 'workspace-a', + version: '1.2.3-a', + repository: 'http://repo.workspace-a/', }), - 'workspace-a': { - 'package.json': JSON.stringify({ - name: 'workspace-a', - version: '1.2.3-a', - repository: 'http://repo.workspace-a/', - }), - }, - 'workspace-b': { - 'package.json': JSON.stringify({ - name: 'workspace-b', - version: '1.2.3-n', - repository: 'https://github.com/npm/workspace-b', - }), - }, - 'workspace-c': JSON.stringify({ - 'package.json': { - name: 'workspace-n', - version: '1.2.3-n', - }, + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.2.3-n', + repository: 'https://github.com/npm/workspace-b', }), }, -}) + 'workspace-c': JSON.stringify({ + 'package.json': { + name: 'workspace-n', + version: '1.2.3-n', + }, + }), +} // keep a tally of which urls got opened let opened = {} @@ -185,20 +186,18 @@ const openUrl = async (npm, url, errMsg) => { opened[url] = opened[url] || 0 opened[url]++ } - -const { Npm } = mockNpm(t, { - '../../lib/utils/open-url.js': openUrl, -}) -const npm = new Npm() - -t.before(async () => { - await npm.load() -}) - t.afterEach(() => opened = {}) -t.test('open repo urls', t => { - npm.localPrefix = pkgDirs +const loadMockNpm = async (t, prefix) => { + const res = await _loadMockNpm(t, { + mocks: { '../../lib/utils/open-url.js': openUrl }, + testdir: prefix, + }) + return res +} + +t.test('open repo urls', async t => { + const { npm } = await loadMockNpm(t, fixture) const expect = { hostedgit: 'https://github.com/foo/hostedgit', hostedgitat: 'https://github.com/foo/hostedgitat', @@ -239,8 +238,9 @@ t.test('open repo urls', t => { }) }) -t.test('fail if cannot figure out repo url', t => { - npm.localPrefix = pkgDirs +t.test('fail if cannot figure out repo url', async t => { + const { npm } = await loadMockNpm(t, fixture) + const cases = [ 'norepo', 'repoobbj-nourl', @@ -261,13 +261,13 @@ t.test('fail if cannot figure out repo url', t => { }) t.test('open default package if none specified', async t => { - npm.localPrefix = pkgDirs + const { npm } = await loadMockNpm(t, fixture) await npm.exec('repo', []) t.equal(opened['https://example.com/thispkg'], 1, 'opened expected url', { opened }) }) -t.test('workspaces', t => { - npm.localPrefix = join(pkgDirs, 'workspaces') +t.test('workspaces', async t => { + const { npm } = await loadMockNpm(t, workspaceFixture) t.afterEach(() => { npm.config.set('workspaces', null) @@ -311,5 +311,4 @@ t.test('workspaces', t => { ) t.match({}, opened, 'opened no repo urls') }) - t.end() }) diff --git a/test/lib/commands/restart.js b/test/lib/commands/restart.js index 608de0331..7730f1a30 100644 --- a/test/lib/commands/restart.js +++ b/test/lib/commands/restart.js @@ -1,6 +1,6 @@ const t = require('tap') const spawk = require('spawk') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') spawk.preventUnmatched() t.teardown(() => { @@ -12,24 +12,24 @@ t.teardown(() => { // pretty specific internals of runScript const makeSpawnArgs = require('@npmcli/run-script/lib/make-spawn-args.js') -t.test('should run stop script from package.json', async t => { - const prefix = t.testdir({ - 'package.json': JSON.stringify({ - name: 'x', - version: '1.2.3', - scripts: { - restart: 'node ./test-restart.js', - }, - }), +t.test('should run restart script from package.json', async t => { + const { npm } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: 'x', + version: '1.2.3', + scripts: { + restart: 'node ./test-restart.js', + }, + }), + }, + config: { + loglevel: 'silent', + }, }) - const { Npm } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.log.level = 'silent' - npm.localPrefix = prefix - const [scriptShell] = makeSpawnArgs({ path: prefix }) + const [scriptShell] = makeSpawnArgs({ path: npm.prefix }) const script = spawk.spawn(scriptShell, (args) => { - t.ok(args.includes('node ./test-restart.js "foo"'), 'ran stop script with extra args') + t.ok(args.includes('node ./test-restart.js "foo"'), 'ran restart script with extra args') return true }) await npm.exec('restart', ['foo']) diff --git a/test/lib/commands/root.js b/test/lib/commands/root.js index 9871ddb25..a886b30c3 100644 --- a/test/lib/commands/root.js +++ b/test/lib/commands/root.js @@ -1,9 +1,8 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') t.test('prefix', async (t) => { - const { joinedOutput, Npm } = mockNpm(t) - const npm = new Npm() + const { joinedOutput, npm } = await loadMockNpm(t, { load: false }) await npm.exec('root', []) t.equal( joinedOutput(), diff --git a/test/lib/commands/run-script.js b/test/lib/commands/run-script.js index e421c655e..ea0227cda 100644 --- a/test/lib/commands/run-script.js +++ b/test/lib/commands/run-script.js @@ -31,13 +31,16 @@ const output = [] const npmlog = { disableProgress: () => null, level: 'warn', +} + +const log = { error: () => null, } t.afterEach(() => { npm.color = false npmlog.level = 'warn' - npmlog.error = () => null + log.error = () => null output.length = 0 RUN_SCRIPTS.length = 0 config['if-present'] = false @@ -56,6 +59,7 @@ const getRS = windows => { } ), npmlog, + 'proc-log': log, '../../../lib/utils/is-windows-shell.js': windows, }) return new RunScript(npm) @@ -758,7 +762,7 @@ t.test('workspaces', t => { t.test('missing scripts in all workspaces', async t => { const LOG = [] - npmlog.error = err => { + log.error = err => { LOG.push(String(err)) } await t.rejects( @@ -805,7 +809,7 @@ t.test('workspaces', t => { t.test('missing scripts in some workspaces', async t => { const LOG = [] - npmlog.error = err => { + log.error = err => { LOG.push(String(err)) } await runScript.execWorkspaces(['test'], ['a', 'b', 'c', 'd']) @@ -857,6 +861,7 @@ t.test('workspaces', t => { throw new Error('err') }, npmlog, + 'proc-log': log, '../../../lib/utils/is-windows-shell.js': false, }) const runScript = new RunScript(npm) @@ -875,6 +880,7 @@ t.test('workspaces', t => { RUN_SCRIPTS.push(opts) }, npmlog, + 'proc-log': log, '../../../lib/utils/is-windows-shell.js': false, }) const runScript = new RunScript(npm) diff --git a/test/lib/commands/set-script.js b/test/lib/commands/set-script.js index 592a2431c..2c4fe57d6 100644 --- a/test/lib/commands/set-script.js +++ b/test/lib/commands/set-script.js @@ -10,7 +10,7 @@ const npm = mockNpm(flatOptions) const ERROR_OUTPUT = [] const WARN_OUTPUT = [] const SetScript = t.mock('../../../lib/commands/set-script.js', { - npmlog: { + 'proc-log': { error: (...args) => { ERROR_OUTPUT.push(args) }, diff --git a/test/lib/commands/set.js b/test/lib/commands/set.js index a57ea1a54..feeb90157 100644 --- a/test/lib/commands/set.js +++ b/test/lib/commands/set.js @@ -2,6 +2,7 @@ const t = require('tap') // can't run this until npm set can save to project level npmrc t.skip('npm set', async t => { + // XXX: convert to loadMockNpm const { real: mockNpm } = require('../../fixtures/mock-npm') const { joinedOutput, Npm } = mockNpm(t) const npm = new Npm() diff --git a/test/lib/commands/shrinkwrap.js b/test/lib/commands/shrinkwrap.js index db4021abd..2b9e46c70 100644 --- a/test/lib/commands/shrinkwrap.js +++ b/test/lib/commands/shrinkwrap.js @@ -1,7 +1,7 @@ const t = require('tap') const fs = require('fs') const { resolve } = require('path') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') // Attempt to parse json values in snapshots before // stringifying to remove escaped values like \\" @@ -13,7 +13,7 @@ t.formatSnapshot = obj => (k, v) => { try { return JSON.parse(v) - } catch (_) {} + } catch {} return v }, 2 @@ -23,33 +23,25 @@ t.formatSnapshot = obj => // and make some assertions that should always be true. Sets // the results on t.context for use in child tests const shrinkwrap = async (t, testdir = {}, config = {}, mocks = {}) => { - const { Npm, filteredLogs } = mockNpm(t, mocks) - const npm = new Npm() - await npm.load() - - npm.localPrefix = t.testdir(testdir) - if (config.lockfileVersion) { - npm.config.set('lockfile-version', config.lockfileVersion) - } - if (config.global) { - npm.config.set('global', config.global) - } + const { npm, logs } = await loadMockNpm(t, { + mocks, + config, + testdir, + }) await npm.exec('shrinkwrap', []) - const newFile = resolve(npm.localPrefix, 'npm-shrinkwrap.json') - const oldFile = resolve(npm.localPrefix, 'package-lock.json') - const notices = filteredLogs('notice') - const warnings = filteredLogs('warn') + const newFile = resolve(npm.prefix, 'npm-shrinkwrap.json') + const oldFile = resolve(npm.prefix, 'package-lock.json') t.notOk(fs.existsSync(oldFile), 'package-lock is always deleted') - t.same(warnings, [], 'no warnings') + t.same(logs.warn, [], 'no warnings') t.teardown(() => delete t.context) t.context = { localPrefix: testdir, config, shrinkwrap: JSON.parse(fs.readFileSync(newFile)), - logs: notices, + logs: logs.notice.map(([, m]) => m), } } @@ -58,8 +50,8 @@ const shrinkwrap = async (t, testdir = {}, config = {}, mocks = {}) => { const shrinkwrapMatrix = async (t, file, assertions) => { const ancient = JSON.stringify({ lockfileVersion: 1 }) const existing = JSON.stringify({ lockfileVersion: 2 }) - const upgrade = { lockfileVersion: 3 } - const downgrade = { lockfileVersion: 1 } + const upgrade = { 'lockfile-version': 3 } + const downgrade = { 'lockfile-version': 1 } let ancientDir = {} let existingDir = null diff --git a/test/lib/commands/star.js b/test/lib/commands/star.js index 13838bb10..9a4903642 100644 --- a/test/lib/commands/star.js +++ b/test/lib/commands/star.js @@ -15,9 +15,9 @@ const npm = mockNpm({ }, }) const npmFetch = { json: noop } -const npmlog = { error: noop, info: noop, verbose: noop } +const log = { error: noop, info: noop, verbose: noop } const mocks = { - npmlog, + 'proc-log': log, 'npm-registry-fetch': npmFetch, '../../../lib/utils/get-identity.js': async () => 'foo', '../../../lib/utils/usage.js': () => 'usage instructions', @@ -29,7 +29,7 @@ const star = new Star(npm) t.afterEach(() => { config.unicode = false config['star.unstar'] = false - npmlog.info = noop + log.info = noop result = '' }) @@ -53,7 +53,7 @@ t.test('star a package', async t => { : {} ), }) - npmlog.info = (title, msg, id) => { + log.info = (title, msg, id) => { t.equal(title, 'star', 'should use expected title') t.equal(msg, 'starring', 'should use expected msg') t.equal(id, pkgName, 'should use expected id') @@ -78,7 +78,7 @@ t.test('unstar a package', async t => { : { foo: true } ), }) - npmlog.info = (title, msg, id) => { + log.info = (title, msg, id) => { t.equal(title, 'unstar', 'should use expected title') t.equal(msg, 'unstarring', 'should use expected msg') t.equal(id, pkgName, 'should use expected id') diff --git a/test/lib/commands/stars.js b/test/lib/commands/stars.js index 4ed643858..959739653 100644 --- a/test/lib/commands/stars.js +++ b/test/lib/commands/stars.js @@ -11,9 +11,9 @@ const npm = { }, } const npmFetch = { json: noop } -const npmlog = { warn: noop } +const log = { warn: noop } const mocks = { - npmlog, + 'proc-log': log, 'npm-registry-fetch': npmFetch, '../../../lib/utils/get-identity.js': async () => 'foo', '../../../lib/utils/usage.js': () => 'usage instructions', @@ -24,7 +24,7 @@ const stars = new Stars(npm) t.afterEach(() => { npm.config = { get () {} } - npmlog.warn = noop + log.warn = noop result = '' }) @@ -81,7 +81,7 @@ t.test('unauthorized request', async t => { ) } - npmlog.warn = (title, msg) => { + log.warn = (title, msg) => { t.equal(title, 'stars', 'should use expected title') t.equal( msg, @@ -108,7 +108,7 @@ t.test('unexpected error', async t => { throw new Error('ERROR') } - npmlog.warn = (title, msg) => { + log.warn = (title, msg) => { throw new Error('Should not output extra warning msgs') } @@ -123,7 +123,7 @@ t.test('no pkg starred', async t => { t.plan(2) npmFetch.json = async (uri, opts) => ({ rows: [] }) - npmlog.warn = (title, msg) => { + log.warn = (title, msg) => { t.equal(title, 'stars', 'should use expected title') t.equal( msg, diff --git a/test/lib/commands/start.js b/test/lib/commands/start.js index 1f26f38ea..4f7dc366d 100644 --- a/test/lib/commands/start.js +++ b/test/lib/commands/start.js @@ -1,6 +1,6 @@ const t = require('tap') const spawk = require('spawk') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') spawk.preventUnmatched() t.teardown(() => { @@ -12,22 +12,23 @@ t.teardown(() => { // pretty specific internals of runScript const makeSpawnArgs = require('@npmcli/run-script/lib/make-spawn-args.js') -t.test('should run stop script from package.json', async t => { - const prefix = t.testdir({ - 'package.json': JSON.stringify({ - name: 'x', - version: '1.2.3', - scripts: { - start: 'node ./test-start.js', - }, - }), +t.test('should run start script from package.json', async t => { + t.plan(2) + const { npm } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: 'x', + version: '1.2.3', + scripts: { + start: 'node ./test-start.js', + }, + }), + }, + config: { + loglevel: 'silent', + }, }) - const { Npm } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.log.level = 'silent' - npm.localPrefix = prefix - const [scriptShell] = makeSpawnArgs({ path: prefix }) + const [scriptShell] = makeSpawnArgs({ path: npm.prefix }) const script = spawk.spawn(scriptShell, (args) => { t.ok(args.includes('node ./test-start.js "foo"'), 'ran start script with extra args') return true diff --git a/test/lib/commands/stop.js b/test/lib/commands/stop.js index 4f189449b..53d057b71 100644 --- a/test/lib/commands/stop.js +++ b/test/lib/commands/stop.js @@ -1,6 +1,6 @@ const t = require('tap') const spawk = require('spawk') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') spawk.preventUnmatched() t.teardown(() => { @@ -13,21 +13,21 @@ t.teardown(() => { const makeSpawnArgs = require('@npmcli/run-script/lib/make-spawn-args.js') t.test('should run stop script from package.json', async t => { - const prefix = t.testdir({ - 'package.json': JSON.stringify({ - name: 'x', - version: '1.2.3', - scripts: { - stop: 'node ./test-stop.js', - }, - }), + const { npm } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: 'x', + version: '1.2.3', + scripts: { + stop: 'node ./test-stop.js', + }, + }), + }, + config: { + loglevel: 'silent', + }, }) - const { Npm } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.log.level = 'silent' - npm.localPrefix = prefix - const [scriptShell] = makeSpawnArgs({ path: prefix }) + const [scriptShell] = makeSpawnArgs({ path: npm.prefix }) const script = spawk.spawn(scriptShell, (args) => { t.ok(args.includes('node ./test-stop.js "foo"'), 'ran stop script with extra args') return true diff --git a/test/lib/commands/test.js b/test/lib/commands/test.js index 4e5ce289b..a3dbd3ff4 100644 --- a/test/lib/commands/test.js +++ b/test/lib/commands/test.js @@ -1,6 +1,6 @@ const t = require('tap') const spawk = require('spawk') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') spawk.preventUnmatched() t.teardown(() => { @@ -12,22 +12,22 @@ t.teardown(() => { // pretty specific internals of runScript const makeSpawnArgs = require('@npmcli/run-script/lib/make-spawn-args.js') -t.test('should run stop script from package.json', async t => { - const prefix = t.testdir({ - 'package.json': JSON.stringify({ - name: 'x', - version: '1.2.3', - scripts: { - test: 'node ./test-test.js', - }, - }), +t.test('should run test script from package.json', async t => { + const { npm } = await loadMockNpm(t, { + testdir: { + 'package.json': JSON.stringify({ + name: 'x', + version: '1.2.3', + scripts: { + test: 'node ./test-test.js', + }, + }), + }, + config: { + loglevel: 'silent', + }, }) - const { Npm } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.log.level = 'silent' - npm.localPrefix = prefix - const [scriptShell] = makeSpawnArgs({ path: prefix }) + const [scriptShell] = makeSpawnArgs({ path: npm.prefix }) const script = spawk.spawn(scriptShell, (args) => { t.ok(args.includes('node ./test-test.js "foo"'), 'ran test script with extra args') return true diff --git a/test/lib/commands/token.js b/test/lib/commands/token.js index 6d0dc9d7e..65a094a0b 100644 --- a/test/lib/commands/token.js +++ b/test/lib/commands/token.js @@ -3,25 +3,24 @@ const t = require('tap') const mocks = { profile: {}, output: () => {}, - log: {}, readUserInfo: {}, } const npm = { output: (...args) => mocks.output(...args), } -const Token = t.mock('../../../lib/commands/token.js', { +const mockToken = (otherMocks) => t.mock('../../../lib/commands/token.js', { '../../../lib/utils/otplease.js': (opts, fn) => { return Promise.resolve().then(() => fn(opts)) }, '../../../lib/utils/read-user-info.js': mocks.readUserInfo, 'npm-profile': mocks.profile, - npmlog: mocks.log, + ...otherMocks, }) -const token = new Token(npm) +const tokenWithMocks = (options = {}) => { + const { log, ...mockRequests } = options -const tokenWithMocks = mockRequests => { for (const mod in mockRequests) { if (mod === 'npm') { mockRequests.npm = { ...npm, ...mockRequests.npm } @@ -50,13 +49,24 @@ const tokenWithMocks = mockRequests => { } } - const token = new Token(mockRequests.npm || npm) + const MockedToken = mockToken(log ? { + 'proc-log': { + info: log.info, + }, + npmlog: { + gauge: log.gauge, + newItem: log.newItem, + }, + } : {}) + const token = new MockedToken(mockRequests.npm || npm) return [token, reset] } t.test('completion', t => { t.plan(5) + const [token] = tokenWithMocks() + const testComp = (argv, expect) => { t.resolveMatch(token.completion({ conf: { argv: { remain: argv } } }), expect, argv.join(' ')) } @@ -74,7 +84,7 @@ t.test('completion', t => { t.test('token foobar', async t => { t.plan(2) - const [, reset] = tokenWithMocks({ + const [token, reset] = tokenWithMocks({ log: { gauge: { show: name => { diff --git a/test/lib/commands/unpublish.js b/test/lib/commands/unpublish.js index 6ac206753..1424adf5c 100644 --- a/test/lib/commands/unpublish.js +++ b/test/lib/commands/unpublish.js @@ -17,7 +17,6 @@ const testDir = t.testdir({ const npm = mockNpm({ localPrefix: testDir, - log: { silly () {}, verbose () {} }, config, output: (...msg) => { result += msg.join('\n') @@ -30,10 +29,10 @@ const mocks = { 'npm-registry-fetch': { json: noop }, '../../../lib/utils/otplease.js': async (opts, fn) => fn(opts), '../../../lib/utils/get-identity.js': async () => 'foo', + 'proc-log': { silly () {}, verbose () {} }, } t.afterEach(() => { - npm.log = { silly () {}, verbose () {} } npm.localPrefix = testDir result = '' config['dry-run'] = false @@ -44,7 +43,7 @@ t.afterEach(() => { t.test('no args --force', async t => { config.force = true - npm.log = { + const log = { silly (title) { t.equal(title, 'unpublish', 'should silly log args') }, @@ -74,6 +73,7 @@ t.test('no args --force', async t => { const Unpublish = t.mock('../../../lib/commands/unpublish.js', { ...mocks, libnpmpublish, + 'proc-log': log, }) const unpublish = new Unpublish(npm) @@ -147,7 +147,7 @@ t.test('too many args', async t => { }) t.test('unpublish <pkg>@version', async t => { - npm.log = { + const log = { silly (title, key, value) { t.equal(title, 'unpublish', 'should silly log args') if (key === 'spec') { @@ -172,6 +172,7 @@ t.test('unpublish <pkg>@version', async t => { const Unpublish = t.mock('../../../lib/commands/unpublish.js', { ...mocks, libnpmpublish, + 'proc-log': log, }) const unpublish = new Unpublish(npm) diff --git a/test/lib/commands/update.js b/test/lib/commands/update.js index 6ca6dbc87..aecb2c32b 100644 --- a/test/lib/commands/update.js +++ b/test/lib/commands/update.js @@ -9,12 +9,10 @@ const config = { const noop = () => null const npm = mockNpm({ globalDir: '', - log: noop, config, prefix: '', }) const mocks = { - npmlog: { warn () {} }, '@npmcli/arborist': class { reify () {} }, @@ -29,22 +27,23 @@ t.afterEach(() => { }) t.test('no args', async t => { - t.plan(3) + t.plan(4) npm.prefix = '/project/a' class Arborist { constructor (args) { + const { log, ...rest } = args t.same( - args, + rest, { ...npm.flatOptions, path: npm.prefix, - log: noop, workspaces: null, }, 'should call arborist contructor with expected args' ) + t.match(log, {}, 'log is passed in') } reify ({ update }) { @@ -65,22 +64,23 @@ t.test('no args', async t => { }) t.test('with args', async t => { - t.plan(3) + t.plan(4) npm.prefix = '/project/a' class Arborist { constructor (args) { + const { log, ...rest } = args t.same( - args, + rest, { ...npm.flatOptions, path: npm.prefix, - log: noop, workspaces: null, }, 'should call arborist contructor with expected args' ) + t.match(log, {}, 'log is passed in') } reify ({ update }) { @@ -108,7 +108,7 @@ t.test('update --depth=<number>', async t => { const Update = t.mock('../../../lib/commands/update.js', { ...mocks, - npmlog: { + 'proc-log': { warn: (title, msg) => { t.equal(title, 'update', 'should print expected title') t.match( @@ -125,7 +125,7 @@ t.test('update --depth=<number>', async t => { }) t.test('update --global', async t => { - t.plan(2) + t.plan(3) const normalizePath = p => p.replace(/\\+/g, '/') const redactCwd = (path) => normalizePath(path) @@ -137,13 +137,15 @@ t.test('update --global', async t => { class Arborist { constructor (args) { - const { path, ...opts } = args + const { path, log, ...rest } = args t.same( - opts, - { ...npm.flatOptions, log: noop, workspaces: undefined }, + rest, + { ...npm.flatOptions, workspaces: undefined }, 'should call arborist contructor with expected options' ) + t.match(log, {}, 'log is passed in') + t.equal( redactCwd(path), '{CWD}/global/lib', diff --git a/test/lib/commands/version.js b/test/lib/commands/version.js index 6603b5810..980353897 100644 --- a/test/lib/commands/version.js +++ b/test/lib/commands/version.js @@ -1,5 +1,6 @@ const t = require('tap') const { fake: mockNpm } = require('../../fixtures/mock-npm') +const mockGlobals = require('../../fixtures/mock-globals.js') let result = [] @@ -26,294 +27,301 @@ const mocks = { const Version = t.mock('../../../lib/commands/version.js', mocks) const version = new Version(npm) -const _processVersions = process.versions t.afterEach(() => { config.json = false npm.prefix = '' - process.versions = _processVersions result = [] }) -t.test('no args', async t => { - const prefix = t.testdir({ - 'package.json': JSON.stringify({ - name: 'test-version-no-args', - version: '3.2.1', - }), - }) - npm.prefix = prefix - Object.defineProperty(process, 'versions', { value: { node: '1.0.0' } }) - - await version.exec([]) - - t.same( - result, - [ - { - 'test-version-no-args': '3.2.1', - node: '1.0.0', - npm: '1.0.0', - }, - ], - 'should output expected values for various versions in npm' - ) -}) - -t.test('too many args', async t => { - await t.rejects( - version.exec(['foo', 'bar']), - /npm version/, - 'should throw usage instructions error' - ) -}) - -t.test('completion', async t => { - const testComp = async (argv, expect) => { - const res = await version.completion({ conf: { argv: { remain: argv } } }) - t.strictSame(res, expect, argv.join(' ')) - } - - await testComp( - ['npm', 'version'], - ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease', 'from-git'] - ) - await testComp(['npm', 'version', 'major'], []) - - t.end() -}) - -t.test('failure reading package.json', async t => { - const prefix = t.testdir({}) - npm.prefix = prefix - - await version.exec([]) - - t.same( - result, - [ - { - npm: '1.0.0', - node: '1.0.0', - }, - ], - 'should not have package name on returning object' - ) -}) - -t.test('--json option', async t => { - const prefix = t.testdir({}) - config.json = true - npm.prefix = prefix - Object.defineProperty(process, 'versions', { value: {} }) - - await version.exec([]) - t.same(result, ['{\n "npm": "1.0.0"\n}'], 'should return json stringified result') -}) +t.test('node@1', t => { + mockGlobals(t, { 'process.versions': { node: '1.0.0' } }, { replace: true }) -t.test('with one arg', async t => { - const Version = t.mock('../../../lib/commands/version.js', { - ...mocks, - libnpmversion: (arg, opts) => { - t.equal(arg, 'major', 'should forward expected value') - t.same( - opts, - { - path: '', - }, - 'should forward expected options' - ) - return '4.0.0' - }, - }) - const version = new Version(npm) - - await version.exec(['major']) - t.same(result, ['v4.0.0'], 'outputs the new version prefixed by the tagVersionPrefix') -}) + t.test('no args', async t => { + const prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-version-no-args', + version: '3.2.1', + }), + }) + npm.prefix = prefix -t.test('workspaces', async t => { - t.teardown(() => { - npm.localPrefix = '' - npm.prefix = '' - }) + await version.exec([]) - t.test('no args, all workspaces', async t => { - const testDir = t.testdir({ - 'package.json': JSON.stringify( - { - name: 'workspaces-test', - version: '1.0.0', - workspaces: ['workspace-a', 'workspace-b'], - }, - null, - 2 - ), - 'workspace-a': { - 'package.json': JSON.stringify({ - name: 'workspace-a', - version: '1.0.0', - }), - }, - 'workspace-b': { - 'package.json': JSON.stringify({ - name: 'workspace-b', - version: '1.0.0', - }), - }, - }) - npm.localPrefix = testDir - npm.prefix = testDir - const version = new Version(npm) - await version.execWorkspaces([], []) t.same( result, [ { - 'workspaces-test': '1.0.0', - 'workspace-a': '1.0.0', - 'workspace-b': '1.0.0', + 'test-version-no-args': '3.2.1', + node: '1.0.0', npm: '1.0.0', }, ], - 'outputs includes main package and workspace versions' + 'should output expected values for various versions in npm' ) }) - t.test('no args, single workspaces', async t => { - const testDir = t.testdir({ - 'package.json': JSON.stringify( - { - name: 'workspaces-test', - version: '1.0.0', - workspaces: ['workspace-a', 'workspace-b'], - }, - null, - 2 - ), - 'workspace-a': { - 'package.json': JSON.stringify({ - name: 'workspace-a', - version: '1.0.0', - }), - }, - 'workspace-b': { - 'package.json': JSON.stringify({ - name: 'workspace-b', - version: '1.0.0', - }), - }, - }) - npm.localPrefix = testDir - npm.prefix = testDir - const version = new Version(npm) - await version.execWorkspaces([], ['workspace-a']) - t.same( - result, - [ - { - 'workspaces-test': '1.0.0', - 'workspace-a': '1.0.0', - npm: '1.0.0', - }, - ], - 'outputs includes main package and requested workspace versions' + t.test('too many args', async t => { + await t.rejects( + version.exec(['foo', 'bar']), + /npm version/, + 'should throw usage instructions error' ) }) - t.test('no args, all workspaces, workspace with missing name or version', async t => { - const testDir = t.testdir({ - 'package.json': JSON.stringify( - { - name: 'workspaces-test', - version: '1.0.0', - workspaces: ['workspace-a', 'workspace-b', 'workspace-c'], - }, - null, - 2 - ), - 'workspace-a': { - 'package.json': JSON.stringify({ - name: 'workspace-a', - version: '1.0.0', - }), - }, - 'workspace-b': { - 'package.json': JSON.stringify({ - name: 'workspace-b', - }), - }, - 'workspace-c': { - 'package.json': JSON.stringify({ - version: '1.0.0', - }), - }, - }) - npm.localPrefix = testDir - npm.prefix = testDir - const version = new Version(npm) - await version.execWorkspaces([], []) + t.test('completion', async t => { + const testComp = async (argv, expect) => { + const res = await version.completion({ conf: { argv: { remain: argv } } }) + t.strictSame(res, expect, argv.join(' ')) + } + + await testComp( + ['npm', 'version'], + ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease', 'from-git'] + ) + await testComp(['npm', 'version', 'major'], []) + + t.end() + }) + + t.test('failure reading package.json', async t => { + const prefix = t.testdir({}) + npm.prefix = prefix + + await version.exec([]) + t.same( result, [ { - 'workspaces-test': '1.0.0', - 'workspace-a': '1.0.0', npm: '1.0.0', + node: '1.0.0', }, ], - 'outputs includes main package and valid workspace versions' + 'should not have package name on returning object' ) }) + t.end() +}) - t.test('with one arg, all workspaces', async t => { - const libNpmVersionArgs = [] - const testDir = t.testdir({ - 'package.json': JSON.stringify( - { - name: 'workspaces-test', - version: '1.0.0', - workspaces: ['workspace-a', 'workspace-b'], - }, - null, - 2 - ), - 'workspace-a': { - 'package.json': JSON.stringify({ - name: 'workspace-a', - version: '1.0.0', - }), - }, - 'workspace-b': { - 'package.json': JSON.stringify({ - name: 'workspace-b', - version: '1.0.0', - }), - }, - }) +t.test('empty versions', t => { + mockGlobals(t, { 'process.versions': {} }, { replace: true }) + + t.test('--json option', async t => { + const prefix = t.testdir({}) + config.json = true + npm.prefix = prefix + + await version.exec([]) + t.same(result, ['{\n "npm": "1.0.0"\n}'], 'should return json stringified result') + }) + + t.test('with one arg', async t => { const Version = t.mock('../../../lib/commands/version.js', { ...mocks, libnpmversion: (arg, opts) => { - libNpmVersionArgs.push([arg, opts]) - return '2.0.0' + t.equal(arg, 'major', 'should forward expected value') + t.same( + opts, + { + path: '', + }, + 'should forward expected options' + ) + return '4.0.0' }, }) - npm.localPrefix = testDir - npm.prefix = testDir const version = new Version(npm) - await version.execWorkspaces(['major'], []) - t.same( - result, - ['workspace-a', 'v2.0.0', 'workspace-b', 'v2.0.0'], - 'outputs the new version for only the workspaces prefixed by the tagVersionPrefix' - ) + await version.exec(['major']) + t.same(result, ['v4.0.0'], 'outputs the new version prefixed by the tagVersionPrefix') }) - t.test('too many args', async t => { - await t.rejects( - version.execWorkspaces(['foo', 'bar'], []), - /npm version/, - 'should throw usage instructions error' - ) + t.test('workspaces', async t => { + t.teardown(() => { + npm.localPrefix = '' + npm.prefix = '' + }) + + t.test('no args, all workspaces', async t => { + const testDir = t.testdir({ + 'package.json': JSON.stringify( + { + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + }, + null, + 2 + ), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.0.0', + }), + }, + }) + npm.localPrefix = testDir + npm.prefix = testDir + const version = new Version(npm) + await version.execWorkspaces([], []) + t.same( + result, + [ + { + 'workspaces-test': '1.0.0', + 'workspace-a': '1.0.0', + 'workspace-b': '1.0.0', + npm: '1.0.0', + }, + ], + 'outputs includes main package and workspace versions' + ) + }) + + t.test('no args, single workspaces', async t => { + const testDir = t.testdir({ + 'package.json': JSON.stringify( + { + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + }, + null, + 2 + ), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.0.0', + }), + }, + }) + npm.localPrefix = testDir + npm.prefix = testDir + const version = new Version(npm) + await version.execWorkspaces([], ['workspace-a']) + t.same( + result, + [ + { + 'workspaces-test': '1.0.0', + 'workspace-a': '1.0.0', + npm: '1.0.0', + }, + ], + 'outputs includes main package and requested workspace versions' + ) + }) + + t.test('no args, all workspaces, workspace with missing name or version', async t => { + const testDir = t.testdir({ + 'package.json': JSON.stringify( + { + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b', 'workspace-c'], + }, + null, + 2 + ), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + }), + }, + 'workspace-c': { + 'package.json': JSON.stringify({ + version: '1.0.0', + }), + }, + }) + npm.localPrefix = testDir + npm.prefix = testDir + const version = new Version(npm) + await version.execWorkspaces([], []) + t.same( + result, + [ + { + 'workspaces-test': '1.0.0', + 'workspace-a': '1.0.0', + npm: '1.0.0', + }, + ], + 'outputs includes main package and valid workspace versions' + ) + }) + + t.test('with one arg, all workspaces', async t => { + const libNpmVersionArgs = [] + const testDir = t.testdir({ + 'package.json': JSON.stringify( + { + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + }, + null, + 2 + ), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.0.0', + }), + }, + }) + const Version = t.mock('../../../lib/commands/version.js', { + ...mocks, + libnpmversion: (arg, opts) => { + libNpmVersionArgs.push([arg, opts]) + return '2.0.0' + }, + }) + npm.localPrefix = testDir + npm.prefix = testDir + const version = new Version(npm) + + await version.execWorkspaces(['major'], []) + t.same( + result, + ['workspace-a', 'v2.0.0', 'workspace-b', 'v2.0.0'], + 'outputs the new version for only the workspaces prefixed by the tagVersionPrefix' + ) + }) + + t.test('too many args', async t => { + await t.rejects( + version.execWorkspaces(['foo', 'bar'], []), + /npm version/, + 'should throw usage instructions error' + ) + }) }) + + t.end() }) diff --git a/test/lib/commands/view.js b/test/lib/commands/view.js index 728787ec4..035490a79 100644 --- a/test/lib/commands/view.js +++ b/test/lib/commands/view.js @@ -1,6 +1,7 @@ const t = require('tap') -t.cleanSnapshot = str => str.replace(/published .*? ago/g, 'published {TIME} ago') +t.cleanSnapshot = str => str + .replace(/(published ).*?( ago)/g, '$1{TIME}$2') // run the same as tap does when running directly with node process.stdout.columns = undefined @@ -17,8 +18,8 @@ const cleanLogs = () => { console.log = fn } -// 25 hours ago -const yesterday = new Date(Date.now() - 1000 * 60 * 60 * 25) +// 3 days. its never yesterday and never a week ago +const yesterday = new Date(Date.now() - 1000 * 60 * 60 * 24 * 3) const packument = (nv, opts) => { if (!opts.fullMetadata) { @@ -564,6 +565,12 @@ t.test('workspaces', async t => { pacote: { packument, }, + 'proc-log': { + warn: (msg) => { + warnMsg = msg + }, + silly: () => {}, + }, }) const config = { unicode: false, @@ -571,11 +578,6 @@ t.test('workspaces', async t => { } let warnMsg const npm = mockNpm({ - log: { - warn: (msg) => { - warnMsg = msg - }, - }, config, localPrefix: testDir, }) diff --git a/test/lib/commands/whoami.js b/test/lib/commands/whoami.js index dc6144ec1..66c3f0c6b 100644 --- a/test/lib/commands/whoami.js +++ b/test/lib/commands/whoami.js @@ -1,26 +1,24 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm') +const { load: _loadMockNpm } = require('../../fixtures/mock-npm') const username = 'foo' -const { joinedOutput, Npm } = mockNpm(t, { - '../../lib/utils/get-identity.js': () => Promise.resolve(username), -}) -const npm = new Npm() - -t.before(async () => { - await npm.load() +const loadMockNpm = (t, options) => _loadMockNpm(t, { + mocks: { + '../../lib/utils/get-identity.js': () => Promise.resolve(username), + }, + ...options, }) t.test('npm whoami', async (t) => { + const { npm, joinedOutput } = await loadMockNpm(t) await npm.exec('whoami', []) t.equal(joinedOutput(), username, 'should print username') }) t.test('npm whoami --json', async (t) => { - t.teardown(() => { - npm.config.set('json', false) + const { npm, joinedOutput } = await loadMockNpm(t, { + config: { json: true }, }) - npm.config.set('json', true) await npm.exec('whoami', []) t.equal(JSON.parse(joinedOutput()), username, 'should print username') }) diff --git a/test/lib/fixtures/mock-globals.js b/test/lib/fixtures/mock-globals.js new file mode 100644 index 000000000..02566e575 --- /dev/null +++ b/test/lib/fixtures/mock-globals.js @@ -0,0 +1,321 @@ +const t = require('tap') +const mockGlobals = require('../../fixtures/mock-globals') + +const originals = { + platform: process.platform, + error: console.error, + stderrOn: process.stderr.on, + stderrWrite: process.stderr.write, + shell: process.env.SHELL, + home: process.env.HOME, + argv: process.argv, + env: process.env, + setInterval, +} + +t.test('console', async t => { + await t.test('mocks', async (t) => { + const errors = [] + mockGlobals(t, { + 'console.error': (...args) => errors.push(...args), + }) + + console.error(1) + console.error(2) + console.error(3) + t.strictSame(errors, [1, 2, 3], 'i got my errors') + }) + + t.equal(console.error, originals.error) +}) + +t.test('platform', async (t) => { + t.equal(process.platform, originals.platform) + + await t.test('posix', async (t) => { + mockGlobals(t, { 'process.platform': 'posix' }) + t.equal(process.platform, 'posix') + + await t.test('win32 --> woo', async (t) => { + mockGlobals(t, { 'process.platform': 'win32' }) + t.equal(process.platform, 'win32') + + mockGlobals(t, { 'process.platform': 'woo' }) + t.equal(process.platform, 'woo') + }) + + t.equal(process.platform, 'posix') + }) + + t.equal(process.platform, originals.platform) +}) + +t.test('manual reset', async t => { + let errorHandler, data + + const { reset } = mockGlobals(t, { + 'process.stderr.on': (__, handler) => { + errorHandler = handler + reset['process.stderr.on']() + }, + 'process.stderr.write': (chunk, callback) => { + data = chunk + process.nextTick(() => { + errorHandler({ errno: 'EPIPE' }) + callback() + }) + reset['process.stderr.write']() + }, + }) + + await new Promise((res, rej) => { + process.stderr.on('error', er => er.errno === 'EPIPE' ? res() : rej(er)) + process.stderr.write('hey', res) + }) + + t.equal(process.stderr.on, originals.stderrOn) + t.equal(process.stderr.write, originals.stderrWrite) + t.equal(data, 'hey', 'handles EPIPE errors') + t.ok(errorHandler) +}) + +t.test('reset called multiple times', async (t) => { + await t.test('single reset', async t => { + const { reset } = mockGlobals(t, { 'process.platform': 'z' }) + t.equal(process.platform, 'z') + + reset['process.platform']() + t.equal(process.platform, originals.platform) + + reset['process.platform']() + reset['process.platform']() + reset['process.platform']() + t.equal(process.platform, originals.platform) + }) + + t.equal(process.platform, originals.platform) +}) + +t.test('object mode', async t => { + await t.test('mocks', async t => { + const home = t.testdir() + + mockGlobals(t, { + process: { + stderr: { + on: '1', + }, + env: { + HOME: home, + }, + }, + }) + + t.equal(process.stderr.on, '1') + t.equal(process.env.HOME, home) + }) + + t.equal(process.env.HOME, originals.home) + t.equal(process.stderr.write, originals.stderrWrite) +}) + +t.test('mixed object/string mode', async t => { + await t.test('mocks', async t => { + const home = t.testdir() + + mockGlobals(t, { + 'process.env': { + HOME: home, + TEST: '1', + }, + }) + + t.equal(process.env.HOME, home) + t.equal(process.env.TEST, '1') + }) + + t.equal(process.env.HOME, originals.home) + t.equal(process.env.TEST, undefined) +}) + +t.test('conflicting mixed object/string mode', async t => { + await t.test('same key', async t => { + t.throws( + () => mockGlobals(t, { + process: { + env: { + HOME: '1', + TEST: '1', + NODE_ENV: '1', + }, + stderr: { + write: '1', + }, + }, + 'process.env.HOME': '1', + 'process.stderr.write': '1', + }), + /process.env.HOME,process.stderr.write/ + ) + }) + + await t.test('partial overwrite with replace', async t => { + t.throws( + () => mockGlobals(t, { + process: { + env: { + HOME: '1', + TEST: '1', + NODE_ENV: '1', + }, + stderr: { + write: '1', + }, + }, + 'process.env.HOME': '1', + 'process.stderr.write': '1', + }, { replace: true }), + /process -> process.env.HOME,process.stderr.write/ + ) + }) +}) + +t.test('falsy values', async t => { + await t.test('undefined deletes', async t => { + mockGlobals(t, { 'process.platform': undefined }) + t.notOk(Object.prototype.hasOwnProperty.call(process, 'platform')) + t.equal(process.platform, undefined) + }) + + await t.test('null', async t => { + mockGlobals(t, { 'process.platform': null }) + t.ok(Object.prototype.hasOwnProperty.call(process, 'platform')) + t.equal(process.platform, null) + }) + + t.equal(process.platform, originals.platform) +}) + +t.test('date', async t => { + await t.test('mocks', async t => { + mockGlobals(t, { + 'Date.now': () => 100, + 'Date.prototype.toISOString': () => 'DDD', + }) + t.equal(Date.now(), 100) + t.equal(new Date().toISOString(), 'DDD') + }) + + t.ok(Date.now() > 100) + t.ok(new Date().toISOString().includes('T')) +}) + +t.test('argv', async t => { + await t.test('argv', async t => { + mockGlobals(t, { 'process.argv': ['node', 'woo'] }) + t.strictSame(process.argv, ['node', 'woo']) + }) + + t.strictSame(process.argv, originals.argv) +}) + +t.test('replace', async (t) => { + await t.test('env', async t => { + mockGlobals(t, { 'process.env': { HOME: '1' } }, { replace: true }) + t.strictSame(process.env, { HOME: '1' }) + t.equal(Object.keys(process.env).length, 1) + }) + + await t.test('setInterval', async t => { + mockGlobals(t, { setInterval: 0 }, { replace: true }) + t.strictSame(setInterval, 0) + }) + + t.strictSame(setInterval, originals.setInterval) + t.strictSame(process.env, originals.env) +}) + +t.test('multiple mocks and resets', async (t) => { + const initial = 'a' + const platforms = ['b', 'c', 'd', 'e', 'f', 'g'] + + await t.test('first in, first out', async t => { + mockGlobals(t, { 'process.platform': initial }) + t.equal(process.platform, initial) + + await t.test('platforms', async (t) => { + const resets = platforms.map((platform) => { + const { reset } = mockGlobals(t, { 'process.platform': platform }) + t.equal(process.platform, platform) + return reset['process.platform'] + }).reverse() + + ;[...platforms.reverse()].forEach((platform, index) => { + const reset = resets[index] + const nextPlatform = index === platforms.length - 1 ? initial : platforms[index + 1] + t.equal(process.platform, platform) + reset() + t.equal(process.platform, nextPlatform, 'first reset') + reset() + reset() + t.equal(process.platform, nextPlatform, 'multiple resets are indempotent') + }) + }) + + t.equal(process.platform, initial) + }) + + await t.test('last in,first out', async t => { + mockGlobals(t, { 'process.platform': initial }) + t.equal(process.platform, initial) + + await t.test('platforms', async (t) => { + const resets = platforms.map((platform) => { + const { reset } = mockGlobals(t, { 'process.platform': platform }) + t.equal(process.platform, platform) + return reset['process.platform'] + }) + + resets.forEach((reset, index) => { + // Calling a reset out of order removes it from the stack + // but does not change the descriptor so it should still be the + // last in descriptor until there are none left + const lastPlatform = platforms[platforms.length - 1] + const nextPlatform = index === platforms.length - 1 ? initial : lastPlatform + t.equal(process.platform, lastPlatform) + reset() + t.equal(process.platform, nextPlatform, 'multiple resets are indempotent') + reset() + reset() + t.equal(process.platform, nextPlatform, 'multiple resets are indempotent') + }) + }) + + t.equal(process.platform, initial) + }) + + t.test('reset all', async (t) => { + const { teardown } = mockGlobals(t, { 'process.platform': initial }) + + await t.test('platforms', async (t) => { + const resets = platforms.map((p) => { + const { teardown, reset } = mockGlobals(t, { 'process.platform': p }) + t.equal(process.platform, p) + return [ + reset['process.platform'], + teardown, + ] + }) + + resets.forEach(r => r[1]()) + t.equal(process.platform, initial, 'teardown goes to initial value') + + resets.forEach((r) => r[0]()) + t.equal(process.platform, initial, 'calling resets after teardown does nothing') + }) + + t.equal(process.platform, initial) + teardown() + t.equal(process.platform, originals.platform) + }) +}) diff --git a/test/lib/load-all-commands.js b/test/lib/load-all-commands.js index f813e50b2..248c81a30 100644 --- a/test/lib/load-all-commands.js +++ b/test/lib/load-all-commands.js @@ -4,21 +4,16 @@ // renders also ensures that any params we've defined in our commands work. const t = require('tap') const util = require('util') -const { real: mockNpm } = require('../fixtures/mock-npm.js') +const { load: loadMockNpm } = require('../fixtures/mock-npm.js') const { cmdList } = require('../../lib/utils/cmd-list.js') -const { Npm, outputs } = mockNpm(t) -const npm = new Npm() - t.test('load each command', async t => { - t.afterEach(() => { - outputs.length = 0 - }) t.plan(cmdList.length) - await npm.load() - npm.config.set('usage', true) // This makes npm.exec output the usage for (const cmd of cmdList.sort((a, b) => a.localeCompare(b, 'en'))) { t.test(cmd, async t => { + const { npm, outputs } = await loadMockNpm(t, { + config: { usage: true }, + }) const impl = await npm.cmd(cmd) if (impl.completion) { t.type(impl.completion, 'function', 'completion, if present, is a function') diff --git a/test/lib/load-all.js b/test/lib/load-all.js index fb45331ba..e5d7b558c 100644 --- a/test/lib/load-all.js +++ b/test/lib/load-all.js @@ -1,34 +1,31 @@ const t = require('tap') const glob = require('glob') const { resolve } = require('path') -const { real: mockNpm } = require('../fixtures/mock-npm') +const { load: loadMockNpm } = require('../fixtures/mock-npm') const full = process.env.npm_lifecycle_event === 'check-coverage' if (!full) { t.pass('nothing to do here, not checking for full coverage') } else { - const { Npm } = mockNpm(t) - const npm = new Npm() + t.test('load all', async (t) => { + const { npm } = await loadMockNpm(t, { }) - t.teardown(() => { - const exitHandler = require('../../lib/utils/exit-handler.js') - exitHandler.setNpm(npm) - exitHandler() - }) - - t.before(async t => { - await npm.load() - }) + t.teardown(() => { + const exitHandler = require('../../lib/utils/exit-handler.js') + exitHandler.setNpm(npm) + exitHandler() + }) - t.test('load all the files', t => { - // just load all the files so we measure coverage for the missing tests - const dir = resolve(__dirname, '../../lib') - for (const f of glob.sync(`${dir}/**/*.js`)) { - require(f) - t.pass('loaded ' + f) - } - t.pass('loaded all files') - t.end() + t.test('load all the files', t => { + // just load all the files so we measure coverage for the missing tests + const dir = resolve(__dirname, '../../lib') + for (const f of glob.sync(`${dir}/**/*.js`)) { + require(f) + t.pass('loaded ' + f) + } + t.pass('loaded all files') + t.end() + }) }) } diff --git a/test/lib/npm.js b/test/lib/npm.js index 1ccd26e37..2a0c5a89d 100644 --- a/test/lib/npm.js +++ b/test/lib/npm.js @@ -1,7 +1,8 @@ const t = require('tap') +const { resolve, dirname } = require('path') -const npmlog = require('npmlog') -const { real: mockNpm } = require('../fixtures/mock-npm.js') +const { load: loadMockNpm } = require('../fixtures/mock-npm.js') +const mockGlobals = require('../fixtures/mock-globals') // delete this so that we don't have configs from the fact that it // is being run by 'npm test' @@ -15,7 +16,7 @@ for (const env of Object.keys(process.env).filter(e => /^npm_/.test(e))) { // if this test is just run directly, which is also acceptable. if (event === 'test') { t.ok( - ['test', 'run-script'].some(i => i === event), + ['test', 'run-script'].some(i => i === process.env[env]), 'should match "npm test" or "npm run test"' ) } else { @@ -25,41 +26,14 @@ for (const env of Object.keys(process.env).filter(e => /^npm_/.test(e))) { delete process.env[env] } -const { resolve, dirname } = require('path') - -const actualPlatform = process.platform -const beWindows = () => { - Object.defineProperty(process, 'platform', { - value: 'win32', - configurable: true, - }) -} -const bePosix = () => { - Object.defineProperty(process, 'platform', { - value: 'posix', - configurable: true, - }) -} -const argv = [...process.argv] - -t.afterEach(() => { +t.afterEach(async (t) => { for (const env of Object.keys(process.env).filter(e => /^npm_/.test(e))) { delete process.env[env] } - process.env.npm_config_cache = CACHE - process.argv = argv - Object.defineProperty(process, 'platform', { - value: actualPlatform, - configurable: true, - }) }) -const CACHE = t.testdir() -process.env.npm_config_cache = CACHE - t.test('not yet loaded', async t => { - const { Npm, logs } = mockNpm(t) - const npm = new Npm() + const { npm, logs } = await loadMockNpm(t, { load: false }) t.match(npm, { started: Number, command: null, @@ -79,8 +53,7 @@ t.test('not yet loaded', async t => { t.test('npm.load', async t => { t.test('load error', async t => { - const { Npm } = mockNpm(t) - const npm = new Npm() + const { npm } = await loadMockNpm(t, { load: false }) const loadError = new Error('load error') npm.config.load = async () => { throw loadError @@ -103,32 +76,28 @@ t.test('npm.load', async t => { }) t.test('basic loading', async t => { - const { Npm, logs } = mockNpm(t) - const npm = new Npm() - const dir = t.testdir({ - node_modules: {}, + const { npm, logs, prefix: dir, cache } = await loadMockNpm(t, { + testdir: { node_modules: {} }, }) - await npm.load() + t.equal(npm.loaded, true) t.equal(npm.config.loaded, true) t.equal(npm.config.get('force'), false) t.ok(npm.usage, 'has usage') - npm.config.set('prefix', dir) t.match(npm, { flatOptions: {}, }) - t.match(logs, [ - ['timing', 'npm:load', /Completed in [0-9.]+ms/], + t.match(logs.timing.filter(([p]) => p === 'npm:load'), [ + ['npm:load', /Completed in [0-9.]+ms/], ]) - bePosix() - t.equal(resolve(npm.cache), resolve(CACHE), 'cache is cache') + mockGlobals(t, { process: { platform: 'posix' } }) + t.equal(resolve(npm.cache), resolve(cache), 'cache is cache') const newCache = t.testdir() npm.cache = newCache t.equal(npm.config.get('cache'), newCache, 'cache setter sets config') t.equal(npm.cache, newCache, 'cache getter gets new config') - t.equal(npm.log, npmlog, 'npmlog getter') t.equal(npm.lockfileVersion, 2, 'lockfileVersion getter') t.equal(npm.prefix, npm.localPrefix, 'prefix is local prefix') t.not(npm.prefix, npm.globalPrefix, 'prefix is not global prefix') @@ -160,10 +129,9 @@ t.test('npm.load', async t => { t.equal(npm.bin, npm.globalBin, 'bin is global bin after prefix setter') t.not(npm.bin, npm.localBin, 'bin is not local bin after prefix setter') - beWindows() + mockGlobals(t, { process: { platform: 'win32' } }) t.equal(npm.bin, npm.globalBin, 'bin is global bin in windows mode') t.equal(npm.dir, npm.globalDir, 'dir is global dir in windows mode') - bePosix() const tmp = npm.tmp t.match(tmp, String, 'npm.tmp is a string') @@ -171,13 +139,12 @@ t.test('npm.load', async t => { }) t.test('forceful loading', async t => { - process.argv = [...process.argv, '--force', '--color', 'always'] - const { Npm, logs } = mockNpm(t) - const npm = new Npm() - await npm.load() - t.match(logs.filter(l => l[0] !== 'timing'), [ + mockGlobals(t, { + 'process.argv': [...process.argv, '--force', '--color', 'always'], + }) + const { logs } = await loadMockNpm(t) + t.match(logs.warn, [ [ - 'warn', 'using --force', 'Recommended protections disabled.', ], @@ -185,54 +152,42 @@ t.test('npm.load', async t => { }) t.test('node is a symlink', async t => { - const node = actualPlatform === 'win32' ? 'node.exe' : 'node' - const dir = t.testdir({ - '.npmrc': 'foo = bar', - bin: t.fixture('symlink', dirname(process.execPath)), + const node = process.platform === 'win32' ? 'node.exe' : 'node' + mockGlobals(t, { + 'process.argv': [ + node, + process.argv[1], + '--usage', + '--scope=foo', + 'token', + 'revoke', + 'blergggg', + ], }) - - const PATH = process.env.PATH || process.env.Path - process.env.PATH = resolve(dir, 'bin') - process.argv = [ - node, - process.argv[1], - '--prefix', dir, - '--userconfig', `${dir}/.npmrc`, - '--usage', - '--scope=foo', - 'token', - 'revoke', - 'blergggg', - ] - - t.teardown(() => { - process.env.PATH = PATH + const { npm, logs, outputs, prefix } = await loadMockNpm(t, { + testdir: { + bin: t.fixture('symlink', dirname(process.execPath)), + }, + globals: ({ prefix }) => ({ + 'process.env.PATH': resolve(prefix, 'bin'), + }), }) - const { Npm, logs, outputs } = mockNpm(t) - const npm = new Npm() - await npm.load() t.equal(npm.config.get('scope'), '@foo', 'added the @ sign to scope') - t.match(logs.filter(l => l[0] !== 'timing' || !/^config:/.test(l[1])), [ - [ - 'timing', - 'npm:load:whichnode', - /Completed in [0-9.]+ms/, - ], - [ - 'verbose', - 'node symlink', - resolve(dir, 'bin', node), - ], - [ - 'timing', - 'npm:load', - /Completed in [0-9.]+ms/, - ], + t.match([ + ...logs.timing.filter(([p]) => p === 'npm:load:whichnode'), + ...logs.verbose, + ...logs.timing.filter(([p]) => p === 'npm:load'), + ], [ + ['npm:load:whichnode', /Completed in [0-9.]+ms/], + ['node symlink', resolve(prefix, 'bin', node)], + ['logfile', /.*-debug-0.log/], + ['npm:load', /Completed in [0-9.]+ms/], ]) - t.equal(process.execPath, resolve(dir, 'bin', node)) + t.equal(process.execPath, resolve(prefix, 'bin', node)) outputs.length = 0 + logs.length = 0 await npm.exec('ll', []) t.equal(npm.command, 'll', 'command set to first npm command') @@ -271,33 +226,34 @@ t.test('npm.load', async t => { }) t.test('--no-workspaces with --workspace', async t => { - const dir = t.testdir({ - packages: { - a: { - 'package.json': JSON.stringify({ - name: 'a', - version: '1.0.0', - scripts: { test: 'echo test a' }, - }), + mockGlobals(t, { + 'process.argv': [ + process.execPath, + process.argv[1], + '--color', 'false', + '--workspaces', 'false', + '--workspace', 'a', + ], + }) + const { npm } = await loadMockNpm(t, { + load: false, + testdir: { + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + scripts: { test: 'echo test a' }, + }), + }, }, + 'package.json': JSON.stringify({ + name: 'root', + version: '1.0.0', + workspaces: ['./packages/*'], + }), }, - 'package.json': JSON.stringify({ - name: 'root', - version: '1.0.0', - workspaces: ['./packages/*'], - }), }) - process.argv = [ - process.execPath, - process.argv[1], - '--userconfig', resolve(dir, '.npmrc'), - '--color', 'false', - '--workspaces', 'false', - '--workspace', 'a', - ] - const { Npm } = mockNpm(t) - const npm = new Npm() - npm.localPrefix = dir await t.rejects( npm.exec('run', []), /Can not use --no-workspaces and --workspace at the same time/ @@ -305,47 +261,40 @@ t.test('npm.load', async t => { }) t.test('workspace-aware configs and commands', async t => { - const dir = t.testdir({ - packages: { - a: { - 'package.json': JSON.stringify({ - name: 'a', - version: '1.0.0', - scripts: { test: 'echo test a' }, - }), - }, - b: { - 'package.json': JSON.stringify({ - name: 'b', - version: '1.0.0', - scripts: { test: 'echo test b' }, - }), + mockGlobals(t, { + 'process.argv': [ + process.execPath, + process.argv[1], + '--color', 'false', + '--workspaces', 'true', + ], + }) + const { npm, outputs } = await loadMockNpm(t, { + testdir: { + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + scripts: { test: 'echo test a' }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + scripts: { test: 'echo test b' }, + }), + }, }, + 'package.json': JSON.stringify({ + name: 'root', + version: '1.0.0', + workspaces: ['./packages/*'], + }), }, - 'package.json': JSON.stringify({ - name: 'root', - version: '1.0.0', - workspaces: ['./packages/*'], - }), - '.npmrc': '', }) - process.argv = [ - process.execPath, - process.argv[1], - '--userconfig', - resolve(dir, '.npmrc'), - '--color', - 'false', - '--workspaces', - 'true', - ] - - const { Npm, outputs } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.localPrefix = dir - // verify that calling the command with a short name still sets // the npm.command property to the full canonical name of the cmd. npm.command = null @@ -368,44 +317,42 @@ t.test('npm.load', async t => { }) t.test('workspaces in global mode', async t => { - const dir = t.testdir({ - packages: { - a: { - 'package.json': JSON.stringify({ - name: 'a', - version: '1.0.0', - scripts: { test: 'echo test a' }, - }), - }, - b: { - 'package.json': JSON.stringify({ - name: 'b', - version: '1.0.0', - scripts: { test: 'echo test b' }, - }), + mockGlobals(t, { + 'process.argv': [ + process.execPath, + process.argv[1], + '--color', + 'false', + '--workspaces', + '--global', + 'true', + ], + }) + const { npm } = await loadMockNpm(t, { + testdir: { + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + scripts: { test: 'echo test a' }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + scripts: { test: 'echo test b' }, + }), + }, }, + 'package.json': JSON.stringify({ + name: 'root', + version: '1.0.0', + workspaces: ['./packages/*'], + }), }, - 'package.json': JSON.stringify({ - name: 'root', - version: '1.0.0', - workspaces: ['./packages/*'], - }), }) - process.argv = [ - process.execPath, - process.argv[1], - '--userconfig', - resolve(dir, '.npmrc'), - '--color', - 'false', - '--workspaces', - '--global', - 'true', - ] - const { Npm } = mockNpm(t) - const npm = new Npm() - await npm.load() - npm.localPrefix = dir // verify that calling the command with a short name still sets // the npm.command property to the full canonical name of the cmd. npm.command = null @@ -418,109 +365,156 @@ t.test('npm.load', async t => { t.test('set process.title', async t => { t.test('basic title setting', async t => { - process.argv = [ - process.execPath, - process.argv[1], - '--usage', - '--scope=foo', - 'ls', - ] - const { Npm } = mockNpm(t) - const npm = new Npm() - await npm.load() + mockGlobals(t, { + 'process.argv': [ + process.execPath, + process.argv[1], + '--usage', + '--scope=foo', + 'ls', + ], + }) + const { npm } = await loadMockNpm(t) t.equal(npm.title, 'npm ls') t.equal(process.title, 'npm ls') }) t.test('do not expose token being revoked', async t => { - process.argv = [ - process.execPath, - process.argv[1], - '--usage', - '--scope=foo', - 'token', - 'revoke', - 'deadbeefcafebad', - ] - const { Npm } = mockNpm(t) - const npm = new Npm() - await npm.load() + mockGlobals(t, { + 'process.argv': [ + process.execPath, + process.argv[1], + '--usage', + '--scope=foo', + 'token', + 'revoke', + 'deadbeefcafebad', + ], + }) + const { npm } = await loadMockNpm(t) t.equal(npm.title, 'npm token revoke ***') t.equal(process.title, 'npm token revoke ***') }) t.test('do show *** unless a token is actually being revoked', async t => { - process.argv = [ - process.execPath, - process.argv[1], - '--usage', - '--scope=foo', - 'token', - 'revoke', - ] - const { Npm } = mockNpm(t) - const npm = new Npm() - await npm.load() + mockGlobals(t, { + 'process.argv': [ + process.execPath, + process.argv[1], + '--usage', + '--scope=foo', + 'token', + 'revoke', + ], + }) + const { npm } = await loadMockNpm(t) t.equal(npm.title, 'npm token revoke') t.equal(process.title, 'npm token revoke') }) }) -t.test('timings', t => { - const { Npm, logs } = mockNpm(t) - const npm = new Npm() - process.emit('time', 'foo') - process.emit('time', 'bar') - t.match(npm.timers.get('foo'), Number, 'foo timer is a number') - t.match(npm.timers.get('bar'), Number, 'foo timer is a number') - process.emit('timeEnd', 'foo') - process.emit('timeEnd', 'bar') - process.emit('timeEnd', 'baz') - t.match(logs, [ - ['timing', 'foo', /Completed in [0-9]+ms/], - ['timing', 'bar', /Completed in [0-9]+ms/], - [ - 'silly', +t.test('debug-log', async t => { + const { npm, debugFile } = await loadMockNpm(t, { load: false }) + + const log1 = ['silly', 'test', 'before load'] + const log2 = ['silly', 'test', 'after load'] + + process.emit('log', ...log1) + await npm.load() + process.emit('log', ...log2) + + const debug = await debugFile() + t.equal(npm.logFiles.length, 1, 'one debug file') + t.match(debug, log1.join(' '), 'before load appears') + t.match(debug, log2.join(' '), 'after load log appears') +}) + +t.test('timings', async t => { + t.test('gets/sets timers', async t => { + const { npm, logs } = await loadMockNpm(t, { load: false }) + process.emit('time', 'foo') + process.emit('time', 'bar') + t.match(npm.unfinishedTimers.get('foo'), Number, 'foo timer is a number') + t.match(npm.unfinishedTimers.get('bar'), Number, 'foo timer is a number') + process.emit('timeEnd', 'foo') + process.emit('timeEnd', 'bar') + process.emit('timeEnd', 'baz') + // npm timer is started by default + process.emit('timeEnd', 'npm') + t.match(logs.timing, [ + ['foo', /Completed in [0-9]+ms/], + ['bar', /Completed in [0-9]+ms/], + ['npm', /Completed in [0-9]+ms/], + ]) + t.match(logs.silly, [[ 'timing', "Tried to end timer that doesn't exist:", 'baz', - ], - ]) - t.notOk(npm.timers.has('foo'), 'foo timer is gone') - t.notOk(npm.timers.has('bar'), 'bar timer is gone') - t.match(npm.timings, { foo: Number, bar: Number }) - t.end() + ]]) + t.notOk(npm.unfinishedTimers.has('foo'), 'foo timer is gone') + t.notOk(npm.unfinishedTimers.has('bar'), 'bar timer is gone') + t.match(npm.finishedTimers, { foo: Number, bar: Number, npm: Number }) + t.end() + }) + + t.test('writes timings file', async t => { + const { npm, timingFile } = await loadMockNpm(t, { + config: { timing: true }, + }) + process.emit('time', 'foo') + process.emit('timeEnd', 'foo') + process.emit('time', 'bar') + npm.unload() + const timings = await timingFile() + t.match(timings, { + command: [], + logfile: String, + logfiles: [String], + version: String, + unfinished: { + bar: [Number, Number], + npm: [Number, Number], + }, + foo: Number, + 'npm:load': Number, + }) + }) + + t.test('does not write timings file with timers:false', async t => { + const { npm, timingFile } = await loadMockNpm(t, { + config: { false: true }, + }) + npm.unload() + await t.rejects(() => timingFile()) + }) }) -t.test('output clears progress and console.logs the message', t => { - const mock = mockNpm(t) - const { Npm, logs } = mock - const npm = new Npm() - npm.output = mock.npmOutput - const { log } = console - const { log: { clearProgress, showProgress } } = npm +t.test('output clears progress and console.logs the message', async t => { + t.plan(2) let showingProgress = true - npm.log.clearProgress = () => showingProgress = false - npm.log.showProgress = () => showingProgress = true - console.log = (...args) => { - t.equal(showingProgress, false, 'should not be showing progress right now') - logs.push(args) - } - t.teardown(() => { - console.log = log - npm.log.showProgress = showProgress - npm.log.clearProgress = clearProgress + const logs = [] + mockGlobals(t, { + 'console.log': (...args) => { + t.equal(showingProgress, false, 'should not be showing progress right now') + logs.push(args) + }, }) - - npm.output('hello') - t.strictSame(logs, [['hello']]) + const { npm } = await loadMockNpm(t, { + load: false, + mocks: { + npmlog: { + clearProgress: () => showingProgress = false, + showProgress: () => showingProgress = true, + }, + }, + }) + npm.originalOutput('hello') + t.match(logs, [['hello']]) t.end() }) t.test('unknown command', async t => { - const mock = mockNpm(t) - const { Npm } = mock - const npm = new Npm() + const { npm } = await loadMockNpm(t, { load: false }) await t.rejects( npm.cmd('thisisnotacommand'), { code: 'EUNKNOWNCOMMAND' } diff --git a/test/lib/utils/audit-error.js b/test/lib/utils/audit-error.js index c683053cb..bcb7d8c16 100644 --- a/test/lib/utils/audit-error.js +++ b/test/lib/utils/audit-error.js @@ -3,14 +3,15 @@ const t = require('tap') const LOGS = [] const OUTPUT = [] const output = (...msg) => OUTPUT.push(msg) -const auditError = require('../../../lib/utils/audit-error.js') +const auditError = t.mock('../../../lib/utils/audit-error.js', { + 'proc-log': { + warn: (...msg) => LOGS.push(msg), + }, +}) const npm = { command: null, flatOptions: {}, - log: { - warn: (...msg) => LOGS.push(msg), - }, output, } t.afterEach(() => { diff --git a/test/lib/utils/cleanup-log-files.js b/test/lib/utils/cleanup-log-files.js deleted file mode 100644 index e97cf36b5..000000000 --- a/test/lib/utils/cleanup-log-files.js +++ /dev/null @@ -1,79 +0,0 @@ -const t = require('tap') - -const glob = require('glob') -const rimraf = require('rimraf') -const mocks = { glob, rimraf } -const cleanup = t.mock('../../../lib/utils/cleanup-log-files.js', { - glob: (...args) => mocks.glob(...args), - rimraf: (...args) => mocks.rimraf(...args), -}) -const { basename } = require('path') - -const fs = require('fs') - -t.test('clean up those files', t => { - const cache = t.testdir({ - _logs: { - '1-debug.log': 'hello', - '2-debug.log': 'hello', - '3-debug.log': 'hello', - '4-debug.log': 'hello', - '5-debug.log': 'hello', - }, - }) - const warn = (...warning) => t.fail('failed cleanup', { warning }) - return cleanup(cache, 3, warn).then(() => { - t.strictSame(fs.readdirSync(cache + '/_logs').sort(), [ - '3-debug.log', - '4-debug.log', - '5-debug.log', - ]) - }) -}) - -t.test('nothing to clean up', t => { - const cache = t.testdir({ - _logs: { - '4-debug.log': 'hello', - '5-debug.log': 'hello', - }, - }) - const warn = (...warning) => t.fail('failed cleanup', { warning }) - return cleanup(cache, 3, warn).then(() => { - t.strictSame(fs.readdirSync(cache + '/_logs').sort(), [ - '4-debug.log', - '5-debug.log', - ]) - }) -}) - -t.test('glob fail', t => { - mocks.glob = (pattern, cb) => cb(new Error('no globbity')) - t.teardown(() => mocks.glob = glob) - const cache = t.testdir({}) - const warn = (...warning) => t.fail('failed cleanup', { warning }) - return cleanup(cache, 3, warn) -}) - -t.test('rimraf fail', t => { - mocks.rimraf = (file, cb) => cb(new Error('youll never rimraf me!')) - t.teardown(() => mocks.rimraf = rimraf) - - const cache = t.testdir({ - _logs: { - '1-debug.log': 'hello', - '2-debug.log': 'hello', - '3-debug.log': 'hello', - '4-debug.log': 'hello', - '5-debug.log': 'hello', - }, - }) - const warnings = [] - const warn = (...warning) => warnings.push(basename(warning[2])) - return cleanup(cache, 3, warn).then(() => { - t.strictSame(warnings.sort((a, b) => a.localeCompare(b, 'en')), [ - '1-debug.log', - '2-debug.log', - ]) - }) -}) diff --git a/test/lib/utils/config/definitions.js b/test/lib/utils/config/definitions.js index 7af0b6839..bf4b48709 100644 --- a/test/lib/utils/config/definitions.js +++ b/test/lib/utils/config/definitions.js @@ -1,11 +1,9 @@ const t = require('tap') - const { resolve } = require('path') +const mockGlobals = require('../../../fixtures/mock-globals') // have to fake the node version, or else it'll only pass on this one -Object.defineProperty(process, 'version', { - value: 'v14.8.0', -}) +mockGlobals(t, { 'process.version': 'v14.8.0', 'process.env.NODE_ENV': undefined }) // also fake the npm version, so that it doesn't get reset every time const pkg = require('../../../../package.json') @@ -13,8 +11,6 @@ const pkg = require('../../../../package.json') // this is a pain to keep typing const defpath = '../../../../lib/utils/config/definitions.js' -// set this in the test when we need it -delete process.env.NODE_ENV const definitions = require(defpath) // Tie the definitions to a snapshot so that if they change we are forced to @@ -43,22 +39,19 @@ t.test('basic flattening function camelCases from css-case', t => { t.test('editor', t => { t.test('has EDITOR and VISUAL, use EDITOR', t => { - process.env.EDITOR = 'vim' - process.env.VISUAL = 'mate' + mockGlobals(t, { 'process.env': { EDITOR: 'vim', VISUAL: 'mate' } }) const defs = t.mock(defpath) t.equal(defs.editor.default, 'vim') t.end() }) t.test('has VISUAL but no EDITOR, use VISUAL', t => { - delete process.env.EDITOR - process.env.VISUAL = 'mate' + mockGlobals(t, { 'process.env': { EDITOR: undefined, VISUAL: 'mate' } }) const defs = t.mock(defpath) t.equal(defs.editor.default, 'mate') t.end() }) t.test('has neither EDITOR nor VISUAL, system specific', t => { - delete process.env.EDITOR - delete process.env.VISUAL + mockGlobals(t, { 'process.env': { EDITOR: undefined, VISUAL: undefined } }) const defsWin = t.mock(defpath, { [isWin]: true, }) @@ -74,12 +67,12 @@ t.test('editor', t => { t.test('shell', t => { t.test('windows, env.ComSpec then cmd.exe', t => { - process.env.ComSpec = 'command.com' + mockGlobals(t, { 'process.env.ComSpec': 'command.com' }) const defsComSpec = t.mock(defpath, { [isWin]: true, }) t.equal(defsComSpec.shell.default, 'command.com') - delete process.env.ComSpec + mockGlobals(t, { 'process.env.ComSpec': undefined }) const defsNoComSpec = t.mock(defpath, { [isWin]: true, }) @@ -88,12 +81,12 @@ t.test('shell', t => { }) t.test('nix, SHELL then sh', t => { - process.env.SHELL = '/usr/local/bin/bash' + mockGlobals(t, { 'process.env.SHELL': '/usr/local/bin/bash' }) const defsShell = t.mock(defpath, { [isWin]: false, }) t.equal(defsShell.shell.default, '/usr/local/bin/bash') - delete process.env.SHELL + mockGlobals(t, { 'process.env.SHELL': undefined }) const defsNoShell = t.mock(defpath, { [isWin]: false, }) @@ -136,43 +129,40 @@ t.test('local-address allowed types', t => { }) t.test('unicode allowed?', t => { - const { LC_ALL, LC_CTYPE, LANG } = process.env - t.teardown(() => Object.assign(process.env, { LC_ALL, LC_CTYPE, LANG })) + const setGlobal = (obj = {}) => mockGlobals(t, { 'process.env': obj }) - process.env.LC_ALL = 'utf8' - process.env.LC_CTYPE = 'UTF-8' - process.env.LANG = 'Unicode utf-8' + setGlobal({ LC_ALL: 'utf8', LC_CTYPE: 'UTF-8', LANG: 'Unicode utf-8' }) const lcAll = t.mock(defpath) t.equal(lcAll.unicode.default, true) - process.env.LC_ALL = 'no unicode for youUUUU!' + setGlobal({ LC_ALL: 'no unicode for youUUUU!' }) const noLcAll = t.mock(defpath) t.equal(noLcAll.unicode.default, false) - delete process.env.LC_ALL + setGlobal({ LC_ALL: undefined }) const lcCtype = t.mock(defpath) t.equal(lcCtype.unicode.default, true) - process.env.LC_CTYPE = 'something other than unicode version 8' + setGlobal({ LC_CTYPE: 'something other than unicode version 8' }) const noLcCtype = t.mock(defpath) t.equal(noLcCtype.unicode.default, false) - delete process.env.LC_CTYPE + setGlobal({ LC_CTYPE: undefined }) const lang = t.mock(defpath) t.equal(lang.unicode.default, true) - process.env.LANG = 'ISO-8859-1' + setGlobal({ LANG: 'ISO-8859-1' }) const noLang = t.mock(defpath) t.equal(noLang.unicode.default, false) t.end() }) t.test('cache', t => { - process.env.LOCALAPPDATA = 'app/data/local' + mockGlobals(t, { 'process.env.LOCALAPPDATA': 'app/data/local' }) const defsWinLocalAppData = t.mock(defpath, { [isWin]: true, }) t.equal(defsWinLocalAppData.cache.default, 'app/data/local/npm-cache') - delete process.env.LOCALAPPDATA + mockGlobals(t, { 'process.env.LOCALAPPDATA': undefined }) const defsWinNoLocalAppData = t.mock(defpath, { [isWin]: true, }) @@ -241,7 +231,7 @@ t.test('flatteners that populate flat.omit array', t => { definitions.omit.flatten('omit', obj, flat) t.strictSame(flat, { omit: ['optional'] }, 'do not omit what is included') - process.env.NODE_ENV = 'production' + mockGlobals(t, { 'process.env.NODE_ENV': 'production' }) const defProdEnv = t.mock(defpath) t.strictSame(defProdEnv.omit.default, ['dev'], 'omit dev in production') t.end() @@ -372,42 +362,79 @@ t.test('cache-min', t => { }) t.test('color', t => { - const { isTTY } = process.stdout - t.teardown(() => process.stdout.isTTY = isTTY) + const setTTY = (stream, value) => mockGlobals(t, { [`process.${stream}.isTTY`]: value }) const flat = {} const obj = { color: 'always' } definitions.color.flatten('color', obj, flat) - t.strictSame(flat, { color: true }, 'true when --color=always') + t.strictSame(flat, { color: true, logColor: true }, 'true when --color=always') obj.color = false definitions.color.flatten('color', obj, flat) - t.strictSame(flat, { color: false }, 'true when --no-color') + t.strictSame(flat, { color: false, logColor: false }, 'true when --no-color') - process.stdout.isTTY = false + setTTY('stdout', false) obj.color = true definitions.color.flatten('color', obj, flat) - t.strictSame(flat, { color: false }, 'no color when stdout not tty') - process.stdout.isTTY = true + t.strictSame(flat, { color: false, logColor: false }, 'no color when stdout not tty') + setTTY('stdout', true) definitions.color.flatten('color', obj, flat) - t.strictSame(flat, { color: true }, '--color turns on color when stdout is tty') + t.strictSame(flat, { color: true, logColor: false }, '--color turns on color when stdout is tty') + setTTY('stdout', false) - delete process.env.NO_COLOR + setTTY('stderr', false) + obj.color = true + definitions.color.flatten('color', obj, flat) + t.strictSame(flat, { color: false, logColor: false }, 'no color when stderr not tty') + setTTY('stderr', true) + definitions.color.flatten('color', obj, flat) + t.strictSame(flat, { color: false, logColor: true }, '--color turns on color when stderr is tty') + setTTY('stderr', false) + + const setColor = (value) => mockGlobals(t, { 'process.env.NO_COLOR': value }) + + setColor(undefined) const defsAllowColor = t.mock(defpath) t.equal(defsAllowColor.color.default, true, 'default true when no NO_COLOR env') - process.env.NO_COLOR = '0' + setColor('0') const defsNoColor0 = t.mock(defpath) t.equal(defsNoColor0.color.default, true, 'default true when no NO_COLOR=0') - process.env.NO_COLOR = '1' + setColor('1') const defsNoColor1 = t.mock(defpath) t.equal(defsNoColor1.color.default, false, 'default false when no NO_COLOR=1') t.end() }) +t.test('progress', t => { + const setEnv = ({ tty, term } = {}) => mockGlobals(t, { + 'process.stderr.isTTY': tty, + 'process.env.TERM': term, + }) + + const flat = {} + + definitions.progress.flatten('progress', {}, flat) + t.strictSame(flat, { progress: false }) + + setEnv({ tty: true, term: 'notdumb' }) + definitions.progress.flatten('progress', { progress: true }, flat) + t.strictSame(flat, { progress: true }) + + setEnv({ tty: false, term: 'notdumb' }) + definitions.progress.flatten('progress', { progress: true }, flat) + t.strictSame(flat, { progress: false }) + + setEnv({ tty: true, term: 'dumb' }) + definitions.progress.flatten('progress', { progress: true }, flat) + t.strictSame(flat, { progress: false }) + + t.end() +}) + t.test('retry options', t => { const obj = {} // <config>: flat.retry[<option>] diff --git a/test/lib/utils/did-you-mean.js b/test/lib/utils/did-you-mean.js index 185368d61..d3cb3a24f 100644 --- a/test/lib/utils/did-you-mean.js +++ b/test/lib/utils/did-you-mean.js @@ -1,11 +1,9 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm.js') -const { Npm } = mockNpm(t) -const npm = new Npm() +const { load: loadMockNpm } = require('../../fixtures/mock-npm.js') const dym = require('../../../lib/utils/did-you-mean.js') t.test('did-you-mean', async t => { - await npm.load() + const { npm } = await loadMockNpm(t) t.test('with package.json', async t => { const testdir = t.testdir({ 'package.json': JSON.stringify({ diff --git a/test/lib/utils/display.js b/test/lib/utils/display.js new file mode 100644 index 000000000..30cd2cc27 --- /dev/null +++ b/test/lib/utils/display.js @@ -0,0 +1,85 @@ +const t = require('tap') +const log = require('../../../lib/utils/log-shim') +const mockLogs = require('../../fixtures/mock-logs') +const mockGlobals = require('../../fixtures/mock-globals') + +const mockDisplay = (t, mocks) => { + const { logs, logMocks } = mockLogs(mocks) + const Display = t.mock('../../../lib/utils/display', { + ...mocks, + ...logMocks, + }) + const display = new Display() + t.teardown(() => display.off()) + return { display, logs } +} + +t.test('setup', async (t) => { + const { display } = mockDisplay(t) + + display.load({ timing: true, loglevel: 'notice' }) + t.equal(log.level, 'timing') + + display.load({ timing: false, loglevel: 'notice' }) + t.equal(log.level, 'notice') + + display.load({ color: true }) + t.equal(log.useColor(), true) + + display.load({ unicode: true }) + t.equal(log.gauge._theme.hasUnicode, true) + + display.load({ unicode: false }) + t.equal(log.gauge._theme.hasUnicode, false) + + mockGlobals(t, { 'process.stderr.isTTY': true }) + display.load({ progress: true }) + t.equal(log.progressEnabled, true) +}) + +t.test('can log', async (t) => { + const explains = [] + const { display, logs } = mockDisplay(t, { + npmlog: { + error: (...args) => logs.push(['error', ...args]), + warn: (...args) => logs.push(['warn', ...args]), + }, + '../../../lib/utils/explain-eresolve.js': { + explain: (...args) => { + explains.push(args) + return 'explanation' + }, + }, + }) + + display.log('error', 'test') + t.match(logs.error, [['test']]) + + display.log('warn', 'ERESOLVE', 'hello', { some: 'object' }) + t.match(logs.warn, [['ERESOLVE', 'hello']]) + t.match(explains, [[{ some: 'object' }, false, 2]]) +}) + +t.test('handles log throwing', async (t) => { + const errors = [] + mockGlobals(t, { + 'console.error': (...args) => errors.push(args), + }) + const { display } = mockDisplay(t, { + npmlog: { + verbose: () => { + throw new Error('verbose') + }, + }, + '../../../lib/utils/explain-eresolve.js': { + explain: () => { + throw new Error('explain') + }, + }, + }) + + display.log('warn', 'ERESOLVE', 'hello', { some: 'object' }) + t.match(errors, [ + [/attempt to log .* crashed/, Error('explain'), Error('verbose')], + ]) +}) diff --git a/test/lib/utils/error-message.js b/test/lib/utils/error-message.js index 1959b9217..ddc88c1d9 100644 --- a/test/lib/utils/error-message.js +++ b/test/lib/utils/error-message.js @@ -1,87 +1,51 @@ const t = require('tap') const path = require('path') -const { real: mockNpm } = require('../../fixtures/mock-npm.js') -const { Npm } = mockNpm(t, { - '../../package.json': { - version: '123.456.789-npm', +const { load: _loadMockNpm } = require('../../fixtures/mock-npm.js') +const mockGlobals = require('../../fixtures/mock-globals.js') +const { cleanCwd, cleanDate } = require('../../fixtures/clean-snapshot.js') + +t.cleanSnapshot = p => cleanDate(cleanCwd(p)) + +mockGlobals(t, { + process: { + getuid: () => 867, + getgid: () => 5309, + arch: 'x64', + version: '123.456.789-node', + platform: 'posix', }, }) -const npm = new Npm() -const { Npm: UnloadedNpm } = mockNpm(t, { - '../../package.json': { - version: '123.456.789-npm', - }, -}) -const unloadedNpm = new UnloadedNpm() - -// make a bunch of stuff consistent for snapshots - -process.getuid = () => 867 -process.getgid = () => 5309 - -Object.defineProperty(process, 'arch', { - value: 'x64', - configurable: true, -}) - -Object.defineProperty(process, 'version', { - value: '123.456.789-node', - configurable: true, -}) -const CACHE = '/some/cache/dir' -const testdir = t.testdir({}) -t.before(async () => { - await npm.load() - npm.localPrefix = testdir - unloadedNpm.localPrefix = testdir - npm.config.set('cache', CACHE) - npm.config.set('node-version', '99.99.99') - npm.version = '123.456.789-npm' - unloadedNpm.version = '123.456.789-npm' -}) - -const { resolve } = require('path') - -const npmlog = require('npmlog') -const verboseLogs = [] -npmlog.verbose = (...message) => { - verboseLogs.push(message) -} - -const EXPLAIN_CALLED = [] -const mocks = { - '../../../lib/utils/explain-eresolve.js': { - report: (...args) => { - EXPLAIN_CALLED.push(args) - return 'explanation' +const loadMockNpm = async (t, { load, command, testdir, config } = {}) => { + const { npm, ...rest } = await _loadMockNpm(t, { + load, + testdir, + config, + mocks: { + '../../package.json': { + version: '123.456.789-npm', + }, }, - }, - // XXX ??? - get '../../../lib/utils/is-windows.js' () { - return process.platform === 'win32' - }, -} -let errorMessage = t.mock('../../../lib/utils/error-message.js', { ...mocks }) - -const beWindows = () => { - Object.defineProperty(process, 'platform', { - value: 'win32', - configurable: true, }) - errorMessage = t.mock('../../../lib/utils/error-message.js', { ...mocks }) + if (command !== undefined) { + npm.command = command + } + return { + npm, + ...rest, + } } -const bePosix = () => { - Object.defineProperty(process, 'platform', { - value: 'posix', - configurable: true, - }) - errorMessage = t.mock('../../../lib/utils/error-message.js', { ...mocks }) -} +const errorMessage = (er, { mocks, logMocks, npm } = {}) => + t.mock('../../../lib/utils/error-message.js', { ...mocks, ...logMocks })(er, npm) -t.test('just simple messages', t => { - npm.command = 'audit' +t.test('just simple messages', async t => { + const npm = await loadMockNpm(t, { + command: 'audit', + config: { + 'node-version': '99.99.99', + }, + }) const codes = [ 'ENOAUDIT', 'ENOLOCK', @@ -108,7 +72,7 @@ t.test('just simple messages', t => { 'ERR_SOCKET_TIMEOUT', ] t.plan(codes.length) - codes.forEach(code => { + codes.forEach(async code => { const path = '/some/path' const pkgid = 'some@package' const file = '/some/file' @@ -124,8 +88,8 @@ t.test('just simple messages', t => { }) }) -t.test('replace message/stack sensistive info', t => { - npm.command = 'audit' +t.test('replace message/stack sensistive info', async t => { + const npm = await loadMockNpm(t, { command: 'audit' }) const path = '/some/path' const pkgid = 'some@package' const file = '/some/file' @@ -139,10 +103,10 @@ t.test('replace message/stack sensistive info', t => { stack, }) t.matchSnapshot(errorMessage(er, npm)) - t.end() }) -t.test('bad engine without config loaded', t => { +t.test('bad engine without config loaded', async t => { + const npm = await loadMockNpm(t, { load: false }) const path = '/some/path' const pkgid = 'some@package' const file = '/some/file' @@ -154,11 +118,11 @@ t.test('bad engine without config loaded', t => { file, stack, }) - t.matchSnapshot(errorMessage(er, unloadedNpm)) - t.end() + t.matchSnapshot(errorMessage(er, npm)) }) -t.test('enoent without a file', t => { +t.test('enoent without a file', async t => { + const npm = await loadMockNpm(t) const path = '/some/path' const pkgid = 'some@package' const stack = 'dummy stack trace' @@ -169,11 +133,10 @@ t.test('enoent without a file', t => { stack, }) t.matchSnapshot(errorMessage(er, npm)) - t.end() }) -t.test('enolock without a command', t => { - npm.command = null +t.test('enolock without a command', async t => { + const npm = await loadMockNpm(t, { command: null }) const path = '/some/path' const pkgid = 'some@package' const file = '/some/file' @@ -186,12 +149,12 @@ t.test('enolock without a command', t => { stack, }) t.matchSnapshot(errorMessage(er, npm)) - t.end() }) -t.test('default message', t => { +t.test('default message', async t => { + const npm = await loadMockNpm(t) t.matchSnapshot(errorMessage(new Error('error object'), npm)) - t.matchSnapshot(errorMessage('error string'), npm) + t.matchSnapshot(errorMessage('error string', npm)) t.matchSnapshot(errorMessage(Object.assign(new Error('cmd err'), { cmd: 'some command', signal: 'SIGYOLO', @@ -199,10 +162,10 @@ t.test('default message', t => { stdout: 'stdout', stderr: 'stderr', }), npm)) - t.end() }) -t.test('args are cleaned', t => { +t.test('args are cleaned', async t => { + const npm = await loadMockNpm(t) t.matchSnapshot(errorMessage(Object.assign(new Error('cmd err'), { cmd: 'some command', signal: 'SIGYOLO', @@ -210,35 +173,25 @@ t.test('args are cleaned', t => { stdout: 'stdout', stderr: 'stderr', }), npm)) - t.end() }) -t.test('eacces/eperm', t => { - const runTest = (windows, loaded, cachePath, cacheDest) => t => { +t.test('eacces/eperm', async t => { + const runTest = (windows, loaded, cachePath, cacheDest) => async t => { if (windows) { - beWindows() - } else { - bePosix() + mockGlobals(t, { 'process.platform': 'win32' }) } - - const path = `${cachePath ? CACHE : '/not/cache/dir'}/path` - const dest = `${cacheDest ? CACHE : '/not/cache/dir'}/dest` + const npm = await loadMockNpm(t, { windows, load: loaded }) + const path = `${cachePath ? npm.cache : '/not/cache/dir'}/path` + const dest = `${cacheDest ? npm.cache : '/not/cache/dir'}/dest` const er = Object.assign(new Error('whoopsie'), { code: 'EACCES', path, dest, stack: 'dummy stack trace', }) - verboseLogs.length = 0 - if (loaded) { - t.matchSnapshot(errorMessage(er, npm)) - } else { - t.matchSnapshot(errorMessage(er, unloadedNpm)) - } - t.matchSnapshot(verboseLogs) - t.end() - verboseLogs.length = 0 + t.matchSnapshot(errorMessage(er, npm)) + t.matchSnapshot(npm.logs.verbose) } for (const windows of [true, false]) { @@ -251,12 +204,13 @@ t.test('eacces/eperm', t => { } } } - t.end() }) t.test('json parse', t => { - t.test('merge conflict in package.json', t => { - const dir = t.testdir({ + mockGlobals(t, { 'process.argv': ['arg', 'v'] }) + + t.test('merge conflict in package.json', async t => { + const testdir = { 'package.json': ` { "array": [ @@ -295,59 +249,35 @@ t.test('json parse', t => { } } `, - }) - const { prefix } = npm - const { argv } = process - t.teardown(() => { - Object.defineProperty(npm, 'prefix', { - value: prefix, - configurable: true, - }) - process.argv = argv - }) - Object.defineProperty(npm, 'prefix', { value: dir, configurable: true }) - process.argv = ['arg', 'v'] + } + const npm = await loadMockNpm(t, { testdir }) t.matchSnapshot(errorMessage(Object.assign(new Error('conflicted'), { code: 'EJSONPARSE', - path: resolve(dir, 'package.json'), + path: path.resolve(npm.prefix, 'package.json'), }), npm)) t.end() }) - t.test('just regular bad json in package.json', t => { - const dir = t.testdir({ + t.test('just regular bad json in package.json', async t => { + const testdir = { 'package.json': 'not even slightly json', - }) - const { prefix } = npm - const { argv } = process - t.teardown(() => { - Object.defineProperty(npm, 'prefix', { - value: prefix, - configurable: true, - }) - process.argv = argv - }) - Object.defineProperty(npm, 'prefix', { value: dir, configurable: true }) - process.argv = ['arg', 'v'] + } + const npm = await loadMockNpm(t, { testdir }) t.matchSnapshot(errorMessage(Object.assign(new Error('not json'), { code: 'EJSONPARSE', - path: resolve(dir, 'package.json'), + path: path.resolve(npm.prefix, 'package.json'), }), npm)) t.end() }) - t.test('json somewhere else', t => { - const dir = t.testdir({ + t.test('json somewhere else', async t => { + const testdir = { 'blerg.json': 'not even slightly json', - }) - const { argv } = process - t.teardown(() => { - process.argv = argv - }) - process.argv = ['arg', 'v'] + } + const npm = await loadMockNpm(t, { testdir }) t.matchSnapshot(errorMessage(Object.assign(new Error('not json'), { code: 'EJSONPARSE', - path: `${dir}/blerg.json`, + path: path.resolve(npm.prefix, 'blerg.json'), }), npm)) t.end() }) @@ -355,7 +285,9 @@ t.test('json parse', t => { t.end() }) -t.test('eotp/e401', t => { +t.test('eotp/e401', async t => { + const npm = await loadMockNpm(t) + t.test('401, no auth headers', t => { t.matchSnapshot(errorMessage(Object.assign(new Error('nope'), { code: 'E401', @@ -406,11 +338,11 @@ t.test('eotp/e401', t => { }) } }) - - t.end() }) -t.test('404', t => { +t.test('404', async t => { + const npm = await loadMockNpm(t) + t.test('no package id', t => { const er = Object.assign(new Error('404 not found'), { code: 'E404' }) t.matchSnapshot(errorMessage(er, npm)) @@ -448,10 +380,11 @@ t.test('404', t => { t.matchSnapshot(errorMessage(er, npm)) t.end() }) - t.end() }) -t.test('bad platform', t => { +t.test('bad platform', async t => { + const npm = await loadMockNpm(t) + t.test('string os/arch', t => { const er = Object.assign(new Error('a bad plat'), { pkgid: 'lodash@1.0.0', @@ -484,19 +417,30 @@ t.test('bad platform', t => { t.matchSnapshot(errorMessage(er, npm)) t.end() }) - - t.end() }) -t.test('explain ERESOLVE errors', t => { +t.test('explain ERESOLVE errors', async t => { + const npm = await loadMockNpm(t) + const EXPLAIN_CALLED = [] + const er = Object.assign(new Error('could not resolve'), { code: 'ERESOLVE', }) - t.matchSnapshot(errorMessage(er, npm)) + + t.matchSnapshot(errorMessage(er, { + ...npm, + mocks: { + '../../../lib/utils/explain-eresolve.js': { + report: (...args) => { + EXPLAIN_CALLED.push(args) + return 'explanation' + }, + }, + }, + })) t.match(EXPLAIN_CALLED, [[ er, - undefined, + false, path.resolve(npm.cache, 'eresolve-report.txt'), ]]) - t.end() }) diff --git a/test/lib/utils/exit-handler.js b/test/lib/utils/exit-handler.js index adc7c3f4e..54bf48f89 100644 --- a/test/lib/utils/exit-handler.js +++ b/test/lib/utils/exit-handler.js @@ -1,177 +1,213 @@ -/* eslint-disable no-extend-native */ -/* eslint-disable no-global-assign */ const t = require('tap') -const EventEmitter = require('events') const os = require('os') -const fs = require('fs') -const path = require('path') - -const { real: mockNpm } = require('../../fixtures/mock-npm') - -// generic error to be used in tests -const err = Object.assign(new Error('ERROR'), { code: 'ERROR' }) -err.stack = 'Error: ERROR' - -const redactCwd = (path) => { - const normalizePath = p => p - .replace(/\\+/g, '/') - .replace(/\r\n/g, '\n') - return normalizePath(path) - .replace(new RegExp(normalizePath(process.cwd()), 'g'), '{CWD}') +const EventEmitter = require('events') +const { format } = require('../../../lib/utils/log-file') +const { load: loadMockNpm } = require('../../fixtures/mock-npm') +const mockGlobals = require('../../fixtures/mock-globals') +const { cleanCwd, cleanDate } = require('../../fixtures/clean-snapshot') + +const pick = (obj, ...keys) => keys.reduce((acc, key) => { + acc[key] = obj[key] + return acc +}, {}) + +t.formatSnapshot = (obj) => { + if (Array.isArray(obj)) { + return obj + .map((i) => Array.isArray(i) ? i.join(' ') : i) + .join('\n') + } + return obj } -t.cleanSnapshot = (str) => redactCwd(str) - -const cacheFolder = t.testdir({}) -const logFile = path.resolve(cacheFolder, '_logs', 'expecteddate-debug.log') -const timingFile = path.resolve(cacheFolder, '_timing.json') - -const { Npm } = mockNpm(t, { - '../../package.json': { - version: '1.0.0', - }, -}) -const npm = new Npm() - -t.before(async () => { - await npm.load() - npm.config.set('cache', cacheFolder) -}) +t.cleanSnapshot = (path) => cleanDate(cleanCwd(path)) +// Config loading is dependent on env so strip those from snapshots + .replace(/.*timing config:load:.*\n/gm, '') + .replace(/(Completed in )\d+(ms)/g, '$1{TIME}$2') // cut off process from script so that it won't quit the test runner // while trying to run through the myriad of cases. need to make it // have all the functions signal-exit relies on so that it doesn't // nerf itself, thinking global.process is broken or gone. -const _process = process -process = Object.assign( - new EventEmitter(), - { - argv: ['/node', ..._process.argv.slice(1)], - cwd: _process.cwd, - env: _process.env, +mockGlobals(t, { + process: Object.assign(new EventEmitter(), { + ...pick(process, 'execPath', 'stdout', 'stderr', 'cwd', 'env'), + argv: ['/node', ...process.argv.slice(1)], version: 'v1.0.0', + kill: () => {}, + reallyExit: (code) => process.exit(code), + pid: 123456, exit: (code) => { process.exitCode = code || process.exitCode || 0 process.emit('exit', process.exitCode) }, - stdout: { write (_, cb) { - cb() - } }, - stderr: { write () {} }, - hrtime: _process.hrtime, - kill: () => {}, - reallyExit: (code) => process.exit(code), - pid: 123456, + }), +}, { replace: true }) + +const mockExitHandler = async (t, { init, load, testdir, config } = {}) => { + const errors = [] + mockGlobals(t, { 'console.error': (err) => errors.push(err) }) + + const { npm, logMocks, ...rest } = await loadMockNpm(t, { + init, + load, + testdir, + mocks: { + '../../package.json': { + version: '1.0.0', + }, + }, + config: { + loglevel: 'notice', + ...config, + }, + }) + + const exitHandler = t.mock('../../../lib/utils/exit-handler.js', { + '../../../lib/utils/error-message.js': (err) => ({ + ...err, + summary: [['ERR SUMMARY', err.message]], + detail: [['ERR DETAIL', err.message]], + }), + os: { + type: () => 'Foo', + release: () => '1.0.0', + }, + ...logMocks, + }) + + if (npm) { + exitHandler.setNpm(npm) } -) - -const osType = os.type -const osRelease = os.release -// overrides OS type/release for cross platform snapshots -os.type = () => 'Foo' -os.release = () => '1.0.0' - -// generates logfile name with mocked date -const _toISOString = Date.prototype.toISOString -Date.prototype.toISOString = () => 'expecteddate' - -const consoleError = console.error -const errors = [] -console.error = (err) => { - errors.push(err) -} -t.teardown(() => { - os.type = osType - os.release = osRelease - // needs to put process back in its place in order for tap to exit properly - process = _process - Date.prototype.toISOString = _toISOString - console.error = consoleError -}) -t.afterEach(() => { - errors.length = 0 - npm.log.level = 'silent' - // clear out the 'A complete log' message - npm.log.record.length = 0 - delete process.exitCode -}) + t.teardown(() => { + delete process.exitCode + process.removeAllListeners('exit') + }) -const mocks = { - '../../../lib/utils/error-message.js': (err) => ({ - ...err, - summary: [['ERR', err.message]], - detail: [['ERR', err.message]], - }), + return { + ...rest, + errors, + npm, + // // Make it async to make testing ergonomics a little + // // easier so we dont need to t.plan() every test to + // // make sure we get process.exit called + exitHandler: (...args) => new Promise(resolve => { + process.once('exit', resolve) + exitHandler(...args) + }), + } } -const exitHandler = t.mock('../../../lib/utils/exit-handler.js', mocks) -exitHandler.setNpm(npm) - -t.test('exit handler never called - loglevel silent', (t) => { - npm.log.level = 'silent' - process.emit('exit', 1) - const logData = fs.readFileSync(logFile, 'utf8') - t.match(logData, 'Exit handler never called!') - t.match(errors, [''], 'logs one empty string to console.error') - t.end() -}) +// Create errors with properties to be used in tests +const err = (message = '', options = {}, noStack = false) => { + const e = Object.assign( + new Error(message), + typeof options !== 'object' ? { code: options } : options + ) + e.stack = options.stack || `Error: ${message}` + if (noStack) { + delete e.stack + } + return e +} -t.test('exit handler never called - loglevel notice', (t) => { - npm.log.level = 'notice' - process.emit('exit', 1) - const logData = fs.readFileSync(logFile, 'utf8') - t.match(logData, 'Exit handler never called!') - t.match(errors, ['', ''], 'logs two empty strings to console.error') - t.end() -}) +t.test('handles unknown error with logs and debug file', async (t) => { + const { exitHandler, debugFile, logs } = await mockExitHandler(t) -t.test('handles unknown error', (t) => { - t.plan(2) + await exitHandler(err('Unknown error', 'ECODE')) - npm.log.level = 'notice' + const debugContent = await debugFile() - process.once('timeEnd', (msg) => { - t.equal(msg, 'npm', 'should trigger timeEnd for npm') + t.equal(process.exitCode, 1) + logs.forEach((logItem, i) => { + const logLines = format(i, ...logItem).trim().split(os.EOL) + logLines.forEach((line) => { + t.match(debugContent.trim(), line, 'log appears in debug file') + }) }) - exitHandler(err) - const logData = fs.readFileSync(logFile, 'utf8') - t.matchSnapshot( - logData, - 'should have expected log contents for unknown error' - ) - t.end() + const lastLog = debugContent + .split('\n') + .reduce((__, l) => parseInt(l.match(/^(\d+)\s/)[1])) + t.equal(logs.length, lastLog + 1) + t.match(logs.error, [ + ['code', 'ECODE'], + ['ERR SUMMARY', 'Unknown error'], + ['ERR DETAIL', 'Unknown error'], + ]) + t.match(debugContent, /\d+ error code ECODE/) + t.match(debugContent, /\d+ error ERR SUMMARY Unknown error/) + t.match(debugContent, /\d+ error ERR DETAIL Unknown error/) + t.matchSnapshot(logs, 'logs') + t.matchSnapshot(debugContent, 'debug file contents') }) -t.test('fail to write logfile', (t) => { - t.plan(1) - - t.teardown(() => { - npm.config.set('cache', cacheFolder) +t.test('exit handler never called - loglevel silent', async (t) => { + const { logs, errors } = await mockExitHandler(t, { + config: { loglevel: 'silent' }, }) + process.emit('exit', 1) + t.match(logs.error, [ + ['', /Exit handler never called/], + ['', /error with npm itself/], + ]) + t.strictSame(errors, [''], 'logs one empty string to console.error') +}) - const badDir = t.testdir({ - _logs: 'is a file', - }) +t.test('exit handler never called - loglevel notice', async (t) => { + const { logs, errors } = await mockExitHandler(t) + process.emit('exit', 1) + t.equal(process.exitCode, 1) + t.match(logs.error, [ + ['', /Exit handler never called/], + ['', /error with npm itself/], + ]) + t.strictSame(errors, ['', ''], 'logs two empty strings to console.error') +}) + +t.test('exit handler never called - no npm', async (t) => { + const { logs, errors } = await mockExitHandler(t, { init: false }) + process.emit('exit', 1) + t.equal(process.exitCode, 1) + t.match(logs.error, [ + ['', /Exit handler never called/], + ['', /error with npm itself/], + ]) + t.strictSame(errors, [''], 'logs one empty string to console.error') +}) - npm.config.set('cache', badDir) +t.test('exit handler called - no npm', async (t) => { + const { exitHandler, errors } = await mockExitHandler(t, { init: false }) + await exitHandler() + t.equal(process.exitCode, 1) + t.match(errors, [/Error: Exit prior to setting npm in exit handler/]) +}) - t.doesNotThrow( - () => exitHandler(err), - 'should not throw on cache write failure' - ) +t.test('exit handler called - no npm with error', async (t) => { + const { exitHandler, errors } = await mockExitHandler(t, { init: false }) + await exitHandler(err('something happened')) + t.equal(process.exitCode, 1) + t.match(errors, [/Error: something happened/]) }) -t.test('console.log output using --json', (t) => { - t.plan(1) +t.test('exit handler called - no npm with error without stack', async (t) => { + const { exitHandler, errors } = await mockExitHandler(t, { init: false }) + await exitHandler(err('something happened', {}, true)) + t.equal(process.exitCode, 1) + t.match(errors, [/something happened/]) +}) - npm.config.set('json', true) - t.teardown(() => { - npm.config.set('json', false) +t.test('console.log output using --json', async (t) => { + const { exitHandler, errors } = await mockExitHandler(t, { + config: { + json: true, + }, }) - exitHandler(new Error('Error: EBADTHING Something happened')) + await exitHandler(err('Error: EBADTHING Something happened')) + + t.equal(process.exitCode, 1) t.same( JSON.parse(errors[0]), { @@ -185,213 +221,223 @@ t.test('console.log output using --json', (t) => { ) }) -t.test('throw a non-error obj', (t) => { - t.plan(2) +t.test('throw a non-error obj', async (t) => { + const { exitHandler, logs } = await mockExitHandler(t) - const weirdError = { + await exitHandler({ code: 'ESOMETHING', message: 'foo bar', - } - - process.once('exit', code => { - t.equal(code, 1, 'exits with exitCode 1') }) - exitHandler(weirdError) - t.match( - npm.log.record.find(r => r.level === 'error'), - { message: 'foo bar' } - ) + + t.equal(process.exitCode, 1) + t.match(logs.error, [ + ['weird error', { code: 'ESOMETHING', message: 'foo bar' }], + ]) }) -t.test('throw a string error', (t) => { - t.plan(2) - const error = 'foo bar' +t.test('throw a string error', async (t) => { + const { exitHandler, logs } = await mockExitHandler(t) - process.once('exit', code => { - t.equal(code, 1, 'exits with exitCode 1') - }) - exitHandler(error) - t.match( - npm.log.record.find(r => r.level === 'error'), - { message: 'foo bar' } - ) + await exitHandler('foo bar') + + t.equal(process.exitCode, 1) + t.match(logs.error, [ + ['', 'foo bar'], + ]) }) -t.test('update notification', (t) => { - const updateMsg = 'you should update npm!' - npm.updateNotification = updateMsg - npm.log.level = 'silent' +t.test('update notification', async (t) => { + const { exitHandler, logs, npm } = await mockExitHandler(t) + npm.updateNotification = 'you should update npm!' - t.teardown(() => { - delete npm.updateNotification - }) + await exitHandler() - exitHandler() - t.match( - npm.log.record.find(r => r.level === 'notice'), - { message: 'you should update npm!' } - ) - t.end() + t.match(logs.notice, [ + ['', 'you should update npm!'], + ]) }) -t.test('npm.config not ready', (t) => { - t.plan(1) +t.test('npm.config not ready', async (t) => { + const { exitHandler, logs, errors } = await mockExitHandler(t, { + load: false, + }) - const { Npm: Unloaded } = mockNpm(t) - const unloaded = new Unloaded() + await exitHandler() - t.teardown(() => { - exitHandler.setNpm(npm) + t.equal(process.exitCode, 1) + t.match(errors, [ + /Error: Exit prior to config file resolving./, + ], 'should exit with config error msg') + t.match(logs.verbose, [ + ['stack', /Error: Exit prior to config file resolving./], + ], 'should exit with config error msg') +}) + +t.test('timing with no error', async (t) => { + const { exitHandler, timingFile, npm, logs } = await mockExitHandler(t, { + config: { + timing: true, + }, }) - exitHandler.setNpm(unloaded) + await exitHandler() + const timingFileData = await timingFile() + + t.equal(process.exitCode, 0) + + t.match(logs.error, [ + ['', /A complete log of this run can be found in:[\s\S]*-debug-\d\.log/], + ]) - exitHandler() t.match( - errors[0], - /Error: Exit prior to config file resolving./, - 'should exit with config error msg' + timingFileData, + Object.keys(npm.finishedTimers).reduce((acc, k) => { + acc[k] = Number + return acc + }, {}) ) - t.end() + t.strictSame(npm.unfinishedTimers, new Map()) + t.match(timingFileData, { + command: [], + version: '1.0.0', + npm: Number, + logfile: String, + logfiles: [String], + }) }) -t.test('timing', (t) => { - npm.config.set('timing', true) - - t.teardown(() => { - fs.unlinkSync(timingFile) - npm.config.set('timing', false) +t.test('unfinished timers', async (t) => { + const { exitHandler, timingFile, npm } = await mockExitHandler(t, { + config: { + timing: true, + }, }) - exitHandler() - const timingData = JSON.parse(fs.readFileSync(timingFile, 'utf8')) - t.match(timingData, { version: '1.0.0', 'config:load:defaults': Number }) - t.end() -}) + process.emit('time', 'foo') + process.emit('time', 'bar') -t.test('timing - with error', (t) => { - npm.config.set('timing', true) + await exitHandler() + const timingFileData = await timingFile() - t.teardown(() => { - fs.unlinkSync(timingFile) - npm.config.set('timing', false) + t.equal(process.exitCode, 0) + t.match(npm.unfinishedTimers, new Map([['foo', Number], ['bar', Number]])) + t.match(timingFileData, { + command: [], + version: '1.0.0', + npm: Number, + logfile: String, + logfiles: [String], + unfinished: { + foo: [Number, Number], + bar: [Number, Number], + }, }) - - exitHandler(err) - const timingData = JSON.parse(fs.readFileSync(timingFile, 'utf8')) - t.match(timingData, { version: '1.0.0', 'config:load:defaults': Number }) - t.end() }) -t.test('uses code from errno', (t) => { - t.plan(1) +t.test('uses code from errno', async (t) => { + const { exitHandler, logs } = await mockExitHandler(t) - process.once('exit', code => { - t.equal(code, 127, 'should set exitCode from errno') - }) - exitHandler(Object.assign( - new Error('Error with errno'), - { - errno: 127, - } - )) + await exitHandler(err('Error with errno', { errno: 127 })) + t.equal(process.exitCode, 127) + t.match(logs.error, [['errno', 127]]) }) -t.test('uses code from number', (t) => { - t.plan(1) +t.test('uses code from number', async (t) => { + const { exitHandler, logs } = await mockExitHandler(t) - process.once('exit', code => { - t.equal(code, 404, 'should set exitCode from a number') - }) - exitHandler(Object.assign( - new Error('Error with code type number'), - { - code: 404, - } - )) + await exitHandler(err('Error with code type number', 404)) + t.equal(process.exitCode, 404) + t.match(logs.error, [['code', 404]]) }) -t.test('call exitHandler with no error', (t) => { - t.plan(1) - process.once('exit', code => { - t.equal(code, 0, 'should end up with exitCode 0 (default)') - }) - exitHandler() +t.test('uses all err special properties', async t => { + const { exitHandler, logs } = await mockExitHandler(t) + + const keys = ['code', 'syscall', 'file', 'path', 'dest', 'errno'] + const properties = keys.reduce((acc, k) => { + acc[k] = `${k}-hey` + return acc + }, {}) + + await exitHandler(err('Error with code type number', properties)) + t.equal(process.exitCode, 1) + t.match(logs.error, keys.map((k) => [k, `${k}-hey`]), 'all special keys get logged') }) -t.test('defaults to log error msg if stack is missing', (t) => { - const { Npm: Unloaded } = mockNpm(t) - const unloaded = new Unloaded() +t.test('verbose logs replace info on err props', async t => { + const { exitHandler, logs } = await mockExitHandler(t) - t.teardown(() => { - exitHandler.setNpm(npm) - }) + const keys = ['type', 'stack', 'statusCode', 'pkgid'] + const properties = keys.reduce((acc, k) => { + acc[k] = `${k}-https://user:pass@registry.npmjs.org/` + return acc + }, {}) - exitHandler.setNpm(unloaded) - const noStackErr = Object.assign( - new Error('Error with no stack'), - { - code: 'ENOSTACK', - errno: 127, - } + await exitHandler(err('Error with code type number', properties)) + t.equal(process.exitCode, 1) + t.match( + logs.verbose.filter(([p]) => p !== 'logfile'), + keys.map((k) => [k, `${k}-https://user:***@registry.npmjs.org/`]), + 'all special keys get replaced' ) - delete noStackErr.stack +}) - exitHandler(noStackErr) - t.equal(errors[0], 'Error with no stack', 'should use error msg') - t.end() +t.test('call exitHandler with no error', async (t) => { + const { exitHandler, logs } = await mockExitHandler(t) + + await exitHandler() + + t.equal(process.exitCode, 0) + t.match(logs.error, []) +}) + +t.test('defaults to log error msg if stack is missing when unloaded', async (t) => { + const { exitHandler, logs, errors } = await mockExitHandler(t, { load: false }) + + await exitHandler(err('Error with no stack', { code: 'ENOSTACK', errno: 127 }, true)) + t.equal(process.exitCode, 127) + t.same(errors, ['Error with no stack'], 'should use error msg') + t.match(logs.error, [ + ['code', 'ENOSTACK'], + ['errno', 127], + ]) }) -t.test('exits uncleanly when only emitting exit event', (t) => { - t.plan(2) +t.test('exits uncleanly when only emitting exit event', async (t) => { + const { logs } = await mockExitHandler(t) - npm.log.level = 'silent' process.emit('exit') - const logData = fs.readFileSync(logFile, 'utf8') - t.match(logData, 'Exit handler never called!') - t.match(process.exitCode, 1, 'exitCode coerced to 1') + + t.match(logs.error, [['', 'Exit handler never called!']]) + t.equal(process.exitCode, 1, 'exitCode coerced to 1') t.end() }) -t.test('do no fancy handling for shellouts', t => { - const { command } = npm - const LOG_RECORD = [] - npm.command = 'exec' +t.test('do no fancy handling for shellouts', async t => { + const { exitHandler, npm, logs } = await mockExitHandler(t) - t.teardown(() => { - npm.command = command - }) - t.beforeEach(() => LOG_RECORD.length = 0) + npm.command = 'exec' - const loudNoises = () => npm.log.record - .filter(({ level }) => ['warn', 'error'].includes(level)) + const loudNoises = () => + logs.filter(([level]) => ['warn', 'error'].includes(level)) - t.test('shellout with a numeric error code', t => { - t.plan(2) - process.once('exit', code => { - t.equal(code, 5, 'got expected exit code') - }) - exitHandler(Object.assign(new Error(), { code: 5 })) + t.test('shellout with a numeric error code', async t => { + await exitHandler(err('', 5)) + t.equal(process.exitCode, 5, 'got expected exit code') t.strictSame(loudNoises(), [], 'no noisy warnings') }) - t.test('shellout without a numeric error code (something in npm)', t => { - t.plan(2) - process.once('exit', code => { - t.equal(code, 1, 'got expected exit code') - }) - exitHandler(Object.assign(new Error(), { code: 'banana stand' })) + t.test('shellout without a numeric error code (something in npm)', async t => { + await exitHandler(err('', 'banana stand')) + t.equal(process.exitCode, 1, 'got expected exit code') // should log some warnings and errors, because something weird happened t.strictNotSame(loudNoises(), [], 'bring the noise') t.end() }) - t.test('shellout with code=0 (extra weird?)', t => { - t.plan(2) - process.once('exit', code => { - t.equal(code, 1, 'got expected exit code') - }) - exitHandler(Object.assign(new Error(), { code: 0 })) + t.test('shellout with code=0 (extra weird?)', async t => { + await exitHandler(Object.assign(new Error(), { code: 0 })) + t.equal(process.exitCode, 1, 'got expected exit code') t.strictNotSame(loudNoises(), [], 'bring the noise') }) diff --git a/test/lib/utils/is-windows-bash.js b/test/lib/utils/is-windows-bash.js index 94fde0ace..0fbebdf8e 100644 --- a/test/lib/utils/is-windows-bash.js +++ b/test/lib/utils/is-windows-bash.js @@ -1,4 +1,5 @@ const t = require('tap') +const mockGlobal = require('../../fixtures/mock-globals.js') const isWindowsBash = () => { delete require.cache[require.resolve('../../../lib/utils/is-windows-bash.js')] @@ -6,23 +7,24 @@ const isWindowsBash = () => { return require('../../../lib/utils/is-windows-bash.js') } -Object.defineProperty(process, 'platform', { - value: 'posix', - configurable: true, -}) -t.equal(isWindowsBash(), false, 'false when not windows') +t.test('posix', (t) => { + mockGlobal(t, { 'process.platform': 'posix' }) + t.equal(isWindowsBash(), false, 'false when not windows') -Object.defineProperty(process, 'platform', { - value: 'win32', - configurable: true, + t.end() }) -process.env.MSYSTEM = 'not ming' -process.env.TERM = 'dumb' -t.equal(isWindowsBash(), false, 'false when not mingw or cygwin') -process.env.TERM = 'cygwin' -t.equal(isWindowsBash(), true, 'true when cygwin') +t.test('win32', (t) => { + mockGlobal(t, { 'process.platform': 'win32' }) + + mockGlobal(t, { 'process.env': { TERM: 'dumb', MSYSTEM: undefined } }) + t.equal(isWindowsBash(), false, 'false when not mingw or cygwin') + + mockGlobal(t, { 'process.env.TERM': 'cygwin' }) + t.equal(isWindowsBash(), true, 'true when cygwin') -process.env.MSYSTEM = 'MINGW64' -process.env.TERM = 'dumb' -t.equal(isWindowsBash(), true, 'true when mingw') + mockGlobal(t, { 'process.env': { TERM: 'dumb', MSYSTEM: 'MINGW64' } }) + t.equal(isWindowsBash(), true, 'true when mingw') + + t.end() +}) diff --git a/test/lib/utils/log-file.js b/test/lib/utils/log-file.js new file mode 100644 index 000000000..adc1a2e03 --- /dev/null +++ b/test/lib/utils/log-file.js @@ -0,0 +1,333 @@ +const t = require('tap') +const _fs = require('fs') +const fs = _fs.promises +const path = require('path') +const os = require('os') +const fsMiniPass = require('fs-minipass') +const rimraf = require('rimraf') +const LogFile = require('../../../lib/utils/log-file.js') +const { cleanCwd } = require('../../fixtures/clean-snapshot') + +t.cleanSnapshot = (path) => cleanCwd(path) + +const last = arr => arr[arr.length - 1] +const range = (n) => Array.from(Array(n).keys()) +const makeOldLogs = (count) => { + const d = new Date() + d.setHours(-1) + d.setSeconds(0) + return range(count / 2).reduce((acc, i) => { + const cloneDate = new Date(d.getTime()) + cloneDate.setSeconds(i) + acc[LogFile.fileName(LogFile.logId(cloneDate), 0)] = 'hello' + acc[LogFile.fileName(LogFile.logId(cloneDate), 1)] = 'hello' + return acc + }, {}) +} + +const cleanErr = (message) => { + const err = new Error(message) + const stack = err.stack.split('\n') + err.stack = stack[0] + '\n' + range(10) + .map((__, i) => stack[1].replace(/^(\s+at\s).*/, `$1stack trace line ${i}`)) + .join('\n') + return err +} + +const loadLogFile = async (t, { buffer = [], mocks, testdir = {}, ...options } = {}) => { + const root = t.testdir(testdir) + const MockLogFile = t.mock('../../../lib/utils/log-file.js', mocks) + const logFile = new MockLogFile(Object.keys(options).length ? options : undefined) + buffer.forEach((b) => logFile.log(...b)) + await logFile.load({ dir: root, ...options }) + t.teardown(() => logFile.off()) + return { + root, + logFile, + LogFile, + readLogs: async () => { + const logDir = await fs.readdir(root) + const logFiles = logDir.map((f) => path.join(root, f)) + .filter((f) => _fs.existsSync(f)) + return Promise.all(logFiles.map(async (f) => { + const content = await fs.readFile(f, 'utf8') + const rawLogs = content.split(os.EOL) + return { + filename: f, + content, + rawLogs, + logs: rawLogs.filter(Boolean), + } + })) + }, + } +} + +t.test('init', async t => { + const maxLogsPerFile = 10 + const { root, logFile, readLogs } = await loadLogFile(t, { + maxLogsPerFile, + maxFilesPerProcess: 20, + buffer: [['error', 'buffered']], + }) + + for (const i of range(50)) { + logFile.log('error', `log ${i}`) + } + + // Ignored + logFile.log('pause') + logFile.log('resume') + logFile.log('pause') + + for (const i of range(50)) { + logFile.log('verb', `log ${i}`) + } + + logFile.off() + logFile.log('error', 'ignored') + + const logs = await readLogs() + t.equal(logs.length, 11, 'total log files') + t.ok(logs.slice(0, 10).every(f => f.logs.length === maxLogsPerFile), 'max logs per file') + t.ok(last(logs).logs.length, 1, 'last file has remaining logs') + t.ok(logs.every(f => last(f.rawLogs) === ''), 'all logs end with newline') + t.strictSame( + logFile.files, + logs.map((l) => path.resolve(root, l.filename)) + ) +}) + +t.test('max files per process', async t => { + const maxLogsPerFile = 10 + const maxFilesPerProcess = 5 + const { logFile, readLogs } = await loadLogFile(t, { + maxLogsPerFile, + maxFilesPerProcess, + }) + + for (const i of range(maxLogsPerFile * maxFilesPerProcess)) { + logFile.log('error', `log ${i}`) + } + + for (const i of range(5)) { + logFile.log('verbose', `log ${i}`) + } + + const logs = await readLogs() + t.equal(logs.length, maxFilesPerProcess, 'total log files') + t.equal(last(last(logs).logs), '49 error log 49') +}) + +t.test('stream error', async t => { + let times = 0 + const { logFile, readLogs } = await loadLogFile(t, { + maxLogsPerFile: 1, + maxFilesPerProcess: 99, + mocks: { + 'fs-minipass': { + WriteStreamSync: class { + constructor (...args) { + if (times >= 5) { + throw new Error('bad stream') + } + times++ + return new fsMiniPass.WriteStreamSync(...args) + } + }, + }, + }, + }) + + for (const i of range(10)) { + logFile.log('verbose', `log ${i}`) + } + + const logs = await readLogs() + t.equal(logs.length, 5, 'total log files') +}) + +t.test('initial stream error', async t => { + const { logFile, readLogs } = await loadLogFile(t, { + mocks: { + 'fs-minipass': { + WriteStreamSync: class { + constructor (...args) { + throw new Error('no stream') + } + }, + }, + }, + }) + + for (const i of range(10)) { + logFile.log('verbose', `log ${i}`) + } + + const logs = await readLogs() + t.equal(logs.length, 0, 'total log files') +}) + +t.test('turns off', async t => { + const { logFile, readLogs } = await loadLogFile(t) + + logFile.log('error', 'test') + logFile.off() + logFile.log('error', 'test2') + logFile.load() + + const logs = await readLogs() + t.equal(logs.length, 1) + t.equal(logs[0].logs[0], '0 error test') +}) + +t.test('cleans logs', async t => { + const logsMax = 5 + const { readLogs } = await loadLogFile(t, { + logsMax, + testdir: makeOldLogs(10), + }) + + const logs = await readLogs() + t.equal(logs.length, logsMax + 1) +}) + +t.test('doesnt clean current log by default', async t => { + const logsMax = 0 + const { readLogs, logFile } = await loadLogFile(t, { + logsMax, + testdir: makeOldLogs(10), + }) + + logFile.log('error', 'test') + + const logs = await readLogs() + t.equal(logs.length, 1) + t.match(last(logs).content, /\d+ error test/) +}) + +t.test('negative logs max', async t => { + const logsMax = -10 + const { readLogs, logFile } = await loadLogFile(t, { + logsMax, + testdir: makeOldLogs(10), + }) + + logFile.log('error', 'test') + + const logs = await readLogs() + t.equal(logs.length, 1) + t.match(last(logs).content, /\d+ error test/) +}) + +t.test('doesnt need to clean', async t => { + const logsMax = 20 + const oldLogs = 10 + const { readLogs } = await loadLogFile(t, { + logsMax, + testdir: makeOldLogs(oldLogs), + }) + + const logs = await readLogs() + t.equal(logs.length, oldLogs + 1) +}) + +t.test('glob error', async t => { + const { readLogs } = await loadLogFile(t, { + logsMax: 5, + mocks: { + glob: () => { + throw new Error('bad glob') + }, + }, + }) + + const logs = await readLogs() + t.equal(logs.length, 1) + t.match(last(logs).content, /error cleaning log files .* bad glob/) +}) + +t.test('rimraf error', async t => { + const logsMax = 5 + const oldLogs = 10 + let count = 0 + const { readLogs } = await loadLogFile(t, { + logsMax, + testdir: makeOldLogs(oldLogs), + mocks: { + rimraf: (...args) => { + if (count >= 3) { + throw new Error('bad rimraf') + } + count++ + return rimraf(...args) + }, + }, + }) + + const logs = await readLogs() + t.equal(logs.length, oldLogs - 3 + 1) + t.match(last(logs).content, /error removing log file .* bad rimraf/) +}) + +t.test('delete log file while open', async t => { + const { logFile, root, readLogs } = await loadLogFile(t) + + logFile.log('error', '', 'log 1') + const [log] = await readLogs(true) + t.match(log.content, /\d+ error log 1/) + + await fs.unlink(path.resolve(root, log.filename)) + + logFile.log('error', '', 'log 2') + const logs = await readLogs() + + // XXX: do some retry logic after error? + t.strictSame(logs, [], 'logs arent written after error') +}) + +t.test('snapshot', async t => { + const { logFile, readLogs } = await loadLogFile(t) + + logFile.log('error', '', 'no prefix') + logFile.log('error', 'prefix', 'with prefix') + logFile.log('error', 'prefix', 1, 2, 3) + + const nestedObj = { obj: { with: { many: { props: 1 } } } } + logFile.log('verbose', '', nestedObj) + logFile.log('verbose', '', JSON.stringify(nestedObj)) + logFile.log('verbose', '', JSON.stringify(nestedObj, null, 2)) + + const arr = ['test', 'with', 'an', 'array'] + logFile.log('verbose', '', arr) + logFile.log('verbose', '', JSON.stringify(arr)) + logFile.log('verbose', '', JSON.stringify(arr, null, 2)) + + const nestedArr = ['test', ['with', ['an', ['array']]]] + logFile.log('verbose', '', nestedArr) + logFile.log('verbose', '', JSON.stringify(nestedArr)) + logFile.log('verbose', '', JSON.stringify(nestedArr, null, 2)) + + // XXX: multiple errors are hard to parse visually + // the second error should start on a newline + logFile.log(...[ + 'error', + 'pre', + 'has', + 'many', + 'errors', + cleanErr('message'), + cleanErr('message2'), + ]) + + const err = new Error('message') + delete err.stack + logFile.log(...[ + 'error', + 'nostack', + err, + ]) + + const logs = await readLogs() + t.matchSnapshot(logs.map(l => l.content).join('\n')) +}) diff --git a/test/lib/utils/log-shim.js b/test/lib/utils/log-shim.js new file mode 100644 index 000000000..dee4efbaa --- /dev/null +++ b/test/lib/utils/log-shim.js @@ -0,0 +1,100 @@ +const t = require('tap') + +const makeShim = (mocks) => t.mock('../../../lib/utils/log-shim.js', mocks) + +const loggers = [ + 'notice', + 'error', + 'warn', + 'info', + 'verbose', + 'http', + 'silly', + 'pause', + 'resume', +] + +t.test('has properties', (t) => { + const shim = makeShim() + + t.match(shim, { + level: String, + levels: {}, + gauge: {}, + stream: {}, + heading: undefined, + enableColor: Function, + disableColor: Function, + enableUnicode: Function, + disableUnicode: Function, + enableProgress: Function, + disableProgress: Function, + ...loggers.reduce((acc, l) => { + acc[l] = Function + return acc + }, {}), + }) + + t.match(Object.keys(shim).sort(), [ + 'level', + 'heading', + 'levels', + 'gauge', + 'stream', + 'tracker', + 'useColor', + 'enableColor', + 'disableColor', + 'enableUnicode', + 'disableUnicode', + 'enableProgress', + 'disableProgress', + 'progressEnabled', + 'clearProgress', + 'showProgress', + 'newItem', + 'newGroup', + ...loggers, + ].sort()) + + t.end() +}) + +t.test('works with npmlog/proclog proxy', t => { + const procLog = { silly: () => 'SILLY' } + const npmlog = { level: 'woo', enableColor: () => true } + const shim = makeShim({ npmlog, 'proc-log': procLog }) + + t.equal(shim.level, 'woo', 'can get a property') + + npmlog.level = 'hey' + t.strictSame( + [shim.level, npmlog.level], + ['hey', 'hey'], + 'can get a property after update on npmlog' + ) + + shim.level = 'test' + t.strictSame( + [shim.level, npmlog.level], + ['test', 'test'], + 'can get a property after update on shim' + ) + + t.ok(shim.enableColor(), 'can call method on shim to call npmlog') + t.equal(shim.silly(), 'SILLY', 'can call method on proclog') + t.notOk(shim.LEVELS, 'only includes levels from npmlog') + t.throws(() => shim.gauge = 100, 'cant set getters properies') + + t.end() +}) + +t.test('works with npmlog/proclog proxy', t => { + const shim = makeShim() + + loggers.forEach((k) => { + t.doesNotThrow(() => shim[k]('test')) + }) + + t.end() +}) diff --git a/test/lib/utils/npm-usage.js b/test/lib/utils/npm-usage.js index 77254a80d..035d4bbb2 100644 --- a/test/lib/utils/npm-usage.js +++ b/test/lib/utils/npm-usage.js @@ -1,10 +1,8 @@ const t = require('tap') -const { real: mockNpm } = require('../../fixtures/mock-npm.js') -const { Npm } = mockNpm(t) -const npm = new Npm() +const { load: loadMockNpm } = require('../../fixtures/mock-npm.js') t.test('usage', async t => { - await npm.load() + const { npm } = await loadMockNpm(t) t.afterEach(() => { npm.config.set('viewer', null) npm.config.set('long', false) diff --git a/test/lib/utils/proc-log-listener.js b/test/lib/utils/proc-log-listener.js deleted file mode 100644 index d580defa8..000000000 --- a/test/lib/utils/proc-log-listener.js +++ /dev/null @@ -1,41 +0,0 @@ -const t = require('tap') -const { inspect } = require('util') - -const logs = [] -const npmlog = { - warn: (...args) => logs.push(['warn', ...args]), - verbose: (...args) => logs.push(['verbose', ...args]), -} - -t.mock('../../../lib/utils/proc-log-listener.js', { - npmlog, -})() - -process.emit('log', 'warn', 'hello', 'i am a warning') -t.strictSame(logs, [['warn', 'hello', 'i am a warning']]) -logs.length = 0 - -const nopeError = new Error('nope') -npmlog.warn = () => { - throw nopeError -} - -process.emit('log', 'warn', 'fail') -t.strictSame(logs, [[ - 'verbose', - `attempt to log ${inspect(['warn', 'fail'])} crashed`, - nopeError, -]]) -logs.length = 0 - -npmlog.verbose = () => { - throw nopeError -} -const consoleErrors = [] -console.error = (...args) => consoleErrors.push(args) -process.emit('log', 'warn', 'fail2') -t.strictSame(logs, []) -t.strictSame(consoleErrors, [[ - `attempt to log ${inspect(['warn', 'fail2'])} crashed`, - nopeError, -]]) diff --git a/test/lib/utils/pulse-till-done.js b/test/lib/utils/pulse-till-done.js index acbf66396..9f7a94614 100644 --- a/test/lib/utils/pulse-till-done.js +++ b/test/lib/utils/pulse-till-done.js @@ -1,18 +1,17 @@ const t = require('tap') let pulseStarted = null -const npmlog = { - gauge: { - pulse: () => { - if (pulseStarted) { - pulseStarted() - } - }, - }, -} const pulseTillDone = t.mock('../../../lib/utils/pulse-till-done.js', { - npmlog, + npmlog: { + gauge: { + pulse: () => { + if (pulseStarted) { + pulseStarted() + } + }, + }, + }, }) t.test('pulses (with promise)', async (t) => { diff --git a/test/lib/utils/read-user-info.js b/test/lib/utils/read-user-info.js index 35101f1d7..be805a2a8 100644 --- a/test/lib/utils/read-user-info.js +++ b/test/lib/utils/read-user-info.js @@ -7,11 +7,6 @@ const read = (opts, cb) => { return cb(null, readResult) } -const npmlog = { - clearProgress: () => {}, - showProgress: () => {}, -} - const npmUserValidate = { username: (username) => { if (username === 'invalid') { @@ -29,12 +24,23 @@ const npmUserValidate = { }, } +let logMsg = null const readUserInfo = t.mock('../../../lib/utils/read-user-info.js', { read, - npmlog, + npmlog: { + clearProgress: () => {}, + showProgress: () => {}, + }, + 'proc-log': { + warn: (msg) => logMsg = msg, + }, 'npm-user-validate': npmUserValidate, }) +t.beforeEach(() => { + logMsg = null +}) + t.test('otp', async (t) => { readResult = '1234' t.teardown(() => { @@ -75,11 +81,7 @@ t.test('username - invalid warns and retries', async (t) => { readOpts = null }) - let logMsg - const log = { - warn: (msg) => logMsg = msg, - } - const pResult = readUserInfo.username(null, null, { log }) + const pResult = readUserInfo.username(null, null) // have to swap it to a valid username after execution starts // or it will loop forever readResult = 'valid' @@ -105,11 +107,7 @@ t.test('email - invalid warns and retries', async (t) => { readOpts = null }) - let logMsg - const log = { - warn: (msg) => logMsg = msg, - } - const pResult = readUserInfo.email(null, null, { log }) + const pResult = readUserInfo.email(null, null) readResult = 'foo@bar.baz' const result = await pResult t.equal(result, 'foo@bar.baz', 'received the email') diff --git a/test/lib/utils/reify-output.js b/test/lib/utils/reify-output.js index 9a1bffb40..4e9ed7133 100644 --- a/test/lib/utils/reify-output.js +++ b/test/lib/utils/reify-output.js @@ -1,7 +1,9 @@ const t = require('tap') +const log = require('../../../lib/utils/log-shim') -const log = require('npmlog') -log.level = 'warn' +const _level = log.level +t.beforeEach(() => log.level = 'warn') +t.teardown(() => log.level = _level) t.cleanSnapshot = str => str.replace(/in [0-9]+m?s/g, 'in {TIME}') @@ -237,7 +239,6 @@ t.test('showing and not showing audit report', async t => { npm.output = out => { t.fail('should not get output when silent', { actual: out }) } - t.teardown(() => log.level = 'warn') log.level = 'silent' reifyOutput(npm, { actualTree: { inventory: { size: 999 }, children: [] }, diff --git a/test/lib/utils/setup-log.js b/test/lib/utils/setup-log.js deleted file mode 100644 index 7f907bc7e..000000000 --- a/test/lib/utils/setup-log.js +++ /dev/null @@ -1,296 +0,0 @@ -const t = require('tap') - -const settings = { - level: 'warn', -} -t.afterEach(() => { - Object.keys(settings).forEach(k => { - delete settings[k] - }) -}) - -const WARN_CALLED = [] -const npmlog = { - warn: (...args) => { - WARN_CALLED.push(args) - }, - levels: { - silly: -Infinity, - verbose: 1000, - info: 2000, - timing: 2500, - http: 3000, - notice: 3500, - warn: 4000, - error: 5000, - silent: Infinity, - }, - settings, - enableColor: () => { - settings.color = true - }, - disableColor: () => { - settings.color = false - }, - enableUnicode: () => { - settings.unicode = true - }, - disableUnicode: () => { - settings.unicode = false - }, - enableProgress: () => { - settings.progress = true - }, - disableProgress: () => { - settings.progress = false - }, - get heading () { - return settings.heading - }, - set heading (h) { - settings.heading = h - }, - get level () { - return settings.level - }, - set level (l) { - settings.level = l - }, -} - -const EXPLAIN_CALLED = [] -const setupLog = t.mock('../../../lib/utils/setup-log.js', { - '../../../lib/utils/explain-eresolve.js': { - explain: (...args) => { - EXPLAIN_CALLED.push(args) - return 'explanation' - }, - }, - npmlog, -}) - -const config = obj => ({ - get (k) { - return obj[k] - }, - set (k, v) { - obj[k] = v - }, -}) - -t.test('setup with color=always and unicode', t => { - npmlog.warn('ERESOLVE', 'hello', { some: 'object' }) - t.strictSame(EXPLAIN_CALLED, [], 'log.warn() not patched yet') - t.strictSame(WARN_CALLED, [['ERESOLVE', 'hello', { some: 'object' }]]) - WARN_CALLED.length = 0 - - setupLog(config({ - loglevel: 'warn', - color: 'always', - unicode: true, - progress: false, - })) - - npmlog.warn('ERESOLVE', 'hello', { some: { other: 'object' } }) - t.strictSame(EXPLAIN_CALLED, [[{ some: { other: 'object' } }, true, 2]], - 'log.warn(ERESOLVE) patched to call explainEresolve()') - t.strictSame(WARN_CALLED, [ - ['ERESOLVE', 'hello'], - ['', 'explanation'], - ], 'warn the explanation') - EXPLAIN_CALLED.length = 0 - WARN_CALLED.length = 0 - npmlog.warn('some', 'other', 'thing') - t.strictSame(EXPLAIN_CALLED, [], 'do not try to explain other things') - t.strictSame(WARN_CALLED, [['some', 'other', 'thing']], 'warnings passed through') - - t.strictSame(settings, { - level: 'warn', - color: true, - unicode: true, - progress: false, - heading: 'npm', - }) - - t.end() -}) - -t.test('setup with color=true, no unicode, and non-TTY terminal', t => { - const { isTTY: stderrIsTTY } = process.stderr - const { isTTY: stdoutIsTTY } = process.stdout - t.teardown(() => { - process.stderr.isTTY = stderrIsTTY - process.stdout.isTTY = stdoutIsTTY - }) - process.stderr.isTTY = false - process.stdout.isTTY = false - - setupLog(config({ - loglevel: 'warn', - color: false, - progress: false, - heading: 'asdf', - })) - - t.strictSame(settings, { - level: 'warn', - color: false, - unicode: false, - progress: false, - heading: 'asdf', - }) - - t.end() -}) - -t.test('setup with color=true, no unicode, and dumb TTY terminal', t => { - const { isTTY: stderrIsTTY } = process.stderr - const { isTTY: stdoutIsTTY } = process.stdout - const { TERM } = process.env - t.teardown(() => { - process.stderr.isTTY = stderrIsTTY - process.stdout.isTTY = stdoutIsTTY - process.env.TERM = TERM - }) - process.stderr.isTTY = true - process.stdout.isTTY = true - process.env.TERM = 'dumb' - - setupLog(config({ - loglevel: 'warn', - color: true, - progress: false, - heading: 'asdf', - })) - - t.strictSame(settings, { - level: 'warn', - color: true, - unicode: false, - progress: false, - heading: 'asdf', - }) - - t.end() -}) - -t.test('setup with color=true, no unicode, and non-dumb TTY terminal', t => { - const { isTTY: stderrIsTTY } = process.stderr - const { isTTY: stdoutIsTTY } = process.stdout - const { TERM } = process.env - t.teardown(() => { - process.stderr.isTTY = stderrIsTTY - process.stdout.isTTY = stdoutIsTTY - process.env.TERM = TERM - }) - process.stderr.isTTY = true - process.stdout.isTTY = true - process.env.TERM = 'totes not dum' - - setupLog(config({ - loglevel: 'warn', - color: true, - progress: true, - heading: 'asdf', - })) - - t.strictSame(settings, { - level: 'warn', - color: true, - unicode: false, - progress: true, - heading: 'asdf', - }) - - t.end() -}) - -t.test('setup with non-TTY stdout, TTY stderr', t => { - const { isTTY: stderrIsTTY } = process.stderr - const { isTTY: stdoutIsTTY } = process.stdout - const { TERM } = process.env - t.teardown(() => { - process.stderr.isTTY = stderrIsTTY - process.stdout.isTTY = stdoutIsTTY - process.env.TERM = TERM - }) - process.stderr.isTTY = true - process.stdout.isTTY = false - process.env.TERM = 'definitely not a dummy' - - setupLog(config({ - loglevel: 'warn', - color: true, - progress: true, - heading: 'asdf', - })) - - t.strictSame(settings, { - level: 'warn', - color: true, - unicode: false, - progress: true, - heading: 'asdf', - }) - - t.end() -}) - -t.test('setup with TTY stdout, non-TTY stderr', t => { - const { isTTY: stderrIsTTY } = process.stderr - const { isTTY: stdoutIsTTY } = process.stdout - const { TERM } = process.env - t.teardown(() => { - process.stderr.isTTY = stderrIsTTY - process.stdout.isTTY = stdoutIsTTY - process.env.TERM = TERM - }) - process.stderr.isTTY = false - process.stdout.isTTY = true - - setupLog(config({ - loglevel: 'warn', - color: true, - progress: true, - heading: 'asdf', - })) - - t.strictSame(settings, { - level: 'warn', - color: false, - unicode: false, - progress: false, - heading: 'asdf', - }) - - t.end() -}) - -t.test('set loglevel to timing', t => { - setupLog(config({ - timing: true, - loglevel: 'notice', - })) - t.equal(settings.level, 'timing') - t.end() -}) - -t.test('silent has no logging', t => { - const { isTTY: stderrIsTTY } = process.stderr - const { isTTY: stdoutIsTTY } = process.stdout - const { TERM } = process.env - t.teardown(() => { - process.stderr.isTTY = stderrIsTTY - process.stdout.isTTY = stdoutIsTTY - process.env.TERM = TERM - }) - process.stderr.isTTY = true - process.stdout.isTTY = true - process.env.TERM = 'totes not dum' - - setupLog(config({ - loglevel: 'silent', - })) - t.equal(settings.progress, false, 'progress disabled when silent') - t.end() -}) diff --git a/test/lib/utils/tar.js b/test/lib/utils/tar.js index 19d949169..adc5cb364 100644 --- a/test/lib/utils/tar.js +++ b/test/lib/utils/tar.js @@ -2,18 +2,20 @@ const t = require('tap') const pack = require('libnpmpack') const ssri = require('ssri') -const { logTar, getContents } = require('../../../lib/utils/tar.js') +const { getContents } = require('../../../lib/utils/tar.js') -const printLogs = (tarball, unicode) => { +const mockTar = ({ notice }) => t.mock('../../../lib/utils/tar.js', { + 'proc-log': { + notice, + }, +}) + +const printLogs = (tarball, options) => { const logs = [] - logTar(tarball, { - log: { - notice: (...args) => { - args.map(el => logs.push(el)) - }, - }, - unicode, + const { logTar } = mockTar({ + notice: (...args) => args.map(el => logs.push(el)), }) + logTar(tarball, options) return logs.join('\n') } @@ -41,16 +43,14 @@ t.test('should log tarball contents', async (t) => { version: '1.0.0', }, tarball) - t.matchSnapshot(printLogs(tarballContents, false)) + t.matchSnapshot(printLogs(tarballContents)) }) t.test('should log tarball contents with unicode', async (t) => { - const { logTar } = t.mock('../../../lib/utils/tar.js', { - npmlog: { - notice: (str) => { - t.ok(true, 'defaults to npmlog') - return str - }, + const { logTar } = mockTar({ + notice: (str) => { + t.ok(true, 'defaults to proc-log') + return str }, }) @@ -64,26 +64,6 @@ t.test('should log tarball contents with unicode', async (t) => { t.end() }) -t.test('should default to npmlog', async (t) => { - const { logTar } = t.mock('../../../lib/utils/tar.js', { - npmlog: { - notice: (str) => { - t.ok(true, 'defaults to npmlog') - return str - }, - }, - }) - - logTar({ - files: [], - bundled: [], - size: 0, - unpackedSize: 0, - integrity: '', - }) - t.end() -}) - t.test('should getContents of a tarball', async (t) => { const testDir = t.testdir({ 'package.json': JSON.stringify({ diff --git a/test/lib/utils/timers.js b/test/lib/utils/timers.js new file mode 100644 index 000000000..6127f346b --- /dev/null +++ b/test/lib/utils/timers.js @@ -0,0 +1,82 @@ +const t = require('tap') +const { resolve } = require('path') +const fs = require('graceful-fs') +const mockLogs = require('../../fixtures/mock-logs') + +const mockTimers = (t, options) => { + const { logs, logMocks } = mockLogs() + const Timers = t.mock('../../../lib/utils/timers', { + ...logMocks, + }) + const timers = new Timers(options) + t.teardown(() => timers.off()) + return { timers, logs } +} + +t.test('getters', async (t) => { + const { timers } = mockTimers(t) + t.match(timers.unfinished, new Map()) + t.match(timers.finished, {}) +}) + +t.test('listens/stops on process', async (t) => { + const { timers } = mockTimers(t) + process.emit('time', 'foo') + process.emit('time', 'bar') + process.emit('timeEnd', 'bar') + t.match(timers.unfinished, new Map([['foo', Number]])) + t.match(timers.finished, { bar: Number }) + timers.off() + process.emit('time', 'baz') + t.notOk(timers.unfinished.get('baz')) +}) + +t.test('initial timer', async (t) => { + const { timers } = mockTimers(t, { start: 'foo' }) + process.emit('timeEnd', 'foo') + t.match(timers.finished, { foo: Number }) +}) + +t.test('initial listener', async (t) => { + const events = [] + const listener = (...args) => events.push(args) + const { timers } = mockTimers(t, { listener }) + process.emit('time', 'foo') + process.emit('time', 'bar') + process.emit('timeEnd', 'bar') + timers.off(listener) + process.emit('timeEnd', 'foo') + t.equal(events.length, 1) + t.match(events, [['bar', Number]]) +}) + +t.test('finish unstarted timer', async (t) => { + const { logs } = mockTimers(t) + process.emit('timeEnd', 'foo') + t.match(logs.silly, [['timing', /^Tried to end timer/, 'foo']]) +}) + +t.test('writes file', async (t) => { + const { timers } = mockTimers(t) + const dir = t.testdir() + process.emit('time', 'foo') + process.emit('timeEnd', 'foo') + timers.load({ dir }) + timers.writeFile({ some: 'data' }) + const data = JSON.parse(fs.readFileSync(resolve(dir, '_timing.json'))) + t.match(data, { + some: 'data', + foo: Number, + unfinished: { + npm: [Number, Number], + }, + }) +}) + +t.test('fails to write file', async (t) => { + const { logs, timers } = mockTimers(t) + timers.writeFile() + t.match(logs.warn, [ + ['timing', 'could not write timing file', Error], + ]) +}) diff --git a/test/lib/utils/unsupported.js b/test/lib/utils/unsupported.js index 4d806cefc..2703044a2 100644 --- a/test/lib/utils/unsupported.js +++ b/test/lib/utils/unsupported.js @@ -1,5 +1,6 @@ const t = require('tap') const unsupported = require('../../../lib/utils/unsupported.js') +const mockGlobals = require('../../fixtures/mock-globals.js') const versions = [ // broken unsupported @@ -55,42 +56,30 @@ t.test('checkForBrokenNode', t => { // run it once to not fail unsupported.checkForBrokenNode() - const { exit } = process - const { error } = console - const versionPropDesc = Object.getOwnPropertyDescriptor(process, 'version') - - t.teardown(() => { - process.exit = exit - Object.defineProperty(process, 'version', versionPropDesc) - console.error = error - }) - - // then make it a thing that fails - process.exit = code => { - t.equal(code, 1) - t.strictSame(logs, expectLogs) - t.end() - } - Object.defineProperty(process, 'version', { value: '1.2.3', configurable: true }) const logs = [] const expectLogs = [ 'ERROR: npm is known not to run on Node.js 1.2.3', "You'll need to upgrade to a newer Node.js version in order to use this", 'version of npm. You can find the latest version at https://nodejs.org/', ] - console.error = msg => logs.push(msg) + + // then make it a thing that fails + mockGlobals(t, { + 'console.error': msg => logs.push(msg), + 'process.version': '1.2.3', + 'process.exit': (code) => { + t.equal(code, 1) + t.strictSame(logs, expectLogs) + t.end() + }, + }) + unsupported.checkForBrokenNode() }) t.test('checkForUnsupportedNode', t => { - const npmlog = require('npmlog') - const { warn } = npmlog - const versionPropDesc = Object.getOwnPropertyDescriptor(process, 'version') - - t.teardown(() => { - Object.defineProperty(process, 'version', versionPropDesc) - npmlog.warn = warn - }) + // run it once to not fail or warn + unsupported.checkForUnsupportedNode() const logs = [] const expectLogs = [ @@ -99,14 +88,15 @@ t.test('checkForUnsupportedNode', t => { "can't make any promises that npm will work with this version.", 'You can find the latest version at https://nodejs.org/', ] - npmlog.warn = (section, msg) => logs.push(msg) - - // run it once to not fail or warn - unsupported.checkForUnsupportedNode() // then make it a thing that fails - Object.defineProperty(process, 'version', { value: '8.0.0' }) + mockGlobals(t, { + 'console.error': msg => logs.push(msg), + 'process.version': '8.0.0', + }) + unsupported.checkForUnsupportedNode() + t.strictSame(logs, expectLogs) t.end() }) diff --git a/test/lib/utils/update-notifier.js b/test/lib/utils/update-notifier.js index 78ff93825..a7a800c60 100644 --- a/test/lib/utils/update-notifier.js +++ b/test/lib/utils/update-notifier.js @@ -36,18 +36,13 @@ const pacote = { }, } -const npm = { +const defaultNpm = { flatOptions, - log: { useColor: () => true }, version: CURRENT_VERSION, config: { get: k => k !== 'global' }, command: 'view', argv: ['npm'], } -const npmNoColor = { - ...npm, - log: { useColor: () => false }, -} const { basename } = require('path') @@ -80,12 +75,6 @@ const fs = { }, } -const updateNotifier = t.mock('../../../lib/utils/update-notifier.js', { - '@npmcli/ci-detect': () => ciMock, - pacote, - fs, -}) - t.afterEach(() => { MANIFEST_REQUEST.length = 0 STAT_ERROR = null @@ -94,16 +83,21 @@ t.afterEach(() => { WRITE_ERROR = null }) -const runUpdateNotifier = async npm => { - await updateNotifier(npm) - return npm.updateNotification +const runUpdateNotifier = async ({ color = true, ...npmOptions } = {}) => { + const _npm = { ...defaultNpm, ...npmOptions } + await t.mock('../../../lib/utils/update-notifier.js', { + '@npmcli/ci-detect': () => ciMock, + pacote, + fs, + npmlog: { useColor: () => color }, + })(_npm) + return _npm.updateNotification } t.test('situations in which we do not notify', t => { t.test('nothing to do if notifier disabled', async t => { t.equal( await runUpdateNotifier({ - ...npm, config: { get: k => k !== 'update-notifier' }, }), null @@ -114,7 +108,6 @@ t.test('situations in which we do not notify', t => { t.test('do not suggest update if already updating', async t => { t.equal( await runUpdateNotifier({ - ...npm, flatOptions: { ...flatOptions, global: true }, command: 'install', argv: ['npm'], @@ -127,7 +120,6 @@ t.test('situations in which we do not notify', t => { t.test('do not suggest update if already updating with spec', async t => { t.equal( await runUpdateNotifier({ - ...npm, flatOptions: { ...flatOptions, global: true }, command: 'install', argv: ['npm@latest'], @@ -138,31 +130,31 @@ t.test('situations in which we do not notify', t => { }) t.test('do not update if same as latest', async t => { - t.equal(await runUpdateNotifier(npm), null) + t.equal(await runUpdateNotifier(), null) t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version') }) t.test('check if stat errors (here for coverage)', async t => { STAT_ERROR = new Error('blorg') - t.equal(await runUpdateNotifier(npm), null) + t.equal(await runUpdateNotifier(), null) t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version') }) t.test('ok if write errors (here for coverage)', async t => { WRITE_ERROR = new Error('grolb') - t.equal(await runUpdateNotifier(npm), null) + t.equal(await runUpdateNotifier(), null) t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version') }) t.test('ignore pacote failures (here for coverage)', async t => { PACOTE_ERROR = new Error('pah-KO-tchay') - t.equal(await runUpdateNotifier(npm), null) + t.equal(await runUpdateNotifier(), null) t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version') }) t.test('do not update if newer than latest, but same as next', async t => { - t.equal(await runUpdateNotifier({ ...npm, version: NEXT_VERSION }), null) + t.equal(await runUpdateNotifier({ version: NEXT_VERSION }), null) const reqs = ['npm@latest', `npm@^${NEXT_VERSION}`] t.strictSame(MANIFEST_REQUEST, reqs, 'requested latest and next versions') }) t.test('do not update if on the latest beta', async t => { - t.equal(await runUpdateNotifier({ ...npm, version: CURRENT_BETA }), null) + t.equal(await runUpdateNotifier({ version: CURRENT_BETA }), null) const reqs = [`npm@^${CURRENT_BETA}`] t.strictSame(MANIFEST_REQUEST, reqs, 'requested latest and next versions') }) @@ -172,21 +164,21 @@ t.test('situations in which we do not notify', t => { ciMock = null }) ciMock = 'something' - t.equal(await runUpdateNotifier(npm), null) + t.equal(await runUpdateNotifier(), null) t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests') }) t.test('only check weekly for GA releases', async t => { // One week (plus five minutes to account for test environment fuzziness) STAT_MTIME = Date.now() - 1000 * 60 * 60 * 24 * 7 + 1000 * 60 * 5 - t.equal(await runUpdateNotifier(npm), null) + t.equal(await runUpdateNotifier(), null) t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests') }) t.test('only check daily for betas', async t => { // One day (plus five minutes to account for test environment fuzziness) STAT_MTIME = Date.now() - 1000 * 60 * 60 * 24 + 1000 * 60 * 5 - t.equal(await runUpdateNotifier({ ...npm, version: HAVE_BETA }), null) + t.equal(await runUpdateNotifier({ version: HAVE_BETA }), null) t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests') }) @@ -196,9 +188,9 @@ t.test('situations in which we do not notify', t => { t.test('notification situations', t => { t.test('new beta available', async t => { const version = HAVE_BETA - t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color') + t.matchSnapshot(await runUpdateNotifier({ version }), 'color') t.matchSnapshot( - await runUpdateNotifier({ ...npmNoColor, version }), + await runUpdateNotifier({ version, color: false }), 'no color' ) t.strictSame(MANIFEST_REQUEST, [`npm@^${version}`, `npm@^${version}`]) @@ -206,9 +198,9 @@ t.test('notification situations', t => { t.test('patch to next version', async t => { const version = NEXT_PATCH - t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color') + t.matchSnapshot(await runUpdateNotifier({ version }), 'color') t.matchSnapshot( - await runUpdateNotifier({ ...npmNoColor, version }), + await runUpdateNotifier({ version, color: false }), 'no color' ) t.strictSame(MANIFEST_REQUEST, [ @@ -221,9 +213,9 @@ t.test('notification situations', t => { t.test('minor to next version', async t => { const version = NEXT_MINOR - t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color') + t.matchSnapshot(await runUpdateNotifier({ version }), 'color') t.matchSnapshot( - await runUpdateNotifier({ ...npmNoColor, version }), + await runUpdateNotifier({ version, color: false }), 'no color' ) t.strictSame(MANIFEST_REQUEST, [ @@ -236,9 +228,9 @@ t.test('notification situations', t => { t.test('patch to current', async t => { const version = CURRENT_PATCH - t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color') + t.matchSnapshot(await runUpdateNotifier({ version }), 'color') t.matchSnapshot( - await runUpdateNotifier({ ...npmNoColor, version }), + await runUpdateNotifier({ version, color: false }), 'no color' ) t.strictSame(MANIFEST_REQUEST, ['npm@latest', 'npm@latest']) @@ -246,9 +238,9 @@ t.test('notification situations', t => { t.test('minor to current', async t => { const version = CURRENT_MINOR - t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color') + t.matchSnapshot(await runUpdateNotifier({ version }), 'color') t.matchSnapshot( - await runUpdateNotifier({ ...npmNoColor, version }), + await runUpdateNotifier({ version, color: false }), 'no color' ) t.strictSame(MANIFEST_REQUEST, ['npm@latest', 'npm@latest']) @@ -256,9 +248,9 @@ t.test('notification situations', t => { t.test('major to current', async t => { const version = CURRENT_MAJOR - t.matchSnapshot(await runUpdateNotifier({ ...npm, version }), 'color') + t.matchSnapshot(await runUpdateNotifier({ version }), 'color') t.matchSnapshot( - await runUpdateNotifier({ ...npmNoColor, version }), + await runUpdateNotifier({ version, color: false }), 'no color' ) t.strictSame(MANIFEST_REQUEST, ['npm@latest', 'npm@latest']) |