diff options
author | Ruy Adorno <ruyadorno@hotmail.com> | 2020-12-18 15:39:05 -0500 |
---|---|---|
committer | Michaƫl Zasso <targos@protonmail.com> | 2020-12-21 14:29:20 +0100 |
commit | ffc11c6015bbf5d950328fca28de77a9cebd2ae2 (patch) | |
tree | f3343108a6aea58f8d7d0007b0e3d6032609cd2b /deps/npm/test/lib | |
parent | eefb424c6060582714bcef09a12fc7a74c35384a (diff) | |
download | node-new-ffc11c6015bbf5d950328fca28de77a9cebd2ae2.tar.gz |
deps: upgrade npm to 7.3.0
PR-URL: https://github.com/nodejs/node/pull/36572
Reviewed-By: Myles Borins <myles.borins@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Diffstat (limited to 'deps/npm/test/lib')
-rw-r--r-- | deps/npm/test/lib/adduser.js | 24 | ||||
-rw-r--r-- | deps/npm/test/lib/config.js | 92 | ||||
-rw-r--r-- | deps/npm/test/lib/deprecate.js | 4 | ||||
-rw-r--r-- | deps/npm/test/lib/dist-tag.js | 2 | ||||
-rw-r--r-- | deps/npm/test/lib/help-search.js | 3 | ||||
-rw-r--r-- | deps/npm/test/lib/ls.js | 24 | ||||
-rw-r--r-- | deps/npm/test/lib/npm.js | 9 | ||||
-rw-r--r-- | deps/npm/test/lib/owner.js | 2 | ||||
-rw-r--r-- | deps/npm/test/lib/profile.js | 1465 | ||||
-rw-r--r-- | deps/npm/test/lib/publish.js | 77 | ||||
-rw-r--r-- | deps/npm/test/lib/utils/error-handler.js | 5 | ||||
-rw-r--r-- | deps/npm/test/lib/utils/flat-options.js | 5 | ||||
-rw-r--r-- | deps/npm/test/lib/utils/reify-output.js | 5 | ||||
-rw-r--r-- | deps/npm/test/lib/utils/replace-info.js | 2 |
14 files changed, 1665 insertions, 54 deletions
diff --git a/deps/npm/test/lib/adduser.js b/deps/npm/test/lib/adduser.js index 4e6a56fc19..36f59e0857 100644 --- a/deps/npm/test/lib/adduser.js +++ b/deps/npm/test/lib/adduser.js @@ -9,21 +9,27 @@ const _flatOptions = { authType: 'legacy', registry: 'https://registry.npmjs.org/', scope: '', + fromFlatOptions: true, } let failSave = false let deletedConfig = {} let registryOutput = '' let setConfig = {} -const authDummy = () => Promise.resolve({ - message: 'success', - newCreds: { - username: 'u', - password: 'p', - email: 'u@npmjs.org', - alwaysAuth: false, - }, -}) +const authDummy = (options) => { + if (!options.fromFlatOptions) + throw new Error('did not pass full flatOptions to auth function') + + return Promise.resolve({ + message: 'success', + newCreds: { + username: 'u', + password: 'p', + email: 'u@npmjs.org', + alwaysAuth: false, + }, + }) +} const deleteMock = (key, where) => { deletedConfig = { diff --git a/deps/npm/test/lib/config.js b/deps/npm/test/lib/config.js index 74cd530c68..5d2f54249c 100644 --- a/deps/npm/test/lib/config.js +++ b/deps/npm/test/lib/config.js @@ -122,7 +122,7 @@ t.test('config list overrides', t => { config(['list'], (err) => { t.ifError(err, 'npm config list') - t.matchSnapshot(result, 'should list overriden configs') + t.matchSnapshot(result, 'should list overridden configs') }) }) @@ -212,6 +212,33 @@ t.test('config delete key', t => { }) }) +t.test('config delete multiple key', t => { + t.plan(6) + + const expect = [ + 'foo', + 'bar', + ] + + npm.config.delete = (key, where) => { + t.equal(key, expect.shift(), 'should delete expected keyword') + t.equal(where, 'user', 'should delete key from user config by default') + } + + npm.config.save = where => { + t.equal(where, 'user', 'should save user config post-delete') + } + + config(['delete', 'foo', 'bar'], (err) => { + t.ifError(err, 'npm config delete keys') + }) + + t.teardown(() => { + delete npm.config.delete + delete npm.config.save + }) +}) + t.test('config delete key --global', t => { t.plan(4) @@ -293,12 +320,43 @@ t.test('config set key=val', t => { }) }) +t.test('config set multiple keys', t => { + t.plan(11) + + const expect = [ + ['foo', 'bar'], + ['bar', 'baz'], + ['asdf', ''], + ] + const args = ['foo', 'bar', 'bar=baz', 'asdf'] + + npm.config.set = (key, val, where) => { + const [expectKey, expectVal] = expect.shift() + t.equal(key, expectKey, 'should set expected key to user config') + t.equal(val, expectVal, 'should set expected value to user config') + t.equal(where, 'user', 'should set key/val in user config by default') + } + + npm.config.save = where => { + t.equal(where, 'user', 'should save user config') + } + + config(['set', ...args], (err) => { + t.ifError(err, 'npm config set key') + }) + + t.teardown(() => { + delete npm.config.set + delete npm.config.save + }) +}) + t.test('config set key to empty value', t => { t.plan(5) npm.config.set = (key, val, where) => { t.equal(key, 'foo', 'should set expected key to user config') - t.equal(val, '', 'should set empty value to user config') + t.equal(val, '', 'should set "" to user config') t.equal(where, 'user', 'should set key/val in user config by default') } @@ -403,6 +461,36 @@ t.test('config get key', t => { }) }) +t.test('config get multiple keys', t => { + t.plan(4) + + const expect = [ + 'foo', + 'bar', + ] + + const npmConfigGet = npm.config.get + npm.config.get = (key) => { + t.equal(key, expect.shift(), 'should use expected key') + return 'asdf' + } + + npm.config.save = where => { + throw new Error('should not save') + } + + config(['get', 'foo', 'bar'], (err) => { + t.ifError(err, 'npm config get multiple keys') + t.equal(result, 'foo=asdf\nbar=asdf') + }) + + t.teardown(() => { + result = '' + npm.config.get = npmConfigGet + delete npm.config.save + }) +}) + t.test('config get private key', t => { config(['get', '//private-reg.npmjs.org/:_authThoken'], (err) => { t.match( diff --git a/deps/npm/test/lib/deprecate.js b/deps/npm/test/lib/deprecate.js index 3908254ed0..229cb9137a 100644 --- a/deps/npm/test/lib/deprecate.js +++ b/deps/npm/test/lib/deprecate.js @@ -13,6 +13,7 @@ npmFetch.json = async (uri, opts) => { versions: { '1.0.0': {}, '1.0.1': {}, + '1.0.1-pre': {}, }, } } @@ -126,6 +127,9 @@ test('deprecates all versions when no range is specified', t => { '1.0.1': { deprecated: 'this version is deprecated', }, + '1.0.1-pre': { + deprecated: 'this version is deprecated', + }, }, }) diff --git a/deps/npm/test/lib/dist-tag.js b/deps/npm/test/lib/dist-tag.js index e9dde48062..8b1106fa39 100644 --- a/deps/npm/test/lib/dist-tag.js +++ b/deps/npm/test/lib/dist-tag.js @@ -323,6 +323,6 @@ test('completion', t => { }, }, }, (err) => { - t.notOk(err, 'should ignore any unkown name') + t.notOk(err, 'should ignore any unknown name') }) }) diff --git a/deps/npm/test/lib/help-search.js b/deps/npm/test/lib/help-search.js index 5ecf5db061..f74e2f1efe 100644 --- a/deps/npm/test/lib/help-search.js +++ b/deps/npm/test/lib/help-search.js @@ -40,7 +40,8 @@ const globDir = { 'npm-more-useless.md': 'exec exec', 'npm-extra-useless.md': 'exec\nexec\nexec', } -const glob = (p, cb) => cb(null, Object.keys(globDir).map((file) => join(globRoot, file))) +const glob = (p, cb) => + cb(null, Object.keys(globDir).map((file) => join(globRoot, file))) const helpSearch = requireInject('../../lib/help-search.js', { '../../lib/npm.js': npm, diff --git a/deps/npm/test/lib/ls.js b/deps/npm/test/lib/ls.js index 256ebf3534..7bbfc5f772 100644 --- a/deps/npm/test/lib/ls.js +++ b/deps/npm/test/lib/ls.js @@ -209,7 +209,7 @@ t.test('ls', (t) => { }) ls(['lorem'], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurences of filtered by package and coloured output') + t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurrences of filtered by package and colored output') _flatOptions.color = false t.end() }) @@ -231,7 +231,7 @@ t.test('ls', (t) => { }) ls(['.'], (err) => { t.ifError(err, 'should not throw on missing dep above current level') - t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurences of filtered by package and coloured output') + t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurrences of filtered by package and colored output') _flatOptions.all = true _flatOptions.depth = Infinity t.end() @@ -252,7 +252,7 @@ t.test('ls', (t) => { }) ls(['bar'], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurences of filtered package and its ancestors') + t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurrences of filtered package and its ancestors') t.end() }) }) @@ -280,7 +280,7 @@ t.test('ls', (t) => { }) ls(['bar@*', 'lorem@1.0.0'], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurences of multiple filtered packages and their ancestors') + t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurrences of multiple filtered packages and their ancestors') t.end() }) }) @@ -443,7 +443,7 @@ t.test('ls', (t) => { }) }) - t.test('coloured output', (t) => { + t.test('colored output', (t) => { _flatOptions.color = true prefix = t.testdir({ 'package.json': JSON.stringify({ @@ -1588,7 +1588,7 @@ t.test('ls --parseable', (t) => { }) ls(['lorem'], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurences of filtered by package') + t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurrences of filtered by package') t.end() }) }) @@ -1607,7 +1607,7 @@ t.test('ls --parseable', (t) => { }) ls(['bar'], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurences of filtered package') + t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurrences of filtered package') t.end() }) }) @@ -1635,7 +1635,7 @@ t.test('ls --parseable', (t) => { }) ls(['bar@*', 'lorem@1.0.0'], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurences of multiple filtered packages and their ancestors') + t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurrences of multiple filtered packages and their ancestors') t.end() }) }) @@ -2158,7 +2158,7 @@ t.test('ls --parseable', (t) => { }, }) ls([], () => { - t.matchSnapshot(redactCwd(result), 'should print tree output ommiting deduped ref') + t.matchSnapshot(redactCwd(result), 'should print tree output omitting deduped ref') t.end() }) }) @@ -2482,7 +2482,7 @@ t.test('ls --json', (t) => { }, }, }, - 'should output json contaning only occurences of filtered by package' + 'should output json contaning only occurrences of filtered by package' ) t.equal( process.exitCode, @@ -2523,7 +2523,7 @@ t.test('ls --json', (t) => { }, }, }, - 'should output json contaning only occurences of filtered by package' + 'should output json contaning only occurrences of filtered by package' ) t.end() }) @@ -2571,7 +2571,7 @@ t.test('ls --json', (t) => { }, }, }, - 'should output json contaning only occurences of multiple filtered packages and their ancestors' + 'should output json contaning only occurrences of multiple filtered packages and their ancestors' ) t.end() }) diff --git a/deps/npm/test/lib/npm.js b/deps/npm/test/lib/npm.js index 2c71d229a7..8494af6bb8 100644 --- a/deps/npm/test/lib/npm.js +++ b/deps/npm/test/lib/npm.js @@ -202,8 +202,6 @@ t.test('npm.load', t => { t.equal(npm.bin, npm.globalBin, 'bin is global bin after prefix setter') t.notEqual(npm.bin, npm.localBin, 'bin is not local bin after prefix setter') - t.equal(npm.config.get('metrics-registry'), npm.config.get('registry')) - beWindows() 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') @@ -261,7 +259,6 @@ t.test('npm.load', t => { process.argv = [ node, process.argv[1], - '--metrics-registry', 'http://example.com', '--prefix', dir, '--userconfig', `${dir}/.npmrc`, '--usage', @@ -292,7 +289,6 @@ t.test('npm.load', t => { throw er t.equal(npm.config.get('scope'), '@foo', 'added the @ sign to scope') - t.equal(npm.config.get('metrics-registry'), 'http://example.com') t.match(logs.filter(l => l[0] !== 'timing' || !/^config:/.test(l[1])), [ [ 'verbose', @@ -342,7 +338,7 @@ t.test('npm.load', t => { /Completed in [0-9]+ms/, ], ]) - t.same(consoleLogs, [['@foo']]) + t.same(consoleLogs, [['scope=@foo\n\u2010not-a-dash=undefined']]) }) // need this here or node 10 will improperly end the promise ahead of time @@ -396,7 +392,6 @@ t.test('set process.title', t => { argv: [ process.execPath, process.argv[1], - '--metrics-registry', 'http://example.com', '--usage', '--scope=foo', 'ls', @@ -415,7 +410,6 @@ t.test('set process.title', t => { argv: [ process.execPath, process.argv[1], - '--metrics-registry', 'http://example.com', '--usage', '--scope=foo', 'token', @@ -436,7 +430,6 @@ t.test('set process.title', t => { argv: [ process.execPath, process.argv[1], - '--metrics-registry', 'http://example.com', '--usage', '--scope=foo', 'token', diff --git a/deps/npm/test/lib/owner.js b/deps/npm/test/lib/owner.js index e217533f0d..c5f9d646c2 100644 --- a/deps/npm/test/lib/owner.js +++ b/deps/npm/test/lib/owner.js @@ -119,7 +119,7 @@ t.test('owner ls fails to retrieve packument', t => { t.match( err, /ERR/, - 'should throw unkown error' + 'should throw unknown error' ) }) }) diff --git a/deps/npm/test/lib/profile.js b/deps/npm/test/lib/profile.js new file mode 100644 index 0000000000..48a558cace --- /dev/null +++ b/deps/npm/test/lib/profile.js @@ -0,0 +1,1465 @@ +const t = require('tap') +const requireInject = require('require-inject') + +let result = '' +const flatOptions = { + otp: '', + json: false, + parseable: false, + registry: 'https://registry.npmjs.org/', +} +const npm = { config: {}, flatOptions: { ...flatOptions }} +const mocks = { + ansistyles: { bright: a => a }, + npmlog: { + gauge: { show () {} }, + info () {}, + notice () {}, + warn () {}, + }, + 'npm-profile': { + async get () {}, + async set () {}, + async createToken () {}, + }, + 'qrcode-terminal': { generate: (url, cb) => cb() }, + 'cli-table3': class extends Array { + toString () { + return this + .filter(Boolean) + .map(i => [...Object.entries(i)] + .map(i => i.join(': '))) + .join('\n') + } + }, + '../../lib/npm.js': npm, + '../../lib/utils/output.js': (...msg) => { + result += msg.join('\n') + }, + '../../lib/utils/pulse-till-done.js': { + withPromise: async a => a, + }, + '../../lib/utils/otplease.js': async (opts, fn) => fn(opts), + '../../lib/utils/usage.js': () => 'usage instructions', + '../../lib/utils/read-user-info.js': { + async password () {}, + async otp () {}, + }, +} +const userProfile = { + tfa: { pending: false, mode: 'auth-and-writes' }, + name: 'foo', + email: 'foo@github.com', + email_verified: true, + created: '2015-02-26T01:26:37.384Z', + updated: '2020-08-12T16:19:35.326Z', + cidr_whitelist: null, + fullname: 'Foo Bar', + homepage: 'https://github.com', + freenode: 'foobar', + twitter: 'https://twitter.com/npmjs', + github: 'https://github.com/npm', +} + +t.afterEach(cb => { + result = '' + npm.config = {} + npm.flatOptions = { ...flatOptions } + cb() +}) + +const profile = requireInject('../../lib/profile.js', mocks) + +t.test('no args', t => { + profile([], err => { + t.match( + err, + /usage instructions/, + 'should throw usage instructions' + ) + t.end() + }) +}) + +t.test('profile get no args', t => { + const npmProfile = { + async get () { + return userProfile + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + }) + + t.test('default output', t => { + profile(['get'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output table with contents' + ) + t.end() + }) + }) + + t.test('--json', t => { + npm.flatOptions.json = true + + profile(['get'], err => { + if (err) + throw err + + t.deepEqual( + JSON.parse(result), + userProfile, + 'should output json profile result' + ) + t.end() + }) + }) + + t.test('--parseable', t => { + npm.flatOptions.parseable = true + + profile(['get'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output all profile info as parseable result' + ) + t.end() + }) + }) + + t.test('no tfa enabled', t => { + const npmProfile = { + async get () { + return { + ...userProfile, + tfa: null, + } + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + }) + + profile(['get'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output expected profile values' + ) + t.end() + }) + }) + + t.test('unverified email', t => { + const npmProfile = { + async get () { + return { + ...userProfile, + email_verified: false, + } + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + }) + + profile(['get'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output table with contents' + ) + t.end() + }) + }) + + t.test('profile has cidr_whitelist item', t => { + const npmProfile = { + async get () { + return { + ...userProfile, + cidr_whitelist: ['192.168.1.1'], + } + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + }) + + profile(['get'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output table with contents' + ) + t.end() + }) + }) + + t.end() +}) + +t.test('profile get <key>', t => { + const npmProfile = { + async get () { + return userProfile + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + }) + + t.test('default output', t => { + profile(['get', 'name'], err => { + if (err) + throw err + + t.equal( + result, + 'foo', + 'should output value result' + ) + t.end() + }) + }) + + t.test('--json', t => { + npm.flatOptions.json = true + + profile(['get', 'name'], err => { + if (err) + throw err + + t.deepEqual( + JSON.parse(result), + userProfile, + 'should output json profile result ignoring args filter' + ) + t.end() + }) + }) + + t.test('--parseable', t => { + npm.flatOptions.parseable = true + + profile(['get', 'name'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output parseable result value' + ) + t.end() + }) + }) + + t.end() +}) + +t.test('profile get multiple args', t => { + const npmProfile = { + async get () { + return userProfile + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + }) + + t.test('default output', t => { + profile(['get', 'name', 'email', 'github'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output all keys' + ) + t.end() + }) + }) + + t.test('--json', t => { + npm.flatOptions.json = true + + profile(['get', 'name', 'email', 'github'], err => { + if (err) + throw err + + t.deepEqual( + JSON.parse(result), + userProfile, + 'should output json profile result and ignore args' + ) + t.end() + }) + }) + + t.test('--parseable', t => { + npm.flatOptions.parseable = true + + profile(['get', 'name', 'email', 'github'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output parseable profile value results' + ) + t.end() + }) + }) + + t.test('comma separated', t => { + profile(['get', 'name,email,github'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output all keys' + ) + t.end() + }) + }) + + t.end() +}) + +t.test('profile set <key> <value>', t => { + const npmProfile = t => ({ + async get () { + return userProfile + }, + async set (newUser, conf) { + t.match( + newUser, + { + fullname: 'Lorem Ipsum', + }, + 'should set new value to key' + ) + return { + ...userProfile, + ...newUser, + } + }, + }) + + t.test('no key', t => { + profile(['set'], err => { + t.match( + err, + /npm profile set <prop> <value>/, + 'should throw proper usage message' + ) + t.end() + }) + }) + + t.test('no value', t => { + profile(['set', 'email'], err => { + t.match( + err, + /npm profile set <prop> <value>/, + 'should throw proper usage message' + ) + t.end() + }) + }) + + t.test('set password', t => { + profile(['set', 'password', '1234'], err => { + t.match( + err, + /Do not include your current or new passwords on the command line./, + 'should throw an error refusing to set password from args' + ) + t.end() + }) + }) + + t.test('unwritable key', t => { + profile(['set', 'name', 'foo'], err => { + t.match( + err, + /"name" is not a property we can set./, + 'should throw the unwritable key error' + ) + t.end() + }) + }) + + t.test('writable key', t => { + t.test('default output', t => { + t.plan(2) + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile(t), + }) + + profile(['set', 'fullname', 'Lorem Ipsum'], err => { + if (err) + throw err + + t.equal( + result, + 'Set\nfullname\nto\nLorem Ipsum', + 'should output set key success msg' + ) + }) + }) + + t.test('--json', t => { + t.plan(2) + + npm.flatOptions.json = true + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile(t), + }) + + profile(['set', 'fullname', 'Lorem Ipsum'], err => { + if (err) + throw err + + t.deepEqual( + JSON.parse(result), + { + fullname: 'Lorem Ipsum', + }, + 'should output json set key success msg' + ) + }) + }) + + t.test('--parseable', t => { + t.plan(2) + + npm.flatOptions.parseable = true + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile(t), + }) + + profile(['set', 'fullname', 'Lorem Ipsum'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output parseable set key success msg' + ) + }) + }) + + t.end() + }) + + t.test('write new email', t => { + t.plan(3) + + const npmProfile = { + async get () { + return userProfile + }, + async set (newUser, conf) { + t.match( + newUser, + { + email: 'foo@npmjs.com', + }, + 'should set new value to email' + ) + t.match( + conf, + npm.flatOptions, + 'should forward flatOptions config' + ) + return { + ...userProfile, + ...newUser, + } + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + }) + + profile(['set', 'email', 'foo@npmjs.com'], err => { + if (err) + throw err + + t.equal( + result, + 'Set\nemail\nto\nfoo@npmjs.com', + 'should output set key success msg' + ) + }) + }) + + t.test('change password', t => { + t.plan(6) + + const npmProfile = { + async get () { + return userProfile + }, + async set (newUser, conf) { + t.match( + newUser, + { + password: { + old: 'currentpassword1234', + new: 'newpassword1234', + }, + }, + 'should set new password' + ) + t.match( + conf, + npm.flatOptions, + 'should forward flatOptions config' + ) + return { + ...userProfile, + } + }, + } + + const readUserInfo = { + async password (label) { + if (label === 'Current password: ') + t.ok('should interactively ask for password confirmation') + else if (label === 'New password: ') + t.ok('should interactively ask for new password') + else if (label === ' Again: ') + t.ok('should interactively ask for new password confirmation') + else + throw new Error('Unexpected label: ' + label) + + return label === 'Current password: ' + ? 'currentpassword1234' + : 'newpassword1234' + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + '../../lib/utils/read-user-info.js': readUserInfo, + }) + + profile(['set', 'password'], err => { + if (err) + throw err + + t.equal( + result, + 'Set\npassword', + 'should output set password success msg' + ) + t.end() + }) + }) + + t.test('password confirmation mismatch', t => { + t.plan(3) + let passwordPromptCount = 0 + + const npmProfile = { + async get () { + return userProfile + }, + async set (newUser, conf) { + return { + ...userProfile, + } + }, + } + + const readUserInfo = { + async password (label) { + passwordPromptCount++ + + switch (label) { + case 'Current password: ': + return 'currentpassword1234' + case 'New password: ': + return passwordPromptCount < 3 + ? 'password-that-will-not-be-confirmed' + : 'newpassword' + case ' Again: ': + return 'newpassword' + default: + return 'password1234' + } + }, + } + + 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 = requireInject('../../lib/profile.js', { + ...mocks, + npmlog, + 'npm-profile': npmProfile, + '../../lib/utils/read-user-info.js': readUserInfo, + }) + + profile(['set', 'password'], err => { + if (err) + throw err + + t.equal( + result, + 'Set\npassword', + 'should output set password success msg' + ) + t.end() + }) + }) + + t.end() +}) + +t.test('enable-2fa', t => { + t.test('invalid args', t => { + profile(['enable-2fa', 'foo', 'bar'], err => { + t.match( + err, + /npm profile enable-2fa \[auth-and-writes|auth-only\]/, + 'should throw usage error' + ) + t.end() + }) + }) + + t.test('invalid two factor auth mode', t => { + profile(['enable-2fa', 'foo'], err => { + t.match( + err, + /Invalid two-factor authentication mode "foo"/, + 'should throw invalid auth mode error' + ) + t.end() + }) + }) + + t.test('no support for --json output', t => { + npm.flatOptions.json = true + + profile(['enable-2fa', 'auth-only'], err => { + t.match( + err.message, + 'Enabling two-factor authentication is an interactive ' + + 'operation and JSON output mode is not available', + 'should throw no support msg' + ) + t.end() + }) + }) + + t.test('no support for --parseable output', t => { + npm.flatOptions.parseable = true + + profile(['enable-2fa', 'auth-only'], err => { + t.match( + err.message, + 'Enabling two-factor authentication is an interactive ' + + 'operation and parseable output mode is not available', + 'should throw no support msg' + ) + t.end() + }) + }) + + t.test('no bearer tokens returned by registry', t => { + t.plan(3) + + // mock legacy basic auth style + npm.config.getCredentialsByURI = reg => { + t.equal(reg, flatOptions.registry, 'should use expected registry') + return { auth: Buffer.from('foo:bar').toString('base64') } + } + + const npmProfile = { + async createToken (pass) { + t.match(pass, 'bar', 'should use password for basic auth') + return {} + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + }) + + profile(['enable-2fa', 'auth-only'], err => { + t.match( + err.message, + 'Your registry https://registry.npmjs.org/ does ' + + 'not seem to support bearer tokens. Bearer tokens ' + + 'are required for two-factor authentication', + 'should throw no support msg' + ) + }) + }) + + t.test('from basic username/password auth', t => { + // mock legacy basic auth style with user/pass + npm.config.getCredentialsByURI = () => { + return { username: 'foo', password: 'bar' } + } + + const npmProfile = { + async createToken (pass) { + return {} + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + }) + + profile(['enable-2fa', 'auth-only'], err => { + t.match( + err.message, + 'Your registry https://registry.npmjs.org/ does ' + + 'not seem to support bearer tokens. Bearer tokens ' + + 'are required for two-factor authentication', + 'should throw no support msg' + ) + t.end() + }) + }) + + t.test('no auth found', t => { + npm.config.getCredentialsByURI = () => ({}) + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + }) + + profile(['enable-2fa', 'auth-only'], err => { + t.match( + err.message, + 'You need to be logged in to registry ' + + 'https://registry.npmjs.org/ in order to enable 2fa' + ) + t.end() + }) + }) + + t.test('from basic auth, asks for otp', t => { + t.plan(10) + + // mock legacy basic auth style + npm.config = { + getCredentialsByURI (reg) { + t.equal(reg, flatOptions.registry, 'should use expected registry') + return { auth: Buffer.from('foo:bar').toString('base64') } + }, + setCredentialsByURI (registry, { token }) { + t.equal(registry, flatOptions.registry, 'should set expected registry') + t.equal(token, 'token', 'should set expected token') + }, + save (type) { + t.equal(type, 'user', 'should save to user config') + }, + } + + const npmProfile = { + async createToken (pass) { + t.match(pass, 'bar', 'should use password for basic auth') + return { token: 'token' } + }, + async get () { + return userProfile + }, + async set (newProfile, conf) { + t.match( + newProfile, + { + tfa: { + mode: 'auth-only', + }, + }, + 'should set tfa mode' + ) + t.match( + conf, + { + ...npm.flatOptions, + otp: '123456', + }, + 'should forward flatOptions config' + ) + return { + ...userProfile, + tfa: null, + } + }, + } + + const readUserInfo = { + async password () { + t.ok('should interactively ask for password confirmation') + return 'password1234' + }, + async otp (label) { + t.equal( + label, + 'Enter one-time password from your authenticator app: ', + 'should ask for otp confirmation' + ) + return '123456' + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + '../../lib/utils/read-user-info.js': readUserInfo, + }) + + profile(['enable-2fa', 'auth-only'], err => { + if (err) + throw err + + t.equal( + result, + 'Two factor authentication mode changed to: auth-only', + 'should output success msg' + ) + }) + }) + + t.test('from token and set otp, retries on pending and verifies with qrcode', t => { + t.plan(4) + + npm.flatOptions.otp = '1234' + + npm.config = { + getCredentialsByURI () { + return { token: 'token' } + }, + } + + let setCount = 0 + const npmProfile = { + async get () { + return { + ...userProfile, + tfa: { + pending: true, + }, + } + }, + async set (newProfile, conf) { + setCount++ + + // when profile response shows that 2fa is pending the + // first time calling npm-profile.set should reset 2fa + if (setCount === 1) { + t.match( + newProfile, + { + tfa: { + password: 'password1234', + mode: 'disable', + }, + }, + 'should reset 2fa' + ) + } else if (setCount === 2) { + t.match( + newProfile, + { + tfa: { + mode: 'auth-only', + }, + }, + 'should set tfa mode approprietly in follow-up call' + ) + } else if (setCount === 3) { + t.match( + newProfile, + { + tfa: ['123456'], + }, + 'should set tfa as otp code?' + ) + return { + ...userProfile, + tfa: [ + '123456', + '789101', + ], + } + } + + return { + ...userProfile, + tfa: 'otpauth://foo?secret=1234', + } + }, + } + + const readUserInfo = { + async password () { + return 'password1234' + }, + async otp (label) { + return '123456' + }, + } + + const qrcode = { + // eslint-disable-next-line standard/no-callback-literal + generate: (url, cb) => cb('qrcode'), + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + 'qrcode-terminal': qrcode, + '../../lib/utils/read-user-info.js': readUserInfo, + }) + + profile(['enable-2fa', 'auth-only'], err => { + if (err) + throw err + + t.matchSnapshot( + result, + 'should output 2fa enablement success msgs' + ) + }) + }) + + t.test('from token and set otp, retrieves invalid otp', t => { + npm.flatOptions.otp = '1234' + + npm.config = { + getCredentialsByURI () { + return { token: 'token' } + }, + } + + const npmProfile = { + async get () { + return { + ...userProfile, + tfa: { + pending: true, + }, + } + }, + async set (newProfile, conf) { + return { + ...userProfile, + tfa: 'http://foo?secret=1234', + } + }, + } + + const readUserInfo = { + async password () { + return 'password1234' + }, + async otp (label) { + return '123456' + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + '../../lib/utils/read-user-info.js': readUserInfo, + }) + + profile(['enable-2fa', 'auth-only'], err => { + t.match( + err, + /Unknown error enabling two-factor authentication./, + 'should throw invalid 2fa auth url error' + ) + t.end() + }) + }) + + t.test('from token auth provides --otp config arg', t => { + npm.flatOptions.otp = '123456' + + npm.config = { + getCredentialsByURI (reg) { + return { token: 'token' } + }, + } + + const npmProfile = { + async get () { + return userProfile + }, + async set (newProfile, conf) { + return { + ...userProfile, + tfa: null, + } + }, + } + + const readUserInfo = { + async password () { + return 'password1234' + }, + async otp () { + throw new Error('should not ask for otp') + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + '../../lib/utils/read-user-info.js': readUserInfo, + }) + + profile(['enable-2fa', 'auth-and-writes'], err => { + if (err) + throw err + + t.equal( + result, + 'Two factor authentication mode changed to: auth-and-writes', + 'should output success msg' + ) + t.end() + }) + }) + + t.test('missing tfa from user profile', t => { + npm.config = { + getCredentialsByURI (reg) { + return { token: 'token' } + }, + } + + const npmProfile = { + async get () { + return { + ...userProfile, + tfa: undefined, + } + }, + async set (newProfile, conf) { + return { + ...userProfile, + tfa: null, + } + }, + } + + const readUserInfo = { + async password () { + return 'password1234' + }, + async otp () { + return '123456' + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + '../../lib/utils/read-user-info.js': readUserInfo, + }) + + profile(['enable-2fa', 'auth-only'], err => { + if (err) + throw err + + t.equal( + result, + 'Two factor authentication mode changed to: auth-only', + 'should output success msg' + ) + t.end() + }) + }) + + t.test('defaults to auth-and-writes permission if no mode specified', t => { + npm.config = { + getCredentialsByURI (reg) { + return { token: 'token' } + }, + } + + const npmProfile = { + async get () { + return { + ...userProfile, + tfa: undefined, + } + }, + async set (newProfile, conf) { + return { + ...userProfile, + tfa: null, + } + }, + } + + const readUserInfo = { + async password () { + return 'password1234' + }, + async otp () { + return '123456' + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + '../../lib/utils/read-user-info.js': readUserInfo, + }) + + profile(['enable-2fa'], err => { + if (err) + throw err + + t.equal( + result, + 'Two factor authentication mode changed to: auth-and-writes', + 'should enable 2fa with auth-and-writes permission' + ) + t.end() + }) + }) + + t.end() +}) + +t.test('disable-2fa', t => { + t.test('no tfa enabled', t => { + const npmProfile = { + async get () { + return { + ...userProfile, + tfa: null, + } + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + }) + + profile(['disable-2fa'], err => { + if (err) + throw err + + t.equal( + result, + 'Two factor authentication not enabled.', + 'should output already disalbed msg' + ) + t.end() + }) + }) + + t.test('requests otp', t => { + const npmProfile = t => ({ + async get () { + return userProfile + }, + async set (newProfile, conf) { + t.deepEqual( + newProfile, + { + tfa: { + password: 'password1234', + mode: 'disable', + }, + }, + 'should send the new info for setting in profile' + ) + t.match( + conf, + { + ...npm.flatOptions, + otp: '1234', + }, + 'should forward flatOptions config' + ) + }, + }) + + const readUserInfo = t => ({ + async password () { + t.ok('should interactively ask for password confirmation') + return 'password1234' + }, + async otp (label) { + t.equal( + label, + 'Enter one-time password from your authenticator app: ', + 'should ask for otp confirmation' + ) + return '1234' + }, + }) + + t.test('default output', t => { + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile(t), + '../../lib/utils/read-user-info.js': readUserInfo(t), + }) + + profile(['disable-2fa'], err => { + if (err) + throw err + + t.equal( + result, + 'Two factor authentication disabled.', + 'should output already disabled msg' + ) + t.end() + }) + }) + + t.test('--json', t => { + npm.flatOptions.json = true + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile(t), + '../../lib/utils/read-user-info.js': readUserInfo(t), + }) + + profile(['disable-2fa'], err => { + if (err) + throw err + + t.deepEqual( + JSON.parse(result), + { tfa: false }, + 'should output json already disabled msg' + ) + t.end() + }) + }) + + t.test('--parseable', t => { + npm.flatOptions.parseable = true + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile(t), + '../../lib/utils/read-user-info.js': readUserInfo(t), + }) + + profile(['disable-2fa'], err => { + if (err) + throw err + + t.equal( + result, + 'tfa\tfalse', + 'should output parseable already disabled msg' + ) + t.end() + }) + }) + + t.end() + }) + + t.test('--otp config already set', t => { + t.plan(3) + + npm.flatOptions.otp = '123456' + + const npmProfile = { + async get () { + return userProfile + }, + async set (newProfile, conf) { + t.deepEqual( + newProfile, + { + tfa: { + password: 'password1234', + mode: 'disable', + }, + }, + 'should send the new info for setting in profile' + ) + t.match( + conf, + { + ...npm.flatOptions, + otp: '123456', + }, + 'should forward flatOptions config' + ) + }, + } + + const readUserInfo = { + async password () { + return 'password1234' + }, + async otp (label) { + throw new Error('should not ask for otp') + }, + } + + const profile = requireInject('../../lib/profile.js', { + ...mocks, + 'npm-profile': npmProfile, + '../../lib/utils/read-user-info.js': readUserInfo, + }) + + profile(['disable-2fa'], err => { + if (err) + throw err + + t.equal( + result, + 'Two factor authentication disabled.', + 'should output already disalbed msg' + ) + }) + }) + + t.end() +}) + +t.test('unknown subcommand', t => { + profile(['asfd'], err => { + t.match( + err, + /Unknown profile command: asfd/, + 'should throw unknown cmd error' + ) + t.end() + }) +}) + +t.test('completion', t => { + const { completion } = profile + + const testComp = ({ t, argv, expect, title }) => { + completion({ conf: { argv: { remain: argv } } }, (err, res) => { + if (err) + throw err + + t.strictSame(res, expect, title) + }) + } + + t.test('npm profile autocomplete', t => { + testComp({ + t, + argv: ['npm', 'profile'], + expect: ['enable-2fa', 'disable-2fa', 'get', 'set'], + title: 'should auto complete with subcommands', + }) + + t.end() + }) + + t.test('npm profile enable autocomplete', t => { + testComp({ + t, + argv: ['npm', 'profile', 'enable-2fa'], + expect: ['auth-and-writes', 'auth-only'], + title: 'should auto complete with auth types', + }) + + t.end() + }) + + t.test('npm profile <subcmd> no autocomplete', t => { + const noAutocompleteCmds = ['disable-2fa', 'disable-tfa', 'get', 'set'] + for (const subcmd of noAutocompleteCmds) { + testComp({ + t, + argv: ['npm', 'profile', subcmd], + expect: [], + title: `${subcmd} should have no autocomplete`, + }) + } + + t.end() + }) + + t.test('npm profile unknown subcommand autocomplete', t => { + completion({ + conf: { + argv: { + remain: ['npm', 'profile', 'asdf'], + }, + }, + }, (err, res) => { + t.match( + err, + /asdf not recognized/, + 'should throw unknown cmd error' + ) + + t.end() + }) + }) + + t.end() +}) diff --git a/deps/npm/test/lib/publish.js b/deps/npm/test/lib/publish.js index 14e2179816..c1f353f6fd 100644 --- a/deps/npm/test/lib/publish.js +++ b/deps/npm/test/lib/publish.js @@ -3,7 +3,14 @@ const requireInject = require('require-inject') // mock config const {defaults} = require('../../lib/utils/config.js') -const config = { list: [defaults] } +const credentials = { + token: 'asdfasdf', + alwaysAuth: false, +} +const config = { + list: [defaults], + getCredentialsByURI: () => credentials, +} const fs = require('fs') t.test('should publish with libnpmpublish, respecting publishConfig', (t) => { @@ -23,6 +30,7 @@ t.test('should publish with libnpmpublish, respecting publishConfig', (t) => { flatOptions: { json: true, defaultTag: 'latest', + registry: 'https://registry.npmjs.org', }, config, }, @@ -55,7 +63,7 @@ t.test('should publish with libnpmpublish, respecting publishConfig', (t) => { }, }) - publish([testDir], (er) => { + return publish([testDir], (er) => { if (er) throw er t.pass('got to callback') @@ -77,6 +85,7 @@ t.test('re-loads publishConfig if added during script process', (t) => { flatOptions: { json: true, defaultTag: 'latest', + registry: 'https://registry.npmjs.org/', }, config, }, @@ -108,7 +117,7 @@ t.test('re-loads publishConfig if added during script process', (t) => { }, }) - publish([testDir], (er) => { + return publish([testDir], (er) => { if (er) throw er t.pass('got to callback') @@ -131,6 +140,7 @@ t.test('should not log if silent', (t) => { json: false, defaultTag: 'latest', dryRun: true, + registry: 'https://registry.npmjs.org/', }, config, }, @@ -159,7 +169,7 @@ t.test('should not log if silent', (t) => { }, }) - publish([testDir], (er) => { + return publish([testDir], (er) => { if (er) throw er t.pass('got to callback') @@ -181,6 +191,7 @@ t.test('should log tarball contents', (t) => { json: false, defaultTag: 'latest', dryRun: true, + registry: 'https://registry.npmjs.org/', }, config, }, @@ -206,7 +217,7 @@ t.test('should log tarball contents', (t) => { }, }) - publish([testDir], (er) => { + return publish([testDir], (er) => { if (er) throw er t.pass('got to callback') @@ -220,12 +231,13 @@ t.test('shows usage with wrong set of arguments', (t) => { flatOptions: { json: false, defaultTag: '0.0.13', + registry: 'https://registry.npmjs.org/', }, config, }, }) - publish(['a', 'b', 'c'], (er) => t.matchSnapshot(er, 'should print usage')) + return publish(['a', 'b', 'c'], (er) => t.matchSnapshot(er, 'should print usage')) }) t.test('throws when invalid tag', (t) => { @@ -235,12 +247,13 @@ t.test('throws when invalid tag', (t) => { flatOptions: { json: false, defaultTag: '0.0.13', + registry: 'https://registry.npmjs.org/', }, config, }, }) - publish([], (err) => { + return publish([], (err) => { t.match(err, { message: /Tag name must not be a valid SemVer range: /, }, 'throws when tag name is a valid SemVer range') @@ -274,6 +287,7 @@ t.test('can publish a tarball', t => { flatOptions: { json: true, defaultTag: 'latest', + registry: 'https://registry.npmjs.org/', }, config, }, @@ -298,9 +312,56 @@ t.test('can publish a tarball', t => { }, }) - publish([`${testDir}/package.tgz`], (er) => { + return publish([`${testDir}/package.tgz`], (er) => { if (er) throw er t.pass('got to callback') }) }) + +t.test('throw if no registry', async t => { + t.plan(1) + const publish = requireInject('../../lib/publish.js', { + '../../lib/npm.js': { + flatOptions: { + json: false, + defaultTag: '0.0.13', + registry: null, + }, + config, + }, + }) + + return publish([], (err) => { + t.match(err, { + message: 'No registry specified.', + code: 'ENOREGISTRY', + }, 'throws when registry unset') + }) +}) + +t.test('throw if not logged in', async t => { + t.plan(1) + const publish = requireInject('../../lib/publish.js', { + '../../lib/npm.js': { + flatOptions: { + json: false, + defaultTag: '0.0.13', + registry: 'https://registry.npmjs.org/', + }, + config: { + ...config, + getCredentialsByURI: () => ({ + email: 'me@example.com', + }), + }, + }, + }) + + return publish([], (err) => { + t.match(err, { + message: 'This command requires you to be logged in.', + code: 'ENEEDAUTH', + }, 'throws when not logged in') + }) +}) diff --git a/deps/npm/test/lib/utils/error-handler.js b/deps/npm/test/lib/utils/error-handler.js index 2dc116a4d3..0b896fee4f 100644 --- a/deps/npm/test/lib/utils/error-handler.js +++ b/deps/npm/test/lib/utils/error-handler.js @@ -78,10 +78,6 @@ const npmlog = { }, } -const metrics = { - stop: () => null, -} - // overrides OS type/release for cross platform snapshots const os = require('os') os.type = () => 'Foo' @@ -124,7 +120,6 @@ const mocks = { summary: [['ERR', err.message]], detail: [['ERR', err.message]], }), - '../../../lib/utils/metrics.js': metrics, '../../../lib/utils/cache-file.js': cacheFile, } diff --git a/deps/npm/test/lib/utils/flat-options.js b/deps/npm/test/lib/utils/flat-options.js index ee7620fa78..6f580fabc4 100644 --- a/deps/npm/test/lib/utils/flat-options.js +++ b/deps/npm/test/lib/utils/flat-options.js @@ -34,8 +34,6 @@ class MockConfig { cache: 'cache', 'node-version': '1.2.3', global: 'global', - 'metrics-registry': 'metrics-registry', - 'send-metrics': 'send-metrics', registry: 'registry', access: 'access', 'always-auth': 'always-auth', @@ -299,15 +297,12 @@ t.test('various default values and falsey fallbacks', t => { const npm = new Mocknpm({ 'script-shell': false, registry: 'http://example.com', - 'metrics-registry': null, searchlimit: 0, 'save-exact': false, 'save-prefix': '>=', }) const opts = flatOptions(npm) t.equal(opts.scriptShell, undefined, 'scriptShell is undefined if falsey') - t.equal(opts.metricsRegistry, 'http://example.com', - 'metricsRegistry defaults to registry') t.equal(opts.search.limit, 20, 'searchLimit defaults to 20') t.equal(opts.savePrefix, '>=', 'save-prefix respected if no save-exact') t.equal(opts.scope, '', 'scope defaults to empty string') diff --git a/deps/npm/test/lib/utils/reify-output.js b/deps/npm/test/lib/utils/reify-output.js index f7fd96ee87..f88f072e1e 100644 --- a/deps/npm/test/lib/utils/reify-output.js +++ b/deps/npm/test/lib/utils/reify-output.js @@ -245,7 +245,10 @@ t.test('packages changed message', t => { settings.json = json npmock.command = command const mock = { - actualTree: { inventory: { size: audited, has: () => true }, children: [] }, + actualTree: { + inventory: { size: audited, has: () => true }, + children: [], + }, auditReport: audited ? { toJSON: () => mock.auditReport, vulnerabilities: {}, diff --git a/deps/npm/test/lib/utils/replace-info.js b/deps/npm/test/lib/utils/replace-info.js index f5e1246f1c..ea9f06520d 100644 --- a/deps/npm/test/lib/utils/replace-info.js +++ b/deps/npm/test/lib/utils/replace-info.js @@ -46,7 +46,7 @@ t.equal( t.equal( replaceInfo('Something https://user:pass@registry.npmjs.org/ foo bar'), 'Something https://user:***@registry.npmjs.org/ foo bar', - 'should replace single item withing a phrase' + 'should replace single item within a phrase' ) t.deepEqual( |