• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const t = require('tap')
2const fs = require('fs')
3const path = require('path')
4
5const { load: loadMockNpm } = require('../../fixtures/mock-npm')
6const tnock = require('../../fixtures/tnock.js')
7const mockGlobals = require('@npmcli/mock-globals')
8const { cleanCwd, cleanDate } = require('../../fixtures/clean-snapshot.js')
9
10const cleanCacheSha = (str) =>
11  str.replace(/content-v2\/sha512\/[^"]+/g, 'content-v2/sha512/{sha}')
12
13t.cleanSnapshot = p => cleanCacheSha(cleanDate(cleanCwd(p)))
14
15const npmManifest = (version) => {
16  return {
17    name: 'npm',
18    versions: {
19      [version]: {
20        name: 'npm',
21        version: version,
22      },
23    },
24    time: {
25      [version]: new Date(),
26    },
27    'dist-tags': { latest: version },
28  }
29}
30
31const nodeVersions = [
32  { version: 'v2.0.1', lts: false },
33  { version: 'v2.0.0', lts: false },
34  { version: 'v1.0.0', lts: 'NpmTestium' },
35]
36
37const dirs = {
38  prefixDir: {
39    node_modules: {
40      testLink: t.fixture('symlink', './testDir'),
41      testDir: {
42        testFile: 'test contents',
43      },
44      '.bin': {},
45    },
46  },
47  globalPrefixDir: {
48    bin: {},
49    node_modules: {},
50  },
51}
52
53const globals = ({ globalPrefix }) => {
54  return {
55    process: {
56      'env.PATH': `${globalPrefix}:${path.join(globalPrefix, 'bin')}`,
57      version: 'v1.0.0',
58    },
59  }
60}
61
62mockGlobals(t, {
63  process: {
64    // set platform to not-windows before any tests because mockNpm
65    // sets the platform specific location of node_modules based on it
66    platform: 'test-not-windows',
67    // getuid and getgid do not exist in windows, so we shim them
68    // to return 0, as that is the value that lstat will assign the
69    // gid and uid properties for fs.Stats objects
70    ...(process.platform === 'win32' ? { getuid: () => 0, getgid: () => 0 } : {}),
71  },
72})
73
74const mocks = {
75  '{ROOT}/package.json': { version: '1.0.0' },
76  which: async () => '/path/to/git',
77  cacache: {
78    verify: () => {
79      return { badContentCount: 0, reclaimedCount: 0, missingContent: 0, verifiedContent: 0 }
80    },
81  },
82}
83
84t.test('all clear', async t => {
85  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
86    mocks,
87    globals,
88    ...dirs,
89  })
90  tnock(t, npm.config.get('registry'))
91    .get('/-/ping?write=true').reply(200, '{}')
92    .get('/npm').reply(200, npmManifest(npm.version))
93  tnock(t, 'https://nodejs.org')
94    .get('/dist/index.json').reply(200, nodeVersions)
95  await npm.exec('doctor', [])
96  t.matchSnapshot(joinedOutput(), 'output')
97  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
98})
99
100t.test('all clear in color', async t => {
101  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
102    mocks,
103    globals,
104    ...dirs,
105    config: {
106      color: 'always',
107    },
108  })
109  tnock(t, npm.config.get('registry'))
110    .get('/-/ping?write=true').reply(200, '{}')
111    .get('/npm').reply(200, npmManifest(npm.version))
112  tnock(t, 'https://nodejs.org')
113    .get('/dist/index.json').reply(200, nodeVersions)
114  await npm.exec('doctor', [])
115  t.matchSnapshot(joinedOutput(), 'everything is ok in color')
116  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
117})
118
119t.test('silent success', async t => {
120  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
121    mocks,
122    globals,
123    config: {
124      loglevel: 'silent',
125    },
126    ...dirs,
127  })
128  tnock(t, npm.config.get('registry'))
129    .get('/-/ping?write=true').reply(200, '{}')
130    .get('/npm').reply(200, npmManifest(npm.version))
131  tnock(t, 'https://nodejs.org')
132    .get('/dist/index.json').reply(200, nodeVersions)
133  await npm.exec('doctor', [])
134  t.matchSnapshot(joinedOutput(), 'output')
135  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
136})
137
138t.test('silent errors', async t => {
139  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
140    mocks,
141    globals,
142    config: {
143      loglevel: 'silent',
144    },
145    ...dirs,
146  })
147  tnock(t, npm.config.get('registry'))
148    .get('/-/ping?write=true').reply(404, '{}')
149  await t.rejects(npm.exec('doctor', ['ping']), {
150    message: /Check logs/,
151  })
152  t.matchSnapshot(joinedOutput(), 'output')
153  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
154})
155
156t.test('ping 404', async t => {
157  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
158    mocks,
159    globals,
160    ...dirs,
161  })
162  tnock(t, npm.config.get('registry'))
163    .get('/-/ping?write=true').reply(404, '{}')
164    .get('/npm').reply(200, npmManifest(npm.version))
165  tnock(t, 'https://nodejs.org')
166    .get('/dist/index.json').reply(200, nodeVersions)
167  await t.rejects(npm.exec('doctor', []), {
168    message: /See above/,
169  })
170  t.matchSnapshot(joinedOutput(), 'ping 404')
171  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
172})
173
174t.test('ping 404 in color', async t => {
175  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
176    mocks,
177    globals,
178    ...dirs,
179    config: {
180      color: 'always',
181    },
182  })
183  tnock(t, npm.config.get('registry'))
184    .get('/-/ping?write=true').reply(404, '{}')
185    .get('/npm').reply(200, npmManifest(npm.version))
186  tnock(t, 'https://nodejs.org')
187    .get('/dist/index.json').reply(200, nodeVersions)
188  await t.rejects(npm.exec('doctor', []))
189  t.matchSnapshot(joinedOutput(), 'ping 404 in color')
190  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
191})
192
193t.test('ping exception with code', async t => {
194  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
195    mocks,
196    globals,
197    ...dirs,
198  })
199  tnock(t, npm.config.get('registry'))
200    .get('/-/ping?write=true').replyWithError({ message: 'Test Error', code: 'TEST' })
201    .get('/npm').reply(200, npmManifest(npm.version))
202  tnock(t, 'https://nodejs.org')
203    .get('/dist/index.json').reply(200, nodeVersions)
204  await t.rejects(npm.exec('doctor', []))
205  t.matchSnapshot(joinedOutput(), 'ping failure')
206  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
207})
208
209t.test('ping exception without code', async t => {
210  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
211    mocks,
212    globals,
213    ...dirs,
214  })
215  tnock(t, npm.config.get('registry'))
216    .get('/-/ping?write=true').replyWithError({ message: 'Test Error', code: false })
217    .get('/npm').reply(200, npmManifest(npm.version))
218  tnock(t, 'https://nodejs.org')
219    .get('/dist/index.json').reply(200, nodeVersions)
220  await t.rejects(npm.exec('doctor', []))
221  t.matchSnapshot(joinedOutput(), 'ping failure')
222  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
223})
224
225t.test('npm out of date', async t => {
226  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
227    mocks,
228    globals,
229    ...dirs,
230  })
231  tnock(t, npm.config.get('registry'))
232    .get('/-/ping?write=true').reply(200, '{}')
233    .get('/npm').reply(200, npmManifest('2.0.0'))
234  tnock(t, 'https://nodejs.org')
235    .get('/dist/index.json').reply(200, nodeVersions)
236  await t.rejects(npm.exec('doctor', []))
237  t.matchSnapshot(joinedOutput(), 'npm is out of date')
238  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
239})
240
241t.test('node out of date - lts', async t => {
242  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
243    mocks,
244    globals: (context) => {
245      const g = globals(context)
246      return {
247        ...g,
248        process: {
249          ...g.process,
250          version: 'v0.0.1',
251        },
252      }
253    },
254    ...dirs,
255  })
256  tnock(t, npm.config.get('registry'))
257    .get('/-/ping?write=true').reply(200, '{}')
258    .get('/npm').reply(200, npmManifest(npm.version))
259  tnock(t, 'https://nodejs.org')
260    .get('/dist/index.json').reply(200, nodeVersions)
261  await t.rejects(npm.exec('doctor', []))
262  t.matchSnapshot(joinedOutput(), 'node is out of date')
263  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
264})
265
266t.test('node out of date - current', async t => {
267  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
268    mocks,
269    globals: (context) => {
270      const g = globals(context)
271      return {
272        ...g,
273        process: {
274          ...g.process,
275          version: 'v2.0.0',
276        },
277      }
278    },
279    ...dirs,
280  })
281  tnock(t, npm.config.get('registry'))
282    .get('/-/ping?write=true').reply(200, '{}')
283    .get('/npm').reply(200, npmManifest(npm.version))
284  tnock(t, 'https://nodejs.org')
285    .get('/dist/index.json').reply(200, nodeVersions)
286  await t.rejects(npm.exec('doctor', []))
287  t.matchSnapshot(joinedOutput(), 'node is out of date')
288  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
289})
290
291t.test('non-default registry', async t => {
292  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
293    mocks,
294    globals,
295    config: { registry: 'http://some-other-url.npmjs.org' },
296    ...dirs,
297  })
298  tnock(t, npm.config.get('registry'))
299    .get('/-/ping?write=true').reply(200, '{}')
300    .get('/npm').reply(200, npmManifest(npm.version))
301  tnock(t, 'https://nodejs.org')
302    .get('/dist/index.json').reply(200, nodeVersions)
303  await t.rejects(npm.exec('doctor', []))
304  t.matchSnapshot(joinedOutput(), 'non default registry')
305  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
306})
307
308t.test('missing git', async t => {
309  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
310    mocks: {
311      ...mocks,
312      which: async () => {
313        throw new Error('test error')
314      },
315    },
316    globals,
317    ...dirs,
318  })
319  tnock(t, npm.config.get('registry'))
320    .get('/-/ping?write=true').reply(200, '{}')
321    .get('/npm').reply(200, npmManifest(npm.version))
322  tnock(t, 'https://nodejs.org')
323    .get('/dist/index.json').reply(200, nodeVersions)
324  await t.rejects(npm.exec('doctor', []))
325  t.matchSnapshot(joinedOutput(), 'missing git')
326  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
327})
328
329t.test('windows skips permissions checks', async t => {
330  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
331    mocks,
332    globals: (context) => {
333      const g = globals(context)
334      return {
335        ...g,
336        process: {
337          ...g.process,
338          platform: 'win32',
339        },
340      }
341    },
342    prefixDir: {},
343    globalPrefixDir: {},
344  })
345  tnock(t, npm.config.get('registry'))
346    .get('/-/ping?write=true').reply(200, '{}')
347    .get('/npm').reply(200, npmManifest(npm.version))
348  tnock(t, 'https://nodejs.org')
349    .get('/dist/index.json').reply(200, nodeVersions)
350  await npm.exec('doctor', [])
351  t.matchSnapshot(joinedOutput(), 'no permissions checks')
352  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
353})
354
355t.test('missing global directories', async t => {
356  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
357    mocks,
358    globals,
359    prefixDir: dirs.prefixDir,
360    globalPrefixDir: {},
361  })
362  tnock(t, npm.config.get('registry'))
363    .get('/-/ping?write=true').reply(200, '{}')
364    .get('/npm').reply(200, npmManifest(npm.version))
365  tnock(t, 'https://nodejs.org')
366    .get('/dist/index.json').reply(200, nodeVersions)
367  await t.rejects(npm.exec('doctor', []))
368  t.matchSnapshot(joinedOutput(), 'missing global directories')
369  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
370})
371
372t.test('missing local node_modules', async t => {
373  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
374    mocks,
375    globals,
376    globalPrefixDir: dirs.globalPrefixDir,
377  })
378  tnock(t, npm.config.get('registry'))
379    .get('/-/ping?write=true').reply(200, '{}')
380    .get('/npm').reply(200, npmManifest(npm.version))
381  tnock(t, 'https://nodejs.org')
382    .get('/dist/index.json').reply(200, nodeVersions)
383  await npm.exec('doctor', [])
384  t.matchSnapshot(joinedOutput(), 'missing local node_modules')
385  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
386})
387
388t.test('incorrect owner', async t => {
389  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
390    mocks: {
391      ...mocks,
392      fs: {
393        ...fs,
394        lstat: (p, cb) => {
395          const stat = fs.lstatSync(p)
396          if (p.endsWith('_cacache')) {
397            stat.uid += 1
398            stat.gid += 1
399          }
400          return cb(null, stat)
401        },
402      },
403    },
404    globals,
405    ...dirs,
406  })
407  tnock(t, npm.config.get('registry'))
408    .get('/-/ping?write=true').reply(200, '{}')
409    .get('/npm').reply(200, npmManifest(npm.version))
410  tnock(t, 'https://nodejs.org')
411    .get('/dist/index.json').reply(200, nodeVersions)
412  await t.rejects(npm.exec('doctor', []))
413  t.matchSnapshot(joinedOutput(), 'incorrect owner')
414  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
415})
416
417t.test('incorrect permissions', async t => {
418  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
419    mocks: {
420      ...mocks,
421      fs: {
422        ...fs,
423        access: () => {
424          throw new Error('Test Error')
425        },
426      },
427    },
428    globals,
429    ...dirs,
430  })
431  tnock(t, npm.config.get('registry'))
432    .get('/-/ping?write=true').reply(200, '{}')
433    .get('/npm').reply(200, npmManifest(npm.version))
434  tnock(t, 'https://nodejs.org')
435    .get('/dist/index.json').reply(200, nodeVersions)
436  await t.rejects(npm.exec('doctor', []))
437  t.matchSnapshot(joinedOutput(), 'incorrect owner')
438  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
439})
440
441t.test('error reading directory', async t => {
442  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
443    mocks: {
444      ...mocks,
445      fs: {
446        ...fs,
447        readdir: () => {
448          throw new Error('Test Error')
449        },
450      },
451    },
452    globals,
453    ...dirs,
454  })
455  tnock(t, npm.config.get('registry'))
456    .get('/-/ping?write=true').reply(200, '{}')
457    .get('/npm').reply(200, npmManifest(npm.version))
458  tnock(t, 'https://nodejs.org')
459    .get('/dist/index.json').reply(200, nodeVersions)
460  await t.rejects(npm.exec('doctor', []))
461  t.matchSnapshot(joinedOutput(), 'readdir error')
462  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
463})
464
465t.test('cacache badContent', async t => {
466  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
467    mocks: {
468      ...mocks,
469      cacache: {
470        verify: async () => {
471          return { badContentCount: 1, reclaimedCount: 0, missingContent: 0, verifiedContent: 2 }
472        },
473      },
474    },
475    globals,
476    ...dirs,
477  })
478  tnock(t, npm.config.get('registry'))
479    .get('/-/ping?write=true').reply(200, '{}')
480    .get('/npm').reply(200, npmManifest(npm.version))
481  tnock(t, 'https://nodejs.org')
482    .get('/dist/index.json').reply(200, nodeVersions)
483  await npm.exec('doctor', [])
484  t.matchSnapshot(joinedOutput(), 'corrupted cache content')
485  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
486})
487
488t.test('cacache reclaimedCount', async t => {
489  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
490    mocks: {
491      ...mocks,
492      cacache: {
493        verify: async () => {
494          return { badContentCount: 0, reclaimedCount: 1, missingContent: 0, verifiedContent: 2 }
495        },
496      },
497    },
498    globals,
499    ...dirs,
500  })
501  tnock(t, npm.config.get('registry'))
502    .get('/-/ping?write=true').reply(200, '{}')
503    .get('/npm').reply(200, npmManifest(npm.version))
504  tnock(t, 'https://nodejs.org')
505    .get('/dist/index.json').reply(200, nodeVersions)
506  await npm.exec('doctor', [])
507  t.matchSnapshot(joinedOutput(), 'content garbage collected')
508  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
509})
510
511t.test('cacache missingContent', async t => {
512  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
513    mocks: {
514      ...mocks,
515      cacache: {
516        verify: async () => {
517          return { badContentCount: 0, reclaimedCount: 0, missingContent: 1, verifiedContent: 2 }
518        },
519      },
520    },
521    globals,
522    ...dirs,
523  })
524  tnock(t, npm.config.get('registry'))
525    .get('/-/ping?write=true').reply(200, '{}')
526    .get('/npm').reply(200, npmManifest(npm.version))
527  tnock(t, 'https://nodejs.org')
528    .get('/dist/index.json').reply(200, nodeVersions)
529  await npm.exec('doctor', [])
530  t.matchSnapshot(joinedOutput(), 'missing content')
531  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
532})
533
534t.test('bad proxy', async t => {
535  const { joinedOutput, logs, npm } = await loadMockNpm(t, {
536    mocks,
537    globals,
538    config: {
539      proxy: 'ssh://npmjs.org',
540    },
541    ...dirs,
542  })
543  await t.rejects(npm.exec('doctor', []))
544  t.matchSnapshot(joinedOutput(), 'output')
545  t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
546})
547
548t.test('discrete checks', t => {
549  t.test('ping', async t => {
550    const { joinedOutput, logs, npm } = await loadMockNpm(t, {
551      mocks,
552      globals,
553      ...dirs,
554    })
555    tnock(t, npm.config.get('registry'))
556      .get('/-/ping?write=true').reply(200, '{}')
557    await npm.exec('doctor', ['ping'])
558    t.matchSnapshot(joinedOutput(), 'output')
559    t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
560  })
561
562  t.test('versions', async t => {
563    const { joinedOutput, logs, npm } = await loadMockNpm(t, {
564      mocks,
565      globals,
566      ...dirs,
567    })
568    tnock(t, npm.config.get('registry'))
569      .get('/npm').reply(200, npmManifest(npm.version))
570    tnock(t, 'https://nodejs.org')
571      .get('/dist/index.json').reply(200, nodeVersions)
572    await npm.exec('doctor', ['versions'])
573    t.matchSnapshot(joinedOutput(), 'output')
574    t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
575  })
576
577  t.test('registry', async t => {
578    const { joinedOutput, logs, npm } = await loadMockNpm(t, {
579      mocks,
580      globals,
581      ...dirs,
582    })
583    tnock(t, npm.config.get('registry'))
584      .get('/-/ping?write=true').reply(200, '{}')
585    await npm.exec('doctor', ['registry'])
586    t.matchSnapshot(joinedOutput(), 'output')
587    t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
588  })
589
590  t.test('git', async t => {
591    const { joinedOutput, logs, npm } = await loadMockNpm(t, {
592      mocks,
593      globals,
594      ...dirs,
595    })
596    await npm.exec('doctor', ['git'])
597    t.matchSnapshot(joinedOutput(), 'output')
598    t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
599  })
600
601  t.test('permissions - not windows', async t => {
602    const { joinedOutput, logs, npm } = await loadMockNpm(t, {
603      mocks,
604      globals,
605      ...dirs,
606    })
607    await npm.exec('doctor', ['permissions'])
608    t.matchSnapshot(joinedOutput(), 'output')
609    t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
610  })
611
612  t.test('cache', async t => {
613    const { joinedOutput, logs, npm } = await loadMockNpm(t, {
614      mocks,
615      globals,
616      ...dirs,
617    })
618    await npm.exec('doctor', ['cache'])
619    t.matchSnapshot(joinedOutput(), 'output')
620    t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
621  })
622
623  t.test('permissions - windows', async t => {
624    const { joinedOutput, logs, npm } = await loadMockNpm(t, {
625      mocks,
626      globals: (context) => {
627        const g = globals(context)
628        return {
629          ...g,
630          process: {
631            ...g.process,
632            platform: 'win32',
633          },
634        }
635      },
636      prefixDir: {},
637      globalPrefixDir: {},
638    })
639    await npm.exec('doctor', ['permissions'])
640    t.matchSnapshot(joinedOutput(), 'output')
641    t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
642  })
643
644  t.test('invalid environment', async t => {
645    const { joinedOutput, logs, npm } = await loadMockNpm(t, {
646      mocks,
647      globals: (context) => {
648        const g = globals(context)
649        return {
650          ...g,
651          process: {
652            ...g.process,
653            'env.PATH': '/nope',
654          },
655        }
656      },
657      prefixDir: {},
658      globalPrefixDir: {},
659    })
660    await t.rejects(npm.exec('doctor', ['environment']))
661    t.matchSnapshot(joinedOutput(), 'output')
662    t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
663  })
664
665  t.end()
666})
667