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