Skip to content

Commit da6cf30

Browse files
lukekarrysfritzy
authored andcommitted
chore(libnpmexec): refactor tests to use mock registry
1 parent 968e124 commit da6cf30

File tree

15 files changed

+1195
-1757
lines changed

15 files changed

+1195
-1757
lines changed

DEPENDENCIES.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ graph LR;
3333
libnpmexec-->npm-package-arg;
3434
libnpmexec-->npmcli-arborist["@npmcli/arborist"];
3535
libnpmexec-->npmcli-eslint-config["@npmcli/eslint-config"];
36+
libnpmexec-->npmcli-mock-registry["@npmcli/mock-registry"];
3637
libnpmexec-->npmcli-run-script["@npmcli/run-script"];
3738
libnpmexec-->npmcli-template-oss["@npmcli/template-oss"];
3839
libnpmexec-->npmlog;
@@ -352,11 +353,14 @@ graph LR;
352353
libnpmexec-->bin-links;
353354
libnpmexec-->chalk;
354355
libnpmexec-->ci-info;
356+
libnpmexec-->just-extend;
357+
libnpmexec-->just-safe-set;
355358
libnpmexec-->minify-registry-metadata;
356359
libnpmexec-->mkdirp;
357360
libnpmexec-->npm-package-arg;
358361
libnpmexec-->npmcli-arborist["@npmcli/arborist"];
359362
libnpmexec-->npmcli-eslint-config["@npmcli/eslint-config"];
363+
libnpmexec-->npmcli-mock-registry["@npmcli/mock-registry"];
360364
libnpmexec-->npmcli-run-script["@npmcli/run-script"];
361365
libnpmexec-->npmcli-template-oss["@npmcli/template-oss"];
362366
libnpmexec-->npmlog;
@@ -772,8 +776,8 @@ Each group depends on packages lower down the chain, nothing depends on
772776
packages higher up the chain.
773777

774778
- npm
775-
- @npmcli/smoke-tests, libnpmpublish
776-
- @npmcli/mock-registry, libnpmdiff, libnpmexec, libnpmfund, libnpmpack
779+
- @npmcli/smoke-tests, libnpmexec, libnpmpublish
780+
- @npmcli/mock-registry, libnpmdiff, libnpmfund, libnpmpack
777781
- @npmcli/arborist
778782
- @npmcli/metavuln-calculator
779783
- pacote, libnpmaccess, libnpmhook, libnpmorg, libnpmsearch, libnpmteam, npm-profile

