• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2
3const crypto = require('crypto')
4const cloneDeep = require('lodash.clonedeep')
5const figgyPudding = require('figgy-pudding')
6const mockTar = require('./util/mock-tarball.js')
7const { PassThrough } = require('stream')
8const ssri = require('ssri')
9const { test } = require('tap')
10const tnock = require('./util/tnock.js')
11
12const publish = require('../publish.js')
13
14const OPTS = figgyPudding({ registry: {} })({
15  registry: 'https://mock.reg/'
16})
17
18const REG = OPTS.registry
19
20test('basic publish', t => {
21  const manifest = {
22    name: 'libnpmpublish',
23    version: '1.0.0',
24    description: 'some stuff'
25  }
26  return mockTar({
27    'package.json': JSON.stringify(manifest),
28    'index.js': 'console.log("hello world")'
29  }).then(tarData => {
30    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
31    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
32    const packument = {
33      name: 'libnpmpublish',
34      description: 'some stuff',
35      readme: '',
36      _id: 'libnpmpublish',
37      'dist-tags': {
38        latest: '1.0.0'
39      },
40      versions: {
41        '1.0.0': {
42          _id: 'libnpmpublish@1.0.0',
43          _nodeVersion: process.versions.node,
44          name: 'libnpmpublish',
45          version: '1.0.0',
46          description: 'some stuff',
47          dist: {
48            shasum,
49            integrity: integrity.toString(),
50            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
51          }
52        }
53      },
54      _attachments: {
55        'libnpmpublish-1.0.0.tgz': {
56          'content_type': 'application/octet-stream',
57          data: tarData.toString('base64'),
58          length: tarData.length
59        }
60      }
61    }
62    const srv = tnock(t, REG)
63    srv.put('/libnpmpublish', body => {
64      t.deepEqual(body, packument, 'posted packument matches expectations')
65      return true
66    }, {
67      authorization: 'Bearer deadbeef'
68    }).reply(201, {})
69
70    return publish(manifest, tarData, OPTS.concat({
71      token: 'deadbeef'
72    })).then(ret => {
73      t.ok(ret, 'publish succeeded')
74    })
75  })
76})
77
78test('scoped publish', t => {
79  const manifest = {
80    name: '@zkat/libnpmpublish',
81    version: '1.0.0',
82    description: 'some stuff'
83  }
84  return mockTar({
85    'package.json': JSON.stringify(manifest),
86    'index.js': 'console.log("hello world")'
87  }).then(tarData => {
88    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
89    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
90    const packument = {
91      name: '@zkat/libnpmpublish',
92      description: 'some stuff',
93      readme: '',
94      _id: '@zkat/libnpmpublish',
95      'dist-tags': {
96        latest: '1.0.0'
97      },
98      versions: {
99        '1.0.0': {
100          _id: '@zkat/libnpmpublish@1.0.0',
101          _nodeVersion: process.versions.node,
102          _npmVersion: '6.9.0',
103          name: '@zkat/libnpmpublish',
104          version: '1.0.0',
105          description: 'some stuff',
106          dist: {
107            shasum,
108            integrity: integrity.toString(),
109            tarball: `http://mock.reg/@zkat/libnpmpublish/-/@zkat/libnpmpublish-1.0.0.tgz`
110          }
111        }
112      },
113      _attachments: {
114        '@zkat/libnpmpublish-1.0.0.tgz': {
115          'content_type': 'application/octet-stream',
116          data: tarData.toString('base64'),
117          length: tarData.length
118        }
119      }
120    }
121    const srv = tnock(t, REG)
122    srv.put('/@zkat%2flibnpmpublish', body => {
123      t.deepEqual(body, packument, 'posted packument matches expectations')
124      return true
125    }, {
126      authorization: 'Bearer deadbeef'
127    }).reply(201, {})
128
129    return publish(manifest, tarData, OPTS.concat({
130      npmVersion: '6.9.0',
131      token: 'deadbeef'
132    })).then(() => {
133      t.ok(true, 'publish succeeded')
134    })
135  })
136})
137
138test('retry after a conflict', t => {
139  const REV = '72-47f2986bfd8e8b55068b204588bbf484'
140  const manifest = {
141    name: 'libnpmpublish',
142    version: '1.0.0',
143    description: 'some stuff'
144  }
145  return mockTar({
146    'package.json': JSON.stringify(manifest),
147    'index.js': 'console.log("hello world")'
148  }).then(tarData => {
149    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
150    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
151    const basePackument = {
152      name: 'libnpmpublish',
153      description: 'some stuff',
154      readme: '',
155      _id: 'libnpmpublish',
156      'dist-tags': {},
157      versions: {},
158      _attachments: {}
159    }
160    const currentPackument = cloneDeep(Object.assign({}, basePackument, {
161      time: {
162        modified: new Date().toISOString(),
163        created: new Date().toISOString(),
164        '1.0.1': new Date().toISOString()
165      },
166      'dist-tags': { latest: '1.0.1' },
167      maintainers: [{ name: 'zkat', email: 'idk@idk.tech' }],
168      versions: {
169        '1.0.1': {
170          _id: 'libnpmpublish@1.0.1',
171          _nodeVersion: process.versions.node,
172          _npmVersion: '6.9.0',
173          name: 'libnpmpublish',
174          version: '1.0.1',
175          description: 'some stuff',
176          dist: {
177            shasum,
178            integrity: integrity.toString(),
179            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.1.tgz`
180          }
181        }
182      },
183      _attachments: {
184        'libnpmpublish-1.0.1.tgz': {
185          'content_type': 'application/octet-stream',
186          data: tarData.toString('base64'),
187          length: tarData.length
188        }
189      }
190    }))
191    const newPackument = cloneDeep(Object.assign({}, basePackument, {
192      'dist-tags': { latest: '1.0.0' },
193      maintainers: [{ name: 'other', email: 'other@idk.tech' }],
194      versions: {
195        '1.0.0': {
196          _id: 'libnpmpublish@1.0.0',
197          _nodeVersion: process.versions.node,
198          _npmVersion: '6.9.0',
199          _npmUser: {
200            name: 'other',
201            email: 'other@idk.tech'
202          },
203          name: 'libnpmpublish',
204          version: '1.0.0',
205          description: 'some stuff',
206          maintainers: [{ name: 'other', email: 'other@idk.tech' }],
207          dist: {
208            shasum,
209            integrity: integrity.toString(),
210            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
211          }
212        }
213      },
214      _attachments: {
215        'libnpmpublish-1.0.0.tgz': {
216          'content_type': 'application/octet-stream',
217          data: tarData.toString('base64'),
218          length: tarData.length
219        }
220      }
221    }))
222    const mergedPackument = cloneDeep(Object.assign({}, basePackument, {
223      time: currentPackument.time,
224      'dist-tags': { latest: '1.0.0' },
225      maintainers: currentPackument.maintainers,
226      versions: Object.assign({}, currentPackument.versions, newPackument.versions),
227      _attachments: Object.assign({}, currentPackument._attachments, newPackument._attachments)
228    }))
229    const srv = tnock(t, REG)
230    srv.put('/libnpmpublish', body => {
231      t.notOk(body._rev, 'no _rev in initial post')
232      t.deepEqual(body, newPackument, 'got conflicting packument')
233      return true
234    }).reply(409, { error: 'gimme _rev plz' })
235    srv.get('/libnpmpublish?write=true').reply(200, Object.assign({
236      _rev: REV
237    }, currentPackument))
238    srv.put('/libnpmpublish', body => {
239      t.deepEqual(body, Object.assign({
240        _rev: REV
241      }, mergedPackument), 'posted packument includes _rev and a merged version')
242      return true
243    }).reply(201, {})
244    return publish(manifest, tarData, OPTS.concat({
245      npmVersion: '6.9.0',
246      username: 'other',
247      email: 'other@idk.tech'
248    })).then(() => {
249      t.ok(true, 'publish succeeded')
250    })
251  })
252})
253
254test('retry after a conflict -- no versions on remote', t => {
255  const REV = '72-47f2986bfd8e8b55068b204588bbf484'
256  const manifest = {
257    name: 'libnpmpublish',
258    version: '1.0.0',
259    description: 'some stuff'
260  }
261  return mockTar({
262    'package.json': JSON.stringify(manifest),
263    'index.js': 'console.log("hello world")'
264  }).then(tarData => {
265    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
266    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
267    const basePackument = {
268      name: 'libnpmpublish',
269      description: 'some stuff',
270      readme: '',
271      _id: 'libnpmpublish'
272    }
273    const currentPackument = cloneDeep(Object.assign({}, basePackument, {
274      maintainers: [{ name: 'zkat', email: 'idk@idk.tech' }]
275    }))
276    const newPackument = cloneDeep(Object.assign({}, basePackument, {
277      'dist-tags': { latest: '1.0.0' },
278      maintainers: [{ name: 'other', email: 'other@idk.tech' }],
279      versions: {
280        '1.0.0': {
281          _id: 'libnpmpublish@1.0.0',
282          _nodeVersion: process.versions.node,
283          _npmVersion: '6.9.0',
284          _npmUser: {
285            name: 'other',
286            email: 'other@idk.tech'
287          },
288          name: 'libnpmpublish',
289          version: '1.0.0',
290          description: 'some stuff',
291          maintainers: [{ name: 'other', email: 'other@idk.tech' }],
292          dist: {
293            shasum,
294            integrity: integrity.toString(),
295            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
296          }
297        }
298      },
299      _attachments: {
300        'libnpmpublish-1.0.0.tgz': {
301          'content_type': 'application/octet-stream',
302          data: tarData.toString('base64'),
303          length: tarData.length
304        }
305      }
306    }))
307    const mergedPackument = cloneDeep(Object.assign({}, basePackument, {
308      'dist-tags': { latest: '1.0.0' },
309      maintainers: currentPackument.maintainers,
310      versions: Object.assign({}, currentPackument.versions, newPackument.versions),
311      _attachments: Object.assign({}, currentPackument._attachments, newPackument._attachments)
312    }))
313    const srv = tnock(t, REG)
314    srv.put('/libnpmpublish', body => {
315      t.notOk(body._rev, 'no _rev in initial post')
316      t.deepEqual(body, newPackument, 'got conflicting packument')
317      return true
318    }).reply(409, { error: 'gimme _rev plz' })
319    srv.get('/libnpmpublish?write=true').reply(200, Object.assign({
320      _rev: REV
321    }, currentPackument))
322    srv.put('/libnpmpublish', body => {
323      t.deepEqual(body, Object.assign({
324        _rev: REV
325      }, mergedPackument), 'posted packument includes _rev and a merged version')
326      return true
327    }).reply(201, {})
328    return publish(manifest, tarData, OPTS.concat({
329      npmVersion: '6.9.0',
330      username: 'other',
331      email: 'other@idk.tech'
332    })).then(() => {
333      t.ok(true, 'publish succeeded')
334    })
335  })
336})
337test('version conflict', t => {
338  const REV = '72-47f2986bfd8e8b55068b204588bbf484'
339  const manifest = {
340    name: 'libnpmpublish',
341    version: '1.0.0',
342    description: 'some stuff'
343  }
344  return mockTar({
345    'package.json': JSON.stringify(manifest),
346    'index.js': 'console.log("hello world")'
347  }).then(tarData => {
348    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
349    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
350    const basePackument = {
351      name: 'libnpmpublish',
352      description: 'some stuff',
353      readme: '',
354      _id: 'libnpmpublish',
355      'dist-tags': {},
356      versions: {},
357      _attachments: {}
358    }
359    const newPackument = cloneDeep(Object.assign({}, basePackument, {
360      'dist-tags': { latest: '1.0.0' },
361      versions: {
362        '1.0.0': {
363          _id: 'libnpmpublish@1.0.0',
364          _nodeVersion: process.versions.node,
365          _npmVersion: '6.9.0',
366          name: 'libnpmpublish',
367          version: '1.0.0',
368          description: 'some stuff',
369          dist: {
370            shasum,
371            integrity: integrity.toString(),
372            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
373          }
374        }
375      },
376      _attachments: {
377        'libnpmpublish-1.0.0.tgz': {
378          'content_type': 'application/octet-stream',
379          data: tarData.toString('base64'),
380          length: tarData.length
381        }
382      }
383    }))
384    const srv = tnock(t, REG)
385    srv.put('/libnpmpublish', body => {
386      t.notOk(body._rev, 'no _rev in initial post')
387      t.deepEqual(body, newPackument, 'got conflicting packument')
388      return true
389    }).reply(409, { error: 'gimme _rev plz' })
390    srv.get('/libnpmpublish?write=true').reply(200, Object.assign({
391      _rev: REV
392    }, newPackument))
393    return publish(manifest, tarData, OPTS.concat({
394      npmVersion: '6.9.0',
395      token: 'deadbeef'
396    })).then(
397      () => { throw new Error('should not succeed') },
398      err => {
399        t.equal(err.code, 'EPUBLISHCONFLICT', 'got publish conflict code')
400      }
401    )
402  })
403})
404
405test('publish with basic auth', t => {
406  const manifest = {
407    name: 'libnpmpublish',
408    version: '1.0.0',
409    description: 'some stuff'
410  }
411  return mockTar({
412    'package.json': JSON.stringify(manifest),
413    'index.js': 'console.log("hello world")'
414  }).then(tarData => {
415    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
416    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
417    const packument = {
418      name: 'libnpmpublish',
419      description: 'some stuff',
420      readme: '',
421      _id: 'libnpmpublish',
422      'dist-tags': {
423        latest: '1.0.0'
424      },
425      maintainers: [{
426        name: 'zkat',
427        email: 'kat@example.tech'
428      }],
429      versions: {
430        '1.0.0': {
431          _id: 'libnpmpublish@1.0.0',
432          _nodeVersion: process.versions.node,
433          _npmVersion: '6.9.0',
434          _npmUser: {
435            name: 'zkat',
436            email: 'kat@example.tech'
437          },
438          maintainers: [{
439            name: 'zkat',
440            email: 'kat@example.tech'
441          }],
442          name: 'libnpmpublish',
443          version: '1.0.0',
444          description: 'some stuff',
445          dist: {
446            shasum,
447            integrity: integrity.toString(),
448            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
449          }
450        }
451      },
452      _attachments: {
453        'libnpmpublish-1.0.0.tgz': {
454          'content_type': 'application/octet-stream',
455          data: tarData.toString('base64'),
456          length: tarData.length
457        }
458      }
459    }
460    const srv = tnock(t, REG)
461    srv.put('/libnpmpublish', body => {
462      t.deepEqual(body, packument, 'posted packument matches expectations')
463      return true
464    }, {
465      authorization: /^Basic /
466    }).reply(201, {})
467
468    return publish(manifest, tarData, OPTS.concat({
469      npmVersion: '6.9.0',
470      username: 'zkat',
471      email: 'kat@example.tech'
472    })).then(() => {
473      t.ok(true, 'publish succeeded')
474    })
475  })
476})
477
478test('publish base64 string', t => {
479  const manifest = {
480    name: 'libnpmpublish',
481    version: '1.0.0',
482    description: 'some stuff'
483  }
484  return mockTar({
485    'package.json': JSON.stringify(manifest),
486    'index.js': 'console.log("hello world")'
487  }).then(tarData => {
488    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
489    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
490    const packument = {
491      name: 'libnpmpublish',
492      description: 'some stuff',
493      readme: '',
494      _id: 'libnpmpublish',
495      'dist-tags': {
496        latest: '1.0.0'
497      },
498      versions: {
499        '1.0.0': {
500          _id: 'libnpmpublish@1.0.0',
501          _nodeVersion: process.versions.node,
502          _npmVersion: '6.9.0',
503          name: 'libnpmpublish',
504          version: '1.0.0',
505          description: 'some stuff',
506          dist: {
507            shasum,
508            integrity: integrity.toString(),
509            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
510          }
511        }
512      },
513      _attachments: {
514        'libnpmpublish-1.0.0.tgz': {
515          'content_type': 'application/octet-stream',
516          data: tarData.toString('base64'),
517          length: tarData.length
518        }
519      }
520    }
521    const srv = tnock(t, REG)
522    srv.put('/libnpmpublish', body => {
523      t.deepEqual(body, packument, 'posted packument matches expectations')
524      return true
525    }, {
526      authorization: 'Bearer deadbeef'
527    }).reply(201, {})
528
529    return publish(manifest, tarData.toString('base64'), OPTS.concat({
530      npmVersion: '6.9.0',
531      token: 'deadbeef'
532    })).then(() => {
533      t.ok(true, 'publish succeeded')
534    })
535  })
536})
537
538test('publish tar stream', t => {
539  const manifest = {
540    name: 'libnpmpublish',
541    version: '1.0.0',
542    description: 'some stuff'
543  }
544  return mockTar({
545    'package.json': JSON.stringify(manifest),
546    'index.js': 'console.log("hello world")'
547  }).then(tarData => {
548    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
549    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
550    const packument = {
551      name: 'libnpmpublish',
552      description: 'some stuff',
553      readme: '',
554      _id: 'libnpmpublish',
555      'dist-tags': {
556        latest: '1.0.0'
557      },
558      versions: {
559        '1.0.0': {
560          _id: 'libnpmpublish@1.0.0',
561          _nodeVersion: process.versions.node,
562          _npmVersion: '6.9.0',
563          name: 'libnpmpublish',
564          version: '1.0.0',
565          description: 'some stuff',
566          dist: {
567            shasum,
568            integrity: integrity.toString(),
569            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
570          }
571        }
572      },
573      _attachments: {
574        'libnpmpublish-1.0.0.tgz': {
575          'content_type': 'application/octet-stream',
576          data: tarData.toString('base64'),
577          length: tarData.length
578        }
579      }
580    }
581    const srv = tnock(t, REG)
582    srv.put('/libnpmpublish', body => {
583      t.deepEqual(body, packument, 'posted packument matches expectations')
584      return true
585    }, {
586      authorization: 'Bearer deadbeef'
587    }).reply(201, {})
588
589    const stream = new PassThrough()
590    setTimeout(() => stream.end(tarData), 0)
591    return publish(manifest, stream, OPTS.concat({
592      npmVersion: '6.9.0',
593      token: 'deadbeef'
594    })).then(() => {
595      t.ok(true, 'publish succeeded')
596    })
597  })
598})
599
600test('refuse if package marked private', t => {
601  const manifest = {
602    name: 'libnpmpublish',
603    version: '1.0.0',
604    description: 'some stuff',
605    private: true
606  }
607  return mockTar({
608    'package.json': JSON.stringify(manifest),
609    'index.js': 'console.log("hello world")'
610  }).then(tarData => {
611    return publish(manifest, tarData, OPTS.concat({
612      npmVersion: '6.9.0',
613      token: 'deadbeef'
614    })).then(
615      () => { throw new Error('should not have succeeded') },
616      err => {
617        t.equal(err.code, 'EPRIVATE', 'got correct error code')
618      }
619    )
620  })
621})
622
623test('publish includes access', t => {
624  const manifest = {
625    name: 'libnpmpublish',
626    version: '1.0.0',
627    description: 'some stuff'
628  }
629  return mockTar({
630    'package.json': JSON.stringify(manifest),
631    'index.js': 'console.log("hello world")'
632  }).then(tarData => {
633    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
634    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
635    const packument = {
636      name: 'libnpmpublish',
637      description: 'some stuff',
638      readme: '',
639      access: 'public',
640      _id: 'libnpmpublish',
641      'dist-tags': {
642        latest: '1.0.0'
643      },
644      versions: {
645        '1.0.0': {
646          _id: 'libnpmpublish@1.0.0',
647          _nodeVersion: process.versions.node,
648          name: 'libnpmpublish',
649          version: '1.0.0',
650          description: 'some stuff',
651          dist: {
652            shasum,
653            integrity: integrity.toString(),
654            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
655          }
656        }
657      },
658      _attachments: {
659        'libnpmpublish-1.0.0.tgz': {
660          'content_type': 'application/octet-stream',
661          data: tarData.toString('base64'),
662          length: tarData.length
663        }
664      }
665    }
666    const srv = tnock(t, REG)
667    srv.put('/libnpmpublish', body => {
668      t.deepEqual(body, packument, 'posted packument matches expectations')
669      return true
670    }, {
671      authorization: 'Bearer deadbeef'
672    }).reply(201, {})
673
674    return publish(manifest, tarData, OPTS.concat({
675      token: 'deadbeef',
676      access: 'public'
677    })).then(() => {
678      t.ok(true, 'publish succeeded')
679    })
680  })
681})
682
683test('refuse if package is unscoped plus `restricted` access', t => {
684  const manifest = {
685    name: 'libnpmpublish',
686    version: '1.0.0',
687    description: 'some stuff'
688  }
689  return mockTar({
690    'package.json': JSON.stringify(manifest),
691    'index.js': 'console.log("hello world")'
692  }).then(tarData => {
693    return publish(manifest, tarData, OPTS.concat({
694      npmVersion: '6.9.0',
695      access: 'restricted'
696    })).then(
697      () => { throw new Error('should not have succeeded') },
698      err => {
699        t.equal(err.code, 'EUNSCOPED', 'got correct error code')
700      }
701    )
702  })
703})
704
705test('refuse if tarball is wrong type', t => {
706  const manifest = {
707    name: 'libnpmpublish',
708    version: '1.0.0',
709    description: 'some stuff'
710  }
711  return publish(manifest, { data: 42 }, OPTS.concat({
712    npmVersion: '6.9.0',
713    token: 'deadbeef'
714  })).then(
715    () => { throw new Error('should not have succeeded') },
716    err => {
717      t.equal(err.code, 'EBADTAR', 'got correct error code')
718    }
719  )
720})
721
722test('refuse if bad semver on manifest', t => {
723  const manifest = {
724    name: 'libnpmpublish',
725    version: 'lmao',
726    description: 'some stuff'
727  }
728  return publish(manifest, 'deadbeef', OPTS).then(
729    () => { throw new Error('should not have succeeded') },
730    err => {
731      t.equal(err.code, 'EBADSEMVER', 'got correct error code')
732    }
733  )
734})
735
736test('other error code', t => {
737  const manifest = {
738    name: 'libnpmpublish',
739    version: '1.0.0',
740    description: 'some stuff'
741  }
742  return mockTar({
743    'package.json': JSON.stringify(manifest),
744    'index.js': 'console.log("hello world")'
745  }).then(tarData => {
746    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
747    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
748    const packument = {
749      name: 'libnpmpublish',
750      description: 'some stuff',
751      readme: '',
752      _id: 'libnpmpublish',
753      'dist-tags': {
754        latest: '1.0.0'
755      },
756      versions: {
757        '1.0.0': {
758          _id: 'libnpmpublish@1.0.0',
759          _nodeVersion: process.versions.node,
760          _npmVersion: '6.9.0',
761          name: 'libnpmpublish',
762          version: '1.0.0',
763          description: 'some stuff',
764          dist: {
765            shasum,
766            integrity: integrity.toString(),
767            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
768          }
769        }
770      },
771      _attachments: {
772        'libnpmpublish-1.0.0.tgz': {
773          'content_type': 'application/octet-stream',
774          data: tarData.toString('base64'),
775          length: tarData.length
776        }
777      }
778    }
779    const srv = tnock(t, REG)
780    srv.put('/libnpmpublish', body => {
781      t.deepEqual(body, packument, 'posted packument matches expectations')
782      return true
783    }, {
784      authorization: 'Bearer deadbeef'
785    }).reply(500, { error: 'go away' })
786
787    return publish(manifest, tarData, OPTS.concat({
788      npmVersion: '6.9.0',
789      token: 'deadbeef'
790    })).then(
791      () => { throw new Error('should not succeed') },
792      err => {
793        t.match(err.message, /go away/, 'no retry on non-409')
794      }
795    )
796  })
797})
798
799test('publish includes access', t => {
800  const manifest = {
801    name: 'libnpmpublish',
802    version: '1.0.0',
803    description: 'some stuff'
804  }
805  return mockTar({
806    'package.json': JSON.stringify(manifest),
807    'index.js': 'console.log("hello world")'
808  }).then(tarData => {
809    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
810    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
811    const packument = {
812      name: 'libnpmpublish',
813      description: 'some stuff',
814      readme: '',
815      access: 'public',
816      _id: 'libnpmpublish',
817      'dist-tags': {
818        latest: '1.0.0'
819      },
820      versions: {
821        '1.0.0': {
822          _id: 'libnpmpublish@1.0.0',
823          _nodeVersion: process.versions.node,
824          name: 'libnpmpublish',
825          version: '1.0.0',
826          description: 'some stuff',
827          dist: {
828            shasum,
829            integrity: integrity.toString(),
830            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
831          }
832        }
833      },
834      _attachments: {
835        'libnpmpublish-1.0.0.tgz': {
836          'content_type': 'application/octet-stream',
837          data: tarData.toString('base64'),
838          length: tarData.length
839        }
840      }
841    }
842    const srv = tnock(t, REG)
843    srv.put('/libnpmpublish', body => {
844      t.deepEqual(body, packument, 'posted packument matches expectations')
845      return true
846    }, {
847      authorization: 'Bearer deadbeef'
848    }).reply(201, {})
849
850    return publish(manifest, tarData, OPTS.concat({
851      token: 'deadbeef',
852      access: 'public'
853    })).then(() => {
854      t.ok(true, 'publish succeeded')
855    })
856  })
857})
858
859test('publishConfig on manifest', t => {
860  const manifest = {
861    name: 'libnpmpublish',
862    version: '1.0.0',
863    description: 'some stuff',
864    publishConfig: {
865      registry: REG
866    }
867  }
868  return mockTar({
869    'package.json': JSON.stringify(manifest),
870    'index.js': 'console.log("hello world")'
871  }).then(tarData => {
872    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
873    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
874    const packument = {
875      name: 'libnpmpublish',
876      description: 'some stuff',
877      readme: '',
878      _id: 'libnpmpublish',
879      'dist-tags': {
880        latest: '1.0.0'
881      },
882      versions: {
883        '1.0.0': {
884          _id: 'libnpmpublish@1.0.0',
885          _nodeVersion: process.versions.node,
886          name: 'libnpmpublish',
887          version: '1.0.0',
888          description: 'some stuff',
889          dist: {
890            shasum,
891            integrity: integrity.toString(),
892            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
893          },
894          publishConfig: {
895            registry: REG
896          }
897        }
898      },
899      _attachments: {
900        'libnpmpublish-1.0.0.tgz': {
901          'content_type': 'application/octet-stream',
902          data: tarData.toString('base64'),
903          length: tarData.length
904        }
905      }
906    }
907    const srv = tnock(t, REG)
908    srv.put('/libnpmpublish', body => {
909      t.deepEqual(body, packument, 'posted packument matches expectations')
910      return true
911    }, {
912      authorization: 'Bearer deadbeef'
913    }).reply(201, {})
914
915    return publish(manifest, tarData, { token: 'deadbeef' }).then(ret => {
916      t.ok(ret, 'publish succeeded')
917    })
918  })
919})
920
921test('publish with encoded _auth', t => {
922  const manifest = {
923    name: 'libnpmpublish',
924    version: '1.0.0',
925    description: 'some stuff'
926  }
927  return mockTar({
928    'package.json': JSON.stringify(manifest),
929    'index.js': 'console.log("hello world")'
930  }).then(tarData => {
931    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
932    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
933    const packument = {
934      name: 'libnpmpublish',
935      description: 'some stuff',
936      readme: '',
937      _id: 'libnpmpublish',
938      'dist-tags': {
939        latest: '1.0.0'
940      },
941      maintainers: [
942        { name: 'myuser', email: 'my@ema.il' }
943      ],
944      versions: {
945        '1.0.0': {
946          _id: 'libnpmpublish@1.0.0',
947          _npmUser: {
948            name: 'myuser',
949            email: 'my@ema.il'
950          },
951          maintainers: [
952            { name: 'myuser', email: 'my@ema.il' }
953          ],
954          _nodeVersion: process.versions.node,
955          name: 'libnpmpublish',
956          version: '1.0.0',
957          description: 'some stuff',
958          dist: {
959            shasum,
960            integrity: integrity.toString(),
961            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
962          }
963        }
964      },
965      _attachments: {
966        'libnpmpublish-1.0.0.tgz': {
967          'content_type': 'application/octet-stream',
968          data: tarData.toString('base64'),
969          length: tarData.length
970        }
971      }
972    }
973    const srv = tnock(t, REG)
974    srv.put('/libnpmpublish', body => {
975      t.deepEqual(body, packument, 'posted packument matches expectations')
976      return true
977    }, {
978      authorization: 'Bearer deadbeef'
979    }).reply(201, {})
980
981    return publish(manifest, tarData, OPTS.concat({
982      _auth: Buffer.from('myuser:mypassword', 'utf8').toString('base64'),
983      email: 'my@ema.il'
984    })).then(ret => {
985      t.ok(ret, 'publish succeeded using _auth')
986    })
987  })
988})
989
990test('publish with 302 redirect', t => {
991  const manifest = {
992    name: 'libnpmpublish',
993    version: '1.0.0',
994    description: 'some stuff'
995  }
996  return mockTar({
997    'package.json': JSON.stringify(manifest),
998    'index.js': 'console.log("hello world")'
999  }).then(tarData => {
1000    const shasum = crypto.createHash('sha1').update(tarData).digest('hex')
1001    const integrity = ssri.fromData(tarData, { algorithms: ['sha512'] })
1002    const packument = {
1003      name: 'libnpmpublish',
1004      description: 'some stuff',
1005      readme: '',
1006      _id: 'libnpmpublish',
1007      'dist-tags': {
1008        latest: '1.0.0'
1009      },
1010      versions: {
1011        '1.0.0': {
1012          _id: 'libnpmpublish@1.0.0',
1013          _nodeVersion: process.versions.node,
1014          name: 'libnpmpublish',
1015          version: '1.0.0',
1016          description: 'some stuff',
1017          dist: {
1018            shasum,
1019            integrity: integrity.toString(),
1020            tarball: `http://mock.reg/libnpmpublish/-/libnpmpublish-1.0.0.tgz`
1021          }
1022        }
1023      },
1024      _attachments: {
1025        'libnpmpublish-1.0.0.tgz': {
1026          'content_type': 'application/octet-stream',
1027          data: tarData.toString('base64'),
1028          length: tarData.length
1029        }
1030      }
1031    }
1032    tnock(t, REG).put('/libnpmpublish').reply(302, '', {
1033      location: 'http://blah.net/libnpmpublish'
1034    })
1035    tnock(t, 'http://blah.net').put('/libnpmpublish', body => {
1036      t.deepEqual(body, packument, 'posted packument matches expectations')
1037      return true
1038    }, {
1039      authorization: 'Bearer deadbeef'
1040    }).reply(201, {})
1041
1042    return publish(manifest, tarData, OPTS.concat({
1043      token: 'deadbeef'
1044    })).then(ret => {
1045      t.ok(ret, 'publish succeeded')
1046    })
1047  })
1048})
1049