Skip to content

Commit 974e7d8

Browse files
committed
chore: add failing publish global self smoke test
1 parent 12f678c commit 974e7d8

File tree

6 files changed

+126
-36
lines changed

6 files changed

+126
-36
lines changed

DEPENDENCIES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ graph LR;
690690
npmcli-mock-globals-->npmcli-eslint-config["@npmcli/eslint-config"];
691691
npmcli-mock-globals-->npmcli-template-oss["@npmcli/template-oss"];
692692
npmcli-mock-globals-->tap;
693+
npmcli-mock-registry-->json-stringify-safe;
693694
npmcli-mock-registry-->nock;
694695
npmcli-mock-registry-->npm-package-arg;
695696
npmcli-mock-registry-->npmcli-arborist["@npmcli/arborist"];

mock-registry/lib/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const pacote = require('pacote')
22
const Arborist = require('@npmcli/arborist')
33
const npa = require('npm-package-arg')
44
const Nock = require('nock')
5+
const stringify = require('json-stringify-safe')
56

67
class MockRegistry {
78
#tap
@@ -39,7 +40,7 @@ class MockRegistry {
3940
// mocked with a 404, 500, etc.
4041
// XXX: this is opt-in currently because it breaks some existing CLI
4142
// tests. We should work towards making this the default for all tests.
42-
t.fail(`Unmatched request: ${JSON.stringify(req, null, 2)}`)
43+
t.fail(`Unmatched request: ${stringify(req, null, 2)}`)
4344
}
4445
}
4546

@@ -337,9 +338,9 @@ class MockRegistry {
337338
}
338339
nock = nock.reply(200, manifest)
339340
if (tarballs) {
340-
for (const version in tarballs) {
341+
for (const [version, tarball] of Object.entries(tarballs)) {
341342
const m = manifest.versions[version]
342-
nock = await this.tarball({ manifest: m, tarball: tarballs[version] })
343+
nock = await this.tarball({ manifest: m, tarball })
343344
}
344345
}
345346
this.nock = nock

mock-registry/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@npmcli/arborist": "^6.1.1",
4848
"@npmcli/eslint-config": "^4.0.1",
4949
"@npmcli/template-oss": "4.14.1",
50+
"json-stringify-safe": "^5.0.1",
5051
"nock": "^13.3.0",
5152
"npm-package-arg": "^10.1.0",
5253
"pacote": "^15.0.8",

package-lock.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@
222222
"@npmcli/arborist": "^6.1.1",
223223
"@npmcli/eslint-config": "^4.0.1",
224224
"@npmcli/template-oss": "4.14.1",
225+
"json-stringify-safe": "^5.0.1",
225226
"nock": "^13.3.0",
226227
"npm-package-arg": "^10.1.0",
227228
"pacote": "^15.0.8",

smoke-tests/test/fixtures/setup.js

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const httpProxy = require('http-proxy')
99

1010
const { SMOKE_PUBLISH_NPM, SMOKE_PUBLISH_TARBALL, CI, PATH, Path, TAP_CHILD_ID = '0' } = process.env
1111
const PROXY_PORT = 12345 + (+TAP_CHILD_ID)
12-
const HTTP_PROXY = `http://localhost:${PROXY_PORT}`
12+
const HTTP_PROXY = `http://localhost:${PROXY_PORT}/`
1313

1414
const NODE_PATH = process.execPath
1515
const CLI_ROOT = resolve(process.cwd(), '..')
@@ -64,12 +64,13 @@ const getCleanPaths = async () => {
6464
})
6565
}
6666