package-lock.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15118,8 +15118,11 @@
1511815118
},
1511915119
"devDependencies": {
1512015120
"@npmcli/eslint-config": "^4.0.0",
15121+
"@npmcli/mock-registry": "^1.0.0",
1512115122
"@npmcli/template-oss": "4.10.0",
1512215123
"bin-links": "^4.0.1",
15124+
"just-extend": "^6.1.1",
15125+
"just-safe-set": "^4.1.1",
1512315126
"minify-registry-metadata": "^2.2.0",
1512415127
"mkdirp": "^1.0.4",
1512515128
"tap": "^16.0.1"

workspaces/libnpmexec/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@
5151
},
5252
"devDependencies": {
5353
"@npmcli/eslint-config": "^4.0.0",
54+
"@npmcli/mock-registry": "^1.0.0",
5455
"@npmcli/template-oss": "4.10.0",
5556
"bin-links": "^4.0.1",
57+
"just-extend": "^6.1.1",
58+
"just-safe-set": "^4.1.1",
5659
"minify-registry-metadata": "^2.2.0",
5760
"mkdirp": "^1.0.4",
5861
"tap": "^16.0.1"
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
2+
const fs = require('fs/promises')
3+
const { existsSync } = require('fs')
4+
const { resolve, extname, join } = require('path')
5+
const binLinks = require('bin-links')
6+
const MockRegistry = require('@npmcli/mock-registry')
7+
const justExtend = require('just-extend')
8+
const set = require('just-safe-set')
9+
10+
const merge = (...args) => justExtend(true, ...args)
11+
12+
const DEFAULT_BIN_FILE = 'bin-file.js'
13+
14+
const createPkg = ({
15+
name = '@npmcli/create-index',
16+
bin,
17+
files,
18+
versions = [],
19+
version,
20+
localVersion,
21+
}) => {
22+
if (localVersion && !versions.includes(localVersion)) {
23+
versions = [...versions, localVersion]
24+
}
25+
if (version && !versions.includes(version)) {
26+
versions = [...versions, version]
27+
}
28+
29+
const defaultBinName = name.includes('/') ? name.split('/')[1] : name
30+
31+
if (!bin) {
32+
bin = {
33+
[defaultBinName]: `./${DEFAULT_BIN_FILE}`,
34+
}
35+
}
36+
37+
const pkgsArr = versions.map((v) => ({
38+
name,
39+
version: v,
40+
bin,
41+
}))
42+
43+
const pkgs = {}
44+
const tarballs = {}
45+
const fixtures = {}
46+
47+
for (const pkg of pkgsArr) {
48+
pkgs[pkg.version] = pkg
49+
}
50+
51+
if (localVersion) {
52+
set(fixtures, ['node_modules', ...name.split('/')], {
53+
'package.json': pkgs[localVersion],
54+
...files || {
55+
[DEFAULT_BIN_FILE]: { key: name, value: `local-${localVersion}` },
56+
},
57+
})
58+
fixtures['package.json'] = {
59+
name: 'pkg',
60+
version: '9.9.9',
61+
dependencies: {
62+
[name]: `^${localVersion}`,
63+
},
64+
}
65+
}
66+
67+
for (const pkg of pkgsArr) {
68+
const fixturePath = `${pkg.name}-${pkg.version}`.replace('/', '-')
69+
set(fixtures, ['packages', fixturePath], {
70+
'package.json': pkg,
71+
...files || {
72+
[DEFAULT_BIN_FILE]: { key: pkg.name, value: `packages-${pkg.version}` },
73+
},
74+
})
75+
tarballs[pkg.version] = join('packages', fixturePath)
76+
}
77+
78+
return {
79+
pkg: pkgsArr[0],
80+
pkgs,
81+
fixtures,
82+
package: ({ registry, path, tarballs: tgz = versions, ...opts }) => registry.package({
83+
times: 2,
84+
manifest: registry.manifest({ name: pkgsArr[0].name, packuments: pkgsArr }),
85+
tarballs: tgz.reduce((acc, v) => {
86+
acc[v] = resolve(path, tarballs[v])
87+
return acc
88+
}, {}),
89+
...opts,
90+
}),
91+
}
92+
}
93+
94+
const createTestdir = (...objs) => {
95+
const testdirHelper = (obj, ancestors = []) => {
96+
for (const [key, value] of Object.entries(obj)) {
97+
if (extname(key) === '.json') {
98+
obj[key] = JSON.stringify(value, null, 2)
99+
} else if (extname(key) === '.js' || ancestors.slice(-2).join('/') === 'node_modules/.bin') {
100+
// a js or bin file is converted to a bin script that writes a file
101+
obj[key] = `#!/usr/bin/env node\nrequire('fs').writeFileSync(
102+
'output-${value.key.replace('/', '-')}',
103+
JSON.stringify({
104+
value: '${value.value}',
105+
args: process.argv.slice(2),
106+
created: '${[...ancestors, key].join('/')}',
107+
})
108+
)`
109+
} else if (value && typeof value === 'object') {
110+
obj[key] = testdirHelper(value, [...ancestors, key])
111+
} else {
112+
obj[key] = value
113+
}
114+
}
115+
return obj
116+
}
117+
118+
return testdirHelper(merge(...objs))
119+
}
120+
121+
const setup = (t, {
122+
pkg,
123+
testdir: _testdir = {},
124+
mocks,
125+
global,
126+
debug,
127+
execPath,
128+
defaults = true,
129+
} = {}) => {
130+
const registry = new MockRegistry({
131+
tap: t,
132+
registry: 'http://smoke-test-registry.club/',
133+
strict: true,
134+
debug,
135+
})
136+
137+
if (debug) {
138+
process.on('log', console.error)
139+
t.teardown(() => process.off('log', console.error))
140+
}
141+
142+
const { node_modules: testdirNm, ...testdir } = _testdir
143+
const fullTestdir = createTestdir({
144+
cache: {},
145+
npxCache: {},
146+
...testdirNm ?
147+
global ? {
148+
global: {
149+
node_modules: {
150+
'.bin': {},
151+
...testdirNm,
152+
},
153+
},
154+
} : {
155+
node_modules: {
156+
'.bin': {},
157+
...testdirNm,
158+
},
159+
}
160+
: {},
161+
}, testdir)
162+
163+
// quick way to remove undefined and null values that we merged
164+
// in to not write certain directories
165+
const path = t.testdir(JSON.parse(JSON.stringify(fullTestdir, (_, v) => {
166+
if (v === null) {
167+
return
168+
}
169+
if (typeof v === 'string') {
170+
return v.replace(/\{REGISTRY\}/g, registry.origin)
171+
}
172+
return v
173+
}))
174+
)
175+
176+
const cache = resolve(path, 'cache')
177+
const npxCache = resolve(path, 'npxCache')
178+
const nodeModules = resolve(path, global ? 'global/node_modules' : 'node_modules')
179+
180+
const defaultOpts = {
181+
call: '',
182+
color: false,
183+
localBin: '',
184+
globalBin: '',
185+
packages: [],
186+
scriptShell: undefined,
187+
yes: true,
188+
path,
189+
runPath: path,
190+
}
191+
192+
const baseOpts = {
193+
audit: false,
194+
registry: registry.origin + '/',
195+
...existsSync(cache) ? { cache } : {},
196+
...existsSync(npxCache) ? { npxCache } : {},
197+
...global ? {
198+
globalBin: resolve(path, nodeModules, '.bin'),
199+
globalPath: resolve(path, 'global'),
200+
} : {},
201+
}
202+
203+
return {
204+
path,
205+
registry,
206+
chmod: async (chmodPath) => {
207+
if (!chmodPath) {
208+
for (const p of [].concat(pkg)) {
209+
await fs.chmod(resolve(path, nodeModules, p.name, DEFAULT_BIN_FILE), 0o775)
210+
}
211+
return
212+
}
213+
return fs.chmod(resolve(path, chmodPath), 0o775)
214+
},
215+
binLinks: async (binPkg) => {
216+
if (!binPkg) {
217+
for (const p of [].concat(pkg)) {
218+
await binLinks({
219+
pkg: p,
220+
path: resolve(path, nodeModules, p.name),
221+
})
222+
}
223+
return
224+
}
225+
await binLinks({
226+
pkg: binPkg,
227+
path: resolve(path, nodeModules, binPkg.name),
228+
})
229+
},
230+
readOutput: async (outputPath, { root = path } = {}) => {
231+
if (!outputPath) {
232+
outputPath = pkg.name.replace('/', '-')
233+
}
234+
return fs.readFile(resolve(root, `output-${outputPath}`), 'utf-8').then(r => JSON.parse(r))
235+
},
236+
rmOutput: (outputPath, { root = path } = {}) => {
237+
if (!outputPath) {
238+
outputPath = pkg.name.replace('/', '-')
239+
}
240+
return fs.rm(resolve(root, `output-${outputPath}`))
241+
},
242+
exec: (opts) => t.mock(execPath || '../../lib/index.js', mocks)({
243+
...defaults ? {
244+
...defaultOpts,
245+
path,
246+
runPath: path,
247+
} : {},
248+
...baseOpts,
249+
...opts,
250+
}),
251+
}
252+
}
253+
254+
module.exports.setup = setup
255+
module.exports.createPkg = createPkg
256+
module.exports.merge = merge

0 commit comments

Comments
 (0)