• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2
3const BB = require('bluebird')
4
5const common = BB.promisifyAll(require('../common-tap.js'))
6const fs = require('fs')
7const mr = common.fakeRegistry.compat
8const path = require('path')
9const rimraf = BB.promisify(require('rimraf'))
10const Tacks = require('tacks')
11const tap = require('tap')
12const test = tap.test
13
14const Dir = Tacks.Dir
15const File = Tacks.File
16const testDir = common.pkg
17
18const EXEC_OPTS = { cwd: testDir }
19
20tap.tearDown(function () {
21  process.chdir(__dirname)
22  try {
23    rimraf.sync(testDir)
24  } catch (e) {
25    if (process.platform !== 'win32') {
26      throw e
27    }
28  }
29})
30
31function tmock (t) {
32  return mr({port: common.port}).then(s => {
33    t.tearDown(function () {
34      s.done()
35      s.close()
36      rimraf.sync(testDir)
37    })
38    return s
39  })
40}
41
42test('fixes shallow vulnerabilities', t => {
43  const fixture = new Tacks(new Dir({
44    'package.json': new File({
45      name: 'foo',
46      version: '1.0.0',
47      dependencies: {
48        baddep: '1.0.0'
49      }
50    })
51  }))
52  fixture.create(testDir)
53  return tmock(t).then(srv => {
54    srv.filteringRequestBody(req => 'ok')
55    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah')
56    srv.get('/baddep').twice().reply(200, {
57      name: 'baddep',
58      'dist-tags': {
59        'latest': '1.2.3'
60      },
61      versions: {
62        '1.0.0': {
63          name: 'baddep',
64          version: '1.0.0',
65          _hasShrinkwrap: false,
66          dist: {
67            shasum: 'deadbeef',
68            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
69          }
70        },
71        '1.2.3': {
72          name: 'baddep',
73          version: '1.2.3',
74          _hasShrinkwrap: false,
75          dist: {
76            shasum: 'deadbeef',
77            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
78          }
79        }
80      }
81    })
82    return common.npm([
83      'install',
84      '--audit',
85      '--json',
86      '--package-lock-only',
87      '--registry', common.registry,
88      '--cache', path.join(testDir, 'npm-cache')
89    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
90      t.equal(code, 0, 'exited OK')
91      t.comment(stderr)
92      t.similar(JSON.parse(stdout), {
93        added: [{
94          action: 'add',
95          name: 'baddep',
96          version: '1.0.0'
97        }]
98      }, 'installed bad version')
99      srv.filteringRequestBody(req => 'ok')
100      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
101        actions: [{
102          action: 'update',
103          module: 'baddep',
104          target: '1.2.3',
105          resolves: [{path: 'baddep'}]
106        }],
107        metadata: {
108          vulnerabilities: {
109            critical: 1
110          }
111        }
112      })
113      return common.npm([
114        'audit', 'fix',
115        '--package-lock-only',
116        '--json',
117        '--registry', common.registry,
118        '--cache', path.join(testDir, 'npm-cache')
119      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
120        t.equal(code, 0, 'exited OK')
121        t.comment(stderr)
122        t.similar(JSON.parse(stdout), {
123          added: [{
124            action: 'add',
125            name: 'baddep',
126            version: '1.2.3'
127          }]
128        }, 'reported dependency update')
129        t.similar(JSON.parse(fs.readFileSync(path.join(testDir, 'package-lock.json'), 'utf8')), {
130          dependencies: {
131            baddep: {
132              version: '1.2.3',
133              resolved: common.registry + '/idk/-/idk-1.2.3.tgz',
134              integrity: 'sha1-3q2+7w=='
135            }
136          }
137        }, 'pkglock updated correctly')
138      })
139    })
140  })
141})
142
143test('fixes nested dep vulnerabilities', t => {
144  const fixture = new Tacks(new Dir({
145    'package.json': new File({
146      name: 'foo',
147      version: '1.0.0',
148      dependencies: {
149        gooddep: '^1.0.0'
150      }
151    })
152  }))
153  fixture.create(testDir)
154  return tmock(t).then(srv => {
155    srv.filteringRequestBody(req => 'ok')
156    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah')
157    srv.get('/baddep').reply(200, {
158      name: 'baddep',
159      'dist-tags': {
160        'latest': '1.0.0'
161      },
162      versions: {
163        '1.0.0': {
164          name: 'baddep',
165          version: '1.0.0',
166          _hasShrinkwrap: false,
167          dist: {
168            shasum: 'c0ffee',
169            integrity: 'sha1-c0ffee',
170            tarball: common.registry + '/baddep/-/baddep-1.0.0.tgz'
171          }
172        },
173        '1.2.3': {
174          name: 'baddep',
175          version: '1.2.3',
176          _hasShrinkwrap: false,
177          dist: {
178            shasum: 'bada55',
179            integrity: 'sha1-bada55',
180            tarball: common.registry + '/baddep/-/baddep-1.2.3.tgz'
181          }
182        }
183      }
184    })
185
186    srv.get('/gooddep').reply(200, {
187      name: 'gooddep',
188      'dist-tags': {
189        'latest': '1.0.0'
190      },
191      versions: {
192        '1.0.0': {
193          name: 'gooddep',
194          version: '1.0.0',
195          dependencies: {
196            baddep: '^1.0.0'
197          },
198          _hasShrinkwrap: false,
199          dist: {
200            shasum: '1234',
201            tarball: common.registry + '/gooddep/-/gooddep-1.0.0.tgz'
202          }
203        },
204        '1.2.3': {
205          name: 'gooddep',
206          version: '1.2.3',
207          _hasShrinkwrap: false,
208          dependencies: {
209            baddep: '^1.0.0'
210          },
211          dist: {
212            shasum: '123456',
213            tarball: common.registry + '/gooddep/-/gooddep-1.2.3.tgz'
214          }
215        }
216      }
217    })
218
219    return common.npm([
220      'install',
221      '--audit',
222      '--json',
223      '--global-style',
224      '--package-lock-only',
225      '--registry', common.registry,
226      '--cache', path.join(testDir, 'npm-cache')
227    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
228      t.equal(code, 0, 'exited OK')
229      t.comment(stderr)
230      t.similar(JSON.parse(stdout), {
231        added: [{
232          action: 'add',
233          name: 'baddep',
234          version: '1.0.0'
235        }, {
236          action: 'add',
237          name: 'gooddep',
238          version: '1.0.0'
239        }]
240      }, 'installed bad version')
241      srv.filteringRequestBody(req => 'ok')
242      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
243        actions: [{
244          action: 'update',
245          module: 'baddep',
246          target: '1.2.3',
247          resolves: [{path: 'gooddep>baddep'}]
248        }],
249        metadata: {
250          vulnerabilities: {
251            critical: 1
252          }
253        }
254      })
255      return common.npm([
256        'audit', 'fix',
257        '--package-lock-only',
258        '--offline',
259        '--json',
260        '--global-style',
261        '--registry', common.registry,
262        '--cache', path.join(testDir, 'npm-cache')
263      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
264        t.equal(code, 0, 'exited OK')
265        t.comment(stderr)
266        t.similar(JSON.parse(stdout), {
267          added: [{
268            action: 'add',
269            name: 'baddep',
270            version: '1.2.3'
271          }, {
272            action: 'add',
273            name: 'gooddep',
274            version: '1.0.0'
275          }]
276        }, 'reported dependency update')
277        t.similar(JSON.parse(fs.readFileSync(path.join(testDir, 'package-lock.json'), 'utf8')), {
278          dependencies: {
279            gooddep: {
280              version: '1.0.0',
281              resolved: common.registry + '/gooddep/-/gooddep-1.0.0.tgz',
282              integrity: 'sha1-EjQ=',
283              requires: {
284                baddep: '^1.0.0'
285              },
286              dependencies: {
287                baddep: {
288                  version: '1.2.3',
289                  resolved: common.registry + '/baddep/-/baddep-1.2.3.tgz',
290                  integrity: 'sha1-bada55'
291                }
292              }
293            }
294          }
295        }, 'pkglock updated correctly')
296      })
297    })
298  })
299})
300
301test('no semver-major without --force', t => {
302  const fixture = new Tacks(new Dir({
303    'package.json': new File({
304      name: 'foo',
305      version: '1.0.0',
306      dependencies: {
307        baddep: '1.0.0'
308      }
309    })
310  }))
311  fixture.create(testDir)
312  return tmock(t).then(srv => {
313    srv.filteringRequestBody(req => 'ok')
314    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah')
315    srv.get('/baddep').twice().reply(200, {
316      name: 'baddep',
317      'dist-tags': {
318        'latest': '2.0.0'
319      },
320      versions: {
321        '1.0.0': {
322          name: 'baddep',
323          version: '1.0.0',
324          _hasShrinkwrap: false,
325          dist: {
326            shasum: 'deadbeef',
327            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
328          }
329        },
330        '2.0.0': {
331          name: 'baddep',
332          version: '2.0.0',
333          _hasShrinkwrap: false,
334          dist: {
335            shasum: 'deadbeef',
336            tarball: common.registry + '/idk/-/idk-2.0.0.tgz'
337          }
338        }
339      }
340    })
341    return common.npm([
342      'install',
343      '--audit',
344      '--json',
345      '--package-lock-only',
346      '--registry', common.registry,
347      '--cache', path.join(testDir, 'npm-cache')
348    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
349      t.equal(code, 0, 'exited OK')
350      t.comment(stderr)
351      t.similar(JSON.parse(stdout), {
352        added: [{
353          action: 'add',
354          name: 'baddep',
355          version: '1.0.0'
356        }]
357      }, 'installed bad version')
358      srv.filteringRequestBody(req => 'ok')
359      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
360        actions: [{
361          action: 'install',
362          module: 'baddep',
363          target: '2.0.0',
364          isMajor: true,
365          resolves: [{path: 'baddep'}]
366        }],
367        metadata: {
368          vulnerabilities: {
369            critical: 1
370          }
371        }
372      })
373      return common.npm([
374        'audit', 'fix',
375        '--package-lock-only',
376        '--registry', common.registry,
377        '--loglevel=warn',
378        '--cache', path.join(testDir, 'npm-cache')
379      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
380        t.equal(code, 0, 'exited OK')
381        t.comment(stderr)
382        t.match(stdout, /breaking changes/, 'informs about semver-major')
383        t.match(stdout, /npm audit fix --force/, 'recommends --force')
384        t.similar(JSON.parse(fs.readFileSync(path.join(testDir, 'package-lock.json'), 'utf8')), {
385          dependencies: {
386            baddep: {
387              version: '1.0.0'
388            }
389          }
390        }, 'pkglock not updated')
391      })
392    })
393  })
394})
395
396test('semver-major when --force', t => {
397  const fixture = new Tacks(new Dir({
398    'package.json': new File({
399      name: 'foo',
400      version: '1.0.0',
401      dependencies: {
402        baddep: '1.0.0'
403      }
404    })
405  }))
406  fixture.create(testDir)
407  return tmock(t).then(srv => {
408    srv.filteringRequestBody(req => 'ok')
409    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah')
410    srv.get('/baddep').twice().reply(200, {
411      name: 'baddep',
412      'dist-tags': {
413        'latest': '2.0.0'
414      },
415      versions: {
416        '1.0.0': {
417          name: 'baddep',
418          version: '1.0.0',
419          _hasShrinkwrap: false,
420          dist: {
421            shasum: 'deadbeef',
422            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
423          }
424        },
425        '2.0.0': {
426          name: 'baddep',
427          version: '2.0.0',
428          _hasShrinkwrap: false,
429          dist: {
430            shasum: 'deadbeef',
431            tarball: common.registry + '/idk/-/idk-2.0.0.tgz'
432          }
433        }
434      }
435    })
436    return common.npm([
437      'install',
438      '--audit',
439      '--json',
440      '--package-lock-only',
441      '--registry', common.registry,
442      '--cache', path.join(testDir, 'npm-cache')
443    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
444      t.equal(code, 0, 'exited OK')
445      t.comment(stderr)
446      t.similar(JSON.parse(stdout), {
447        added: [{
448          action: 'add',
449          name: 'baddep',
450          version: '1.0.0'
451        }]
452      }, 'installed bad version')
453      srv.filteringRequestBody(req => 'ok')
454      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
455        actions: [{
456          action: 'install',
457          module: 'baddep',
458          target: '2.0.0',
459          isMajor: true,
460          resolves: [{path: 'baddep'}]
461        }],
462        metadata: {
463          vulnerabilities: {
464            critical: 1
465          }
466        }
467      })
468      return common.npm([
469        'audit', 'fix',
470        '--package-lock-only',
471        '--registry', common.registry,
472        '--force',
473        '--cache', path.join(testDir, 'npm-cache')
474      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
475        t.equal(code, 0, 'exited OK')
476        t.comment(stderr)
477        t.match(stdout, /breaking changes/, 'informs about semver-major')
478        t.similar(JSON.parse(fs.readFileSync(path.join(testDir, 'package-lock.json'), 'utf8')), {
479          dependencies: {
480            baddep: {
481              version: '2.0.0'
482            }
483          }
484        }, 'pkglock not updated')
485      })
486    })
487  })
488})
489
490test('no installs for review-requires', t => {
491  const fixture = new Tacks(new Dir({
492    'package.json': new File({
493      name: 'foo',
494      version: '1.0.0',
495      dependencies: {
496        baddep: '1.0.0'
497      }
498    })
499  }))
500  fixture.create(testDir)
501  return tmock(t).then(srv => {
502    srv.filteringRequestBody(req => 'k')
503    srv.post('/-/npm/v1/security/audits/quick', 'k').reply(200, 'yeah')
504    srv.get('/baddep').twice().reply(200, {
505      name: 'baddep',
506      'dist-tags': {
507        'latest': '1.2.3'
508      },
509      versions: {
510        '1.0.0': {
511          name: 'baddep',
512          version: '1.0.0',
513          _hasShrinkwrap: false,
514          dist: {
515            shasum: 'deadbeef',
516            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
517          }
518        },
519        '1.2.3': {
520          name: 'baddep',
521          version: '1.2.3',
522          _hasShrinkwrap: false,
523          dist: {
524            shasum: 'deadbeef',
525            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
526          }
527        }
528      }
529    })
530    return common.npm([
531      'install',
532      '--audit',
533      '--json',
534      '--package-lock-only',
535      '--registry', common.registry,
536      '--cache', path.join(testDir, 'npm-cache')
537    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
538      t.equal(code, 0, 'exited OK')
539      t.comment(stderr)
540      t.similar(JSON.parse(stdout), {
541        added: [{
542          action: 'add',
543          name: 'baddep',
544          version: '1.0.0'
545        }]
546      }, 'installed bad version')
547      srv.filteringRequestBody(req => 'ok')
548      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
549        actions: [{
550          action: 'review',
551          module: 'baddep',
552          target: '1.2.3',
553          resolves: [{path: 'baddep'}]
554        }],
555        metadata: {
556          vulnerabilities: {
557            critical: 1
558          }
559        }
560      })
561      return common.npm([
562        'audit', 'fix',
563        '--package-lock-only',
564        '--json',
565        '--registry', common.registry,
566        '--cache', path.join(testDir, 'npm-cache')
567      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
568        t.equal(code, 0, 'exited OK')
569        t.comment(stderr)
570        t.similar(JSON.parse(stdout), {
571          added: [{
572            action: 'add',
573            name: 'baddep',
574            version: '1.0.0'
575          }]
576        }, 'no update for dependency')
577      })
578    })
579  })
580})
581
582test('nothing to fix', t => {
583  const fixture = new Tacks(new Dir({
584    'package.json': new File({
585      name: 'foo',
586      version: '1.0.0',
587      dependencies: {
588        gooddep: '1.0.0'
589      }
590    })
591  }))
592  fixture.create(testDir)
593  return tmock(t).then(srv => {
594    srv.filteringRequestBody(req => 'ok')
595    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah')
596    srv.get('/gooddep').twice().reply(200, {
597      name: 'gooddep',
598      'dist-tags': {
599        'latest': '1.2.3'
600      },
601      versions: {
602        '1.0.0': {
603          name: 'gooddep',
604          version: '1.0.0',
605          _hasShrinkwrap: false,
606          dist: {
607            shasum: 'deadbeef',
608            tarball: common.registry + '/idk/-/idk-1.0.0.tgz'
609          }
610        },
611        '1.2.3': {
612          name: 'gooddep',
613          version: '1.2.3',
614          _hasShrinkwrap: false,
615          dist: {
616            shasum: 'deadbeef',
617            tarball: common.registry + '/idk/-/idk-1.2.3.tgz'
618          }
619        }
620      }
621    })
622    return common.npm([
623      'install',
624      '--audit',
625      '--json',
626      '--package-lock-only',
627      '--registry', common.registry,
628      '--cache', path.join(testDir, 'npm-cache')
629    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
630      t.equal(code, 0, 'exited OK')
631      t.comment(stderr)
632      t.similar(JSON.parse(stdout), {
633        added: [{
634          action: 'add',
635          name: 'gooddep',
636          version: '1.0.0'
637        }]
638      }, 'installed good version')
639      srv.filteringRequestBody(req => 'ok')
640      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
641        actions: [],
642        metadata: {
643          vulnerabilities: { }
644        }
645      })
646      return common.npm([
647        'audit', 'fix',
648        '--package-lock-only',
649        '--json',
650        '--registry', common.registry,
651        '--cache', path.join(testDir, 'npm-cache')
652      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
653        t.equal(code, 0, 'exited OK')
654        t.comment(stderr)
655        t.similar(JSON.parse(stdout), {
656          added: [{
657            action: 'add',
658            name: 'gooddep',
659            version: '1.0.0'
660          }]
661        }, 'nothing to update')
662      })
663    })
664  })
665})
666
667test('preserves deep deps dev: true', t => {
668  const fixture = new Tacks(new Dir({
669    'package.json': new File({
670      name: 'foo',
671      version: '1.0.0',
672      devDependencies: {
673        gooddep: '^1.0.0'
674      }
675    })
676  }))
677  fixture.create(testDir)
678  return tmock(t).then(srv => {
679    srv.filteringRequestBody(req => 'ok')
680    srv.post('/-/npm/v1/security/audits/quick', 'ok').reply(200, 'yeah')
681    srv.get('/baddep').reply(200, {
682      name: 'baddep',
683      'dist-tags': {
684        'latest': '1.0.0'
685      },
686      versions: {
687        '1.0.0': {
688          name: 'baddep',
689          version: '1.0.0',
690          _hasShrinkwrap: false,
691          dist: {
692            shasum: 'c0ffee',
693            integrity: 'sha1-c0ffee',
694            tarball: common.registry + '/baddep/-/baddep-1.0.0.tgz'
695          }
696        },
697        '1.2.3': {
698          name: 'baddep',
699          version: '1.2.3',
700          _hasShrinkwrap: false,
701          dist: {
702            shasum: 'bada55',
703            integrity: 'sha1-bada55',
704            tarball: common.registry + '/baddep/-/baddep-1.2.3.tgz'
705          }
706        }
707      }
708    })
709
710    srv.get('/gooddep').reply(200, {
711      name: 'gooddep',
712      'dist-tags': {
713        'latest': '1.0.0'
714      },
715      versions: {
716        '1.0.0': {
717          name: 'gooddep',
718          version: '1.0.0',
719          dependencies: {
720            baddep: '^1.0.0'
721          },
722          _hasShrinkwrap: false,
723          dist: {
724            shasum: '1234',
725            tarball: common.registry + '/gooddep/-/gooddep-1.0.0.tgz'
726          }
727        },
728        '1.2.3': {
729          name: 'gooddep',
730          version: '1.2.3',
731          _hasShrinkwrap: false,
732          dependencies: {
733            baddep: '^1.0.0'
734          },
735          dist: {
736            shasum: '123456',
737            tarball: common.registry + '/gooddep/-/gooddep-1.2.3.tgz'
738          }
739        }
740      }
741    })
742
743    return common.npm([
744      'install',
745      '--audit',
746      '--json',
747      '--global-style',
748      '--package-lock-only',
749      '--registry', common.registry,
750      '--cache', path.join(testDir, 'npm-cache')
751    ], EXEC_OPTS).then(([code, stdout, stderr]) => {
752      t.equal(code, 0, 'exited OK')
753      t.comment(stderr)
754      t.similar(JSON.parse(stdout), {
755        added: [{
756          action: 'add',
757          name: 'baddep',
758          version: '1.0.0'
759        }, {
760          action: 'add',
761          name: 'gooddep',
762          version: '1.0.0'
763        }]
764      }, 'installed bad version')
765      srv.filteringRequestBody(req => 'ok')
766      srv.post('/-/npm/v1/security/audits', 'ok').reply(200, {
767        actions: [{
768          action: 'update',
769          module: 'baddep',
770          target: '1.2.3',
771          resolves: [{path: 'gooddep>baddep'}]
772        }],
773        metadata: {
774          vulnerabilities: {
775            critical: 1
776          }
777        }
778      })
779      return common.npm([
780        'audit', 'fix',
781        '--package-lock-only',
782        '--offline',
783        '--json',
784        '--global-style',
785        '--registry', common.registry,
786        '--cache', path.join(testDir, 'npm-cache')
787      ], EXEC_OPTS).then(([code, stdout, stderr]) => {
788        t.equal(code, 0, 'exited OK')
789        t.comment(stderr)
790        t.similar(JSON.parse(stdout), {
791          added: [{
792            action: 'add',
793            name: 'baddep',
794            version: '1.2.3'
795          }, {
796            action: 'add',
797            name: 'gooddep',
798            version: '1.0.0'
799          }]
800        }, 'reported dependency update')
801        t.similar(JSON.parse(fs.readFileSync(path.join(testDir, 'package-lock.json'), 'utf8')), {
802          dependencies: {
803            gooddep: {
804              dev: true,
805              version: '1.0.0',
806              resolved: common.registry + '/gooddep/-/gooddep-1.0.0.tgz',
807              integrity: 'sha1-EjQ=',
808              requires: {
809                baddep: '^1.0.0'
810              },
811              dependencies: {
812                baddep: {
813                  dev: true,
814                  version: '1.2.3',
815                  resolved: common.registry + '/baddep/-/baddep-1.2.3.tgz',
816                  integrity: 'sha1-bada55'
817                }
818              }
819            }
820          }
821        }, 'pkglock updated correctly')
822      })
823    })
824  })
825})
826
827test('cleanup', t => {
828  return rimraf(testDir)
829})
830