67-
const createRegistry = async (t, { debug } = {}) => {
67+
const createRegistry = async (t, { debug, ...opts } = {}) => {
6868
const registry = new MockRegistry({
6969
tap: t,
7070
registry: 'http://smoke-test-registry.club/',
7171
debug,
7272
strict: true,
73+
...opts,
7374
})
7475

7576
const proxy = httpProxy.createProxyServer({})
@@ -81,7 +82,7 @@ const createRegistry = async (t, { debug } = {}) => {
8182
return registry
8283
}
8384

84-
module.exports = async (t, { testdir = {}, debug } = {}) => {
85+
module.exports = async (t, { testdir = {}, debug, registry: _registry = {} } = {}) => {
8586
const debugLog = debug || CI ? (...a) => console.error(...a) : () => {}
8687
const cleanPaths = await getCleanPaths()
8788

@@ -105,7 +106,7 @@ module.exports = async (t, { testdir = {}, debug } = {}) => {
105106
globalNodeModules: join(root, 'global', GLOBAL_NODE_MODULES),
106107
}
107108

108-
const registry = await createRegistry(t, { debug })
109+
const registry = await createRegistry(t, { ..._registry, debug })
109110

110111
// update notifier should never be written
111112
t.afterEach((t) => {
@@ -168,7 +169,13 @@ module.exports = async (t, { testdir = {}, debug } = {}) => {
168169
return stdout
169170
}
170171

171-
const baseNpm = async ({ cwd, cmd, argv = [], proxy = true, ...opts } = {}, ...args) => {
172+
const baseNpm = async (baseOpts, ...args) => {
173+
const hasMoreOpts = args[args.length - 1] && typeof args[args.length - 1] === 'object'
174+
const { cwd, cmd, argv = [], proxy = true, ...opts } = {
175+
...baseOpts,
176+
...hasMoreOpts ? args.pop() : {},
177+
}
178+
172179
const isGlobal = args.some(a => ['-g', '--global', '--global=true'].includes(a))
173180

174181
const defaultFlags = [
@@ -218,6 +225,10 @@ module.exports = async (t, { testdir = {}, debug } = {}) => {
218225
argv: [NPM_PATH],
219226
}, ...args)
220227

228+
const npmLocalError = async () => {
229+
throw new Error('npmLocal cannot be called during smoke-publish')
230+
}
231+
221232
// helpers for reading/writing files and their source
222233
const readFile = async (f) => {
223234
const file = await fs.readFile(join(paths.project, f), 'utf-8')
@@ -226,15 +237,22 @@ module.exports = async (t, { testdir = {}, debug } = {}) => {
226237

227238
return {
228239
npmPath,
229-
npmLocal: SMOKE_PUBLISH_NPM ? async () => {
230-
throw new Error('npmLocal cannot be called during smoke-publish')
231-
} : npmLocal,
240+
npmLocal: SMOKE_PUBLISH_NPM ? npmLocalError : npmLocal,
232241
npm: SMOKE_PUBLISH_NPM ? npmPath : npm,
233242
spawn: baseSpawn,
234243
readFile,
235244
getPath,
236245
paths,
237246
registry,
247+
npmLocalTarball: async () => {
248+
if (SMOKE_PUBLISH_TARBALL) {
249+
return SMOKE_PUBLISH_TARBALL
250+
}
251+
if (SMOKE_PUBLISH_NPM) {
252+
return await npmLocalError()
253+
}
254+
return await npmLocal('pack', `--pack-destination=${root}`).then(r => join(root, r))
255+
},
238256
}
239257
}
240258

@@ -244,3 +262,4 @@ module.exports.CLI_ROOT = CLI_ROOT
244262
module.exports.WINDOWS = WINDOWS
245263
module.exports.SMOKE_PUBLISH = !!SMOKE_PUBLISH_NPM
246264
module.exports.SMOKE_PUBLISH_TARBALL = SMOKE_PUBLISH_TARBALL
265+
module.exports.HTTP_PROXY = HTTP_PROXY

smoke-tests/test/npm-replace-global.js

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,48 @@ const which = async (cmd, opts) => {
1010
return path ? join(dirname(path), basename(path, extname(path))) : null
1111
}
1212

13-
t.test('npm replace global', async t => {
13+
const setupNpmGlobal = async (t, opts) => {
14+
const mock = await setup(t, opts)
15+
16+
return {
17+
...mock,
18+
getPaths: async () => {
19+
const binContents = await fs.readdir(mock.paths.globalBin)
20+
.then(r => r.filter(p => p !== '.npmrc' && p !== 'node_modules'))
21+
.catch(() => null)
22+
23+
const nodeModulesContents = await fs.readdir(join(mock.paths.globalNodeModules, 'npm'))
24+
.catch(() => null)
25+
26+
return {
27+
npmRoot: await mock.npmPath('help').then(setup.getNpmRoot),
28+
pathNpm: await which('npm', { path: mock.getPath(), nothrow: true }),
29+
globalNpm: await which('npm', { nothrow: true }),
30+
pathNpx: await which('npx', { path: mock.getPath(), nothrow: true }),
31+
globalNpx: await which('npx', { nothrow: true }),
32+
binContents,
33+
nodeModulesContents,
34+
}
35+
},
36+
}
37+
}
38+
39+
t.test('pack and replace global self', async t => {
1440
const {
1541
npm,
16-
npmLocal,
42+
npmLocalTarball,
1743
npmPath,
18-
getPath,
19-
paths: { root, globalBin, globalNodeModules },
20-
} = await setup(t, {
44+
getPaths,
45+
paths: { globalBin, globalNodeModules },
46+
} = await setupNpmGlobal(t, {
2147
testdir: {
2248
project: {
2349
'package.json': { name: 'npm', version: '999.999.999' },
2450
},
2551
},
2652
})
2753

28-
const getPaths = async () => {
29-
const binContents = await fs.readdir(globalBin).then(results => results
30-
.filter(p => p !== '.npmrc' && p !== 'node_modules')
31-
.map(p => basename(p, extname(p)))
32-
.reduce((set, p) => set.add(p), new Set()))
33-
34-
return {
35-
npmRoot: await npmPath('help').then(setup.getNpmRoot),
36-
pathNpm: await which('npm', { path: getPath(), nothrow: true }),
37-
globalNpm: await which('npm', { nothrow: true }),
38-
pathNpx: await which('npx', { path: getPath(), nothrow: true }),
39-
globalNpx: await which('npx', { nothrow: true }),
40-
binContents: [...binContents],
41-
nodeModulesContents: await fs.readdir(join(globalNodeModules, 'npm')),
42-
}
43-
}
44-
45-
const tarball = setup.SMOKE_PUBLISH_TARBALL ??
46-
await npmLocal('pack', `--pack-destination=${root}`).then(r => join(root, r))
54+
const tarball = await npmLocalTarball()
4755

4856
await npm('install', tarball, '--global')
4957

@@ -64,7 +72,7 @@ t.test('npm replace global', async t => {
6472
t.equal(prePaths.pathNpx, join(globalBin, 'npx'), 'npx bin is in the testdir')
6573
t.not(prePaths.pathNpm, prePaths.globalNpm, 'npm bin is not the same as the global one')
6674
t.not(prePaths.pathNpx, prePaths.globalNpx, 'npm bin is not the same as the global one')
67-
t.match(prePaths.binContents, ['npm', 'npx'], 'bin has npm and npx')
75+
t.strictSame(prePaths.binContents, ['npm', 'npx'], 'bin has npm and npx')
6876
t.ok(prePaths.nodeModulesContents.length > 1, 'node modules has npm contents')
6977
t.ok(prePaths.nodeModulesContents.includes('node_modules'), 'npm has its node_modules')
7078

@@ -80,3 +88,62 @@ t.test('npm replace global', async t => {
8088
t.strictSame(postPaths.binContents, [], 'bin is empty')
8189
t.strictSame(postPaths.nodeModulesContents, ['package.json'], 'contents is only package.json')
8290
})
91+
92+
t.test('publish and replace global self', async t => {
93+
const {
94+
npm,
95+
npmPath,
96+
registry,
97+
npmLocal,
98+
npmLocalTarball,
99+
getPaths,
100+
paths: { globalBin, globalNodeModules },
101+
} = await setupNpmGlobal(t, {
102+
testdir: {
103+
home: {
104+
'.npmrc': `${setup.HTTP_PROXY.slice(5)}:_authToken = test-token`,
105+
},
106+
},
107+
})
108+
109+
const tarball = await npmLocalTarball()
110+
111+
let publishedPackument = null
112+
const pkg = require('../../package.json')
113+
const { name, version } = pkg
114+
115+
registry.nock.put('/npm', body => {
116+
if (body._id === 'npm' && body.versions[version]) {
117+
publishedPackument = body.versions[version]
118+
return true
119+
}
120+
return false
121+
}).reply(201, {})
122+
await npmLocal('publish', { proxy: true })
123+
124+
await registry.package({
125+
manifest: registry.manifest({ name, packuments: [publishedPackument] }),
126+
tarballs: { [version]: tarball },
127+
times: 2,
128+
})
129+
130+
await npm('install', 'npm', '--global', '--prefer-online')
131+
132+
const paths = await getPaths()
133+
t.equal(paths.npmRoot, join(globalNodeModules, 'npm'), 'npm root is in the testdir')
134+
t.equal(paths.pathNpm, join(globalBin, 'npm'), 'npm bin is in the testdir')
135+
t.equal(paths.pathNpx, join(globalBin, 'npx'), 'npx bin is in the testdir')
136+
t.strictSame(paths.binContents, ['npm', 'npx'], 'bin has npm and npx')
137+
t.ok(paths.nodeModulesContents.length > 1, 'node modules has npm contents')
138+
t.ok(paths.nodeModulesContents.includes('node_modules'), 'npm has its node_modules')
139+
140+
await registry.package({
141+
manifest: registry.manifest({ name, packuments: [publishedPackument] }),
142+
tarballs: { [version]: tarball },
143+
times: 2,
144+
})
145+
146+
await npmPath('install', 'npm', '--global', '--prefer-online')
147+
148+
t.strictSame(await getPaths(), paths)
149+
})

0 commit comments

Comments
 (0)