1const t = require('tap')
2const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
3const MockRegistry = require('@npmcli/mock-registry')
4
5const cacache = require('cacache')
6const fs = require('fs')
7const path = require('path')
8
9const pkg = 'test-package'
10
11t.cleanSnapshot = str => {
12  return str
13    .replace(/Finished in [0-9.s]+/g, 'Finished in xxxs')
14    .replace(/Cache verified and compressed (.*)/, 'Cache verified and compressed ({PATH})')
15}
16
17t.test('cache no args', async t => {
18  const { npm } = await loadMockNpm(t)
19  await t.rejects(
20    npm.exec('cache', []),
21    { code: 'EUSAGE' },
22    'should throw usage instructions'
23  )
24})
25
26t.test('cache clean', async t => {
27  const { npm } = await loadMockNpm(t)
28  await t.rejects(
29    npm.exec('cache', ['clean']),
30    /the npm cache self-heals/,
31    'should throw warning'
32  )
33})
34
35t.test('cache clean (force)', async t => {
36  const { npm } = await loadMockNpm(t, {
37    cacheDir: { _cacache: {} },
38    config: { force: true },
39  })
40  const cache = path.join(npm.cache, '_cacache')
41  await npm.exec('cache', ['clean'])
42  t.notOk(fs.existsSync(cache), 'cache dir was removed')
43})
44
45t.test('cache add no arg', async t => {
46  const { npm } = await loadMockNpm(t)
47  await t.rejects(
48    npm.exec('cache', ['add']),
49    {
50      code: 'EUSAGE',
51      message: 'First argument to `add` is required',
52    },
53    'throws usage error'
54  )
55})
56
57t.test('cache add single pkg', async t => {
58  const { npm, joinedOutput } = await loadMockNpm(t, {
59    prefixDir: {
60      package: {
61        'package.json': JSON.stringify({
62          name: pkg,
63          version: '1.0.0',
64        }),
65      },
66    },
67  })
68  const cache = path.join(npm.cache, '_cacache')
69  const registry = new MockRegistry({
70    tap: t,
71    registry: npm.config.get('registry'),
72  })
73  const manifest = registry.manifest({ name: pkg })
74  await registry.package({ manifest, tarballs: { '1.0.0': path.join(npm.prefix, 'package') } })
75  await npm.exec('cache', ['add', pkg])
76  t.equal(joinedOutput(), '')
77  // eslint-disable-next-line max-len
78  t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz'))
79  // eslint-disable-next-line max-len
80  t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package'))
81})
82
83t.test('cache add multiple pkgs', async t => {
84  const pkg2 = 'test-package-two'
85  const { npm, joinedOutput } = await loadMockNpm(t, {
86    prefixDir: {
87      package: {
88        'package.json': JSON.stringify({
89          name: pkg,
90          version: '1.0.0',
91        }),
92      },
93    },
94  })
95  const cache = path.join(npm.cache, '_cacache')
96  const registry = new MockRegistry({
97    tap: t,
98    registry: npm.config.get('registry'),
99  })
100  const manifest = registry.manifest({ name: pkg })
101  const manifest2 = registry.manifest({ name: pkg2 })
102  await registry.package({ manifest, tarballs: { '1.0.0': path.join(npm.prefix, 'package') } })
103  await registry.package({
104    manifest: manifest2, tarballs: { '1.0.0': path.join(npm.prefix, 'package') },
105  })
106  await npm.exec('cache', ['add', pkg, pkg2])
107  t.equal(joinedOutput(), '')
108  // eslint-disable-next-line max-len
109  t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz'))
110  // eslint-disable-next-line max-len
111  t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package'))
112  // eslint-disable-next-line max-len
113  t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package-two/-/test-package-two-1.0.0.tgz'))
114  // eslint-disable-next-line max-len
115  t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package-two'))
116})
117
118t.test('cache ls', async t => {
119  const keys = [
120    'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package',
121    // eslint-disable-next-line max-len
122    'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz',
123  ]
124  const { npm, joinedOutput } = await loadMockNpm(t)
125  const cache = path.join(npm.cache, '_cacache')
126  for (const key of keys) {
127    await cacache.put(cache, key, 'test data')
128  }
129  await npm.exec('cache', ['ls'])
130  t.matchSnapshot(joinedOutput(), 'logs cache entries')
131})
132
133t.test('cache ls pkgs', async t => {
134  const keys = [
135    'make-fetch-happen:request-cache:https://registry.npmjs.org/npm',
136    'make-fetch-happen:request-cache:https://registry.npmjs.org/npm/-/npm-1.2.0.tgz',
137    'make-fetch-happen:request-cache:https://registry.npmjs.org/webpack/-/webpack-4.47.0.tgz',
138    'make-fetch-happen:request-cache:https://registry.npmjs.org/webpack/-/webpack-4.40.0.tgz',
139  ]
140  const { npm, joinedOutput } = await loadMockNpm(t)
141  const cache = path.join(npm.cache, '_cacache')
142  for (const key of keys) {
143    await cacache.put(cache, key, 'test data')
144  }
145  await cacache.put(cache,
146    'make-fetch-happen:request-cache:https://registry.npmjs.org/webpack',
147    JSON.stringify({ versions: {
148      '4.40.0': { dist: { tarball: 'https://registry.npmjs.org/webpack/-/webpack-4.40.0.tgz' } },
149      '4.47.0': { dist: { tarball: 'https://registry.npmjs.org/webpack/-/webpack-4.47.0.tgz' } },
150    } })
151  )
152  await npm.exec('cache', ['ls', 'webpack@>4.44.1', 'npm'])
153  t.matchSnapshot(joinedOutput(), 'logs cache entries for npm and webpack and one webpack tgz')
154})
155
156t.test('cache ls special', async t => {
157  const { npm, joinedOutput } = await loadMockNpm(t)
158  const cache = path.join(npm.cache, '_cacache')
159  await cacache.put(cache,
160    'make-fetch-happen:request-cache:https://registry.npmjs.org/foo',
161    JSON.stringify({ versions: { '1.2.3-beta': {} } })
162  )
163  await cacache.put(cache,
164    'make-fetch-happen:request-cache:https://registry.npmjs.org/foo/-/foo-1.2.3-beta.tgz',
165    'test-data'
166  )
167  await npm.exec('cache', ['ls', 'foo@1.2.3-beta'])
168  t.matchSnapshot(joinedOutput(), 'logs cache entries for foo')
169})
170
171t.test('cache ls nonpublic registry', async t => {
172  const { npm, joinedOutput } = await loadMockNpm(t)
173  const cache = path.join(npm.cache, '_cacache')
174  await cacache.put(cache,
175    'make-fetch-happen:request-cache:https://somerepo.github.org/extemporaneously',
176    JSON.stringify({
177      versions: { '1.0.0': { dist: { tarball: 'https://somerepo.github.org/aabbcc/' } } },
178    })
179  )
180  await cacache.put(cache,
181    'make-fetch-happen:request-cache:https://somerepo.github.org/aabbcc/',
182    'test data'
183  )
184  await npm.exec('cache', ['ls', 'extemporaneously'])
185  t.matchSnapshot(joinedOutput(), 'logs cache entry for extemporaneously and its tarball')
186})
187
188t.test('cache ls tagged', async t => {
189  const { npm } = await loadMockNpm(t)
190  await t.rejects(
191    npm.exec('cache', ['ls', 'webpack@latest']),
192    { code: 'EUSAGE' },
193    'should throw usage error'
194  )
195})
196
197t.test('cache ls scoped and scoped slash', async t => {
198  const keys = [
199    // eslint-disable-next-line max-len
200    'make-fetch-happen:request-cache:https://registry.npmjs.org/@fritzy/staydown/-/@fritzy/staydown-3.1.1.tgz',
201    'make-fetch-happen:request-cache:https://registry.npmjs.org/@fritzy%2fstaydown',
202    // eslint-disable-next-line max-len
203    'make-fetch-happen:request-cache:https://registry.npmjs.org/@gar/npm-expansion/-/@gar/npm-expansion-2.1.0.tgz',
204    'make-fetch-happen:request-cache:https://registry.npmjs.org/@gar%2fnpm-expansion',
205  ]
206  const { npm, joinedOutput } = await loadMockNpm(t)
207  const cache = path.join(npm.cache, '_cacache')
208  for (const key of keys) {
209    await cacache.put(cache, key, 'test data')
210  }
211  await npm.exec('cache', ['ls', '@fritzy/staydown', '@gar/npm-expansion'])
212  t.matchSnapshot(joinedOutput(), 'logs cache entries for @gar and @fritzy')
213})
214
215t.test('cache ls corrupted', async t => {
216  const keys = [
217    'make-fetch-happen:request-cache:https://registry.npmjs.org/corrupted',
218    'make-fetch-happen:request-cache:https://registry.npmjs.org/corrupted/-/corrupted-3.1.0.tgz',
219  ]
220  const { npm, joinedOutput } = await loadMockNpm(t)
221  const cache = path.join(npm.cache, '_cacache')
222  for (const key of keys) {
223    await cacache.put(cache, key, Buffer.from('<>>>}"'))
224  }
225  await npm.exec('cache', ['ls', 'corrupted'])
226  t.matchSnapshot(joinedOutput(), 'logs cache entries with bad data')
227})
228
229t.test('cache ls missing packument version not an object', async t => {
230  const { npm, joinedOutput } = await loadMockNpm(t)
231  const cache = path.join(npm.cache, '_cacache')
232  await cacache.put(cache,
233    'make-fetch-happen:request-cache:https://registry.npmjs.org/missing-version',
234    JSON.stringify({ versions: 'not an object' })
235  )
236  await npm.exec('cache', ['ls', 'missing-version'])
237  t.matchSnapshot(joinedOutput(), 'logs cache entry for packument')
238})
239
240t.test('cache rm', async t => {
241  const { npm, joinedOutput } = await loadMockNpm(t)
242  const cache = path.join(npm.cache, '_cacache')
243  // eslint-disable-next-line max-len
244  await cacache.put(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package', '{}')
245  // eslint-disable-next-line max-len
246  await cacache.put(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz', 'test data')
247  // eslint-disable-next-line max-len
248  await npm.exec('cache', ['rm', 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz'])
249  t.matchSnapshot(joinedOutput(), 'logs deleting single entry')
250  // eslint-disable-next-line max-len
251  t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package'))
252  // eslint-disable-next-line max-len
253  t.rejects(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz'))
254})
255
256t.test('cache rm unfound', async t => {
257  const { npm, joinedOutput } = await loadMockNpm(t)
258  await npm.exec('cache', ['rm', 'made-up-key'])
259  t.same(joinedOutput(), '', 'no errors, no output')
260})
261
262t.test('cache verify', async t => {
263  const { npm, joinedOutput } = await loadMockNpm(t)
264  await npm.exec('cache', ['verify'])
265  t.matchSnapshot(joinedOutput(), 'shows verified cache output')
266})
267
268t.test('cache verify as part of home', async t => {
269  const { npm, joinedOutput } = await loadMockNpm(t, {
270    globals: ({ prefix }) => ({ 'process.env.HOME': path.dirname(prefix) }),
271  })
272  await npm.exec('cache', ['verify'])
273  t.match(joinedOutput(), 'Cache verified and compressed (~', 'contains ~ shorthand')
274})
275
276t.test('cache verify w/ extra output', async t => {
277  const verify = {
278    runTime: {
279      markStartTime: 0,
280      fixPerms: 3,
281      garbageCollect: 54982,
282      rebuildIndex: 62779,
283      cleanTmp: 62781,
284      writeVerifile: 62783,
285      markEndTime: 62783,
286      total: 62783,
287    },
288    verifiedContent: 17057,
289    reclaimedCount: 1144,
290    reclaimedSize: 248164665,
291    badContentCount: 12345,
292    keptSize: 1644485260,
293    missingContent: 92,
294    rejectedEntries: 92,
295    totalEntries: 20175,
296  }
297  const { npm, joinedOutput } = await loadMockNpm(t, {
298    mocks: { cacache: { verify: () => verify } },
299  })
300  await npm.exec('cache', ['verify'])
301  t.matchSnapshot(joinedOutput(), 'shows extra output')
302})
303
304t.test('cache completion', async t => {
305  const { cache } = await loadMockNpm(t, { command: 'cache' })
306  const { completion } = cache
307
308  const testComp = (argv, expect) => {
309    return t.resolveMatch(completion({ conf: { argv: { remain: argv } } }), expect, argv.join(' '))
310  }
311
312  await Promise.all([
313    testComp(['npm', 'cache'], ['add', 'clean', 'verify']),
314    testComp(['npm', 'cache', 'add'], []),
315    testComp(['npm', 'cache', 'clean'], []),
316    testComp(['npm', 'cache', 'verify'], []),
317  ])
318})
319