xref: /third_party/node/deps/npm/test/lib/commands/hook.js (revision 1cb0ef41)
1const t = require('tap')
2const mockNpm = require('../../fixtures/mock-npm')
3const { stripVTControlCharacters } = require('node:util')
4
5const mockHook = async (t, { hookResponse, ...npmOpts } = {}) => {
6  const now = Date.now()
7
8  let hookArgs = null
9
10  const pkgTypes = {
11    semver: 'package',
12    '@npmcli': 'scope',
13    npm: 'owner',
14  }
15
16  const libnpmhook = {
17    add: async (pkg, uri, secret, opts) => {
18      hookArgs = { pkg, uri, secret, opts }
19      return { id: 1, name: pkg, type: pkgTypes[pkg], endpoint: uri }
20    },
21    ls: async opts => {
22      hookArgs = opts
23      let id = 0
24      if (hookResponse) {
25        return hookResponse
26      }
27
28      return Object.keys(pkgTypes).map(name => ({
29        id: ++id,
30        name,
31        type: pkgTypes[name],
32        endpoint: 'https://google.com',
33        last_delivery: id % 2 === 0 ? now : undefined,
34      }))
35    },
36    rm: async (id, opts) => {
37      hookArgs = { id, opts }
38      const pkg = Object.keys(pkgTypes)[0]
39      return {
40        id: 1,
41        name: pkg,
42        type: pkgTypes[pkg],
43        endpoint: 'https://google.com',
44      }
45    },
46    update: async (id, uri, secret, opts) => {
47      hookArgs = { id, uri, secret, opts }
48      const pkg = Object.keys(pkgTypes)[0]
49      return { id, name: pkg, type: pkgTypes[pkg], endpoint: uri }
50    },
51  }
52
53  const mock = await mockNpm(t, {
54    ...npmOpts,
55    command: 'hook',
56    mocks: {
57      libnpmhook,
58      ...npmOpts.mocks,
59    },
60  })
61
62  return {
63    ...mock,
64    now,
65    hookArgs: () => hookArgs,
66  }
67}
68
69t.test('npm hook no args', async t => {
70  const { hook } = await mockHook(t)
71  await t.rejects(hook.exec([]), hook.usage, 'throws usage with no arguments')
72})
73
74t.test('npm hook add', async t => {
75  const { npm, hook, outputs, hookArgs } = await mockHook(t)
76  await hook.exec(['add', 'semver', 'https://google.com', 'some-secret'])
77
78  t.match(
79    hookArgs(),
80    {
81      pkg: 'semver',
82      uri: 'https://google.com',
83      secret: 'some-secret',
84      opts: npm.flatOptions,
85    },
86    'provided the correct arguments to libnpmhook'
87  )
88  t.strictSame(outputs[0], ['+ semver  ->  https://google.com'], 'prints the correct output')
89})
90
91t.test('npm hook add - correct owner hook output', async t => {
92  const { npm, hook, outputs, hookArgs } = await mockHook(t)
93  await hook.exec(['add', '~npm', 'https://google.com', 'some-secret'])
94
95  t.match(
96    hookArgs(),
97    {
98      pkg: '~npm',
99      uri: 'https://google.com',
100      secret: 'some-secret',
101      opts: npm.flatOptions,
102    },
103    'provided the correct arguments to libnpmhook'
104  )
105  t.strictSame(outputs[0], ['+ ~npm  ->  https://google.com'], 'prints the correct output')
106})
107
108t.test('npm hook add - correct scope hook output', async t => {
109  const { npm, hook, outputs, hookArgs } = await mockHook(t)
110  await hook.exec(['add', '@npmcli', 'https://google.com', 'some-secret'])
111
112  t.match(
113    hookArgs(),
114    {
115      pkg: '@npmcli',
116      uri: 'https://google.com',
117      secret: 'some-secret',
118      opts: npm.flatOptions,
119    },
120    'provided the correct arguments to libnpmhook'
121  )
122  t.strictSame(outputs[0], ['+ @npmcli  ->  https://google.com'], 'prints the correct output')
123})
124
125t.test('npm hook add - unicode output', async t => {
126  const config = {
127    unicode: true,
128  }
129  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
130    config,
131  })
132
133  await hook.exec(['add', 'semver', 'https://google.com', 'some-secret'])
134
135  t.match(
136    hookArgs(),
137    {
138      pkg: 'semver',
139      uri: 'https://google.com',
140      secret: 'some-secret',
141      opts: npm.flatOptions,
142    },
143    'provided the correct arguments to libnpmhook'
144  )
145  t.strictSame(outputs[0], ['+ semver  ➜  https://google.com'], 'prints the correct output')
146})
147
148t.test('npm hook add - json output', async t => {
149  const config = {
150    json: true,
151  }
152  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
153    config,
154  })
155
156  await hook.exec(['add', '@npmcli', 'https://google.com', 'some-secret'])
157
158  t.match(
159    hookArgs(),
160    {
161      pkg: '@npmcli',
162      uri: 'https://google.com',
163      secret: 'some-secret',
164      opts: npm.flatOptions,
165    },
166    'provided the correct arguments to libnpmhook'
167  )
168  t.strictSame(
169    JSON.parse(outputs[0][0]),
170    {
171      id: 1,
172      name: '@npmcli',
173      endpoint: 'https://google.com',
174      type: 'scope',
175    },
176    'prints the correct json output'
177  )
178})
179
180t.test('npm hook add - parseable output', async t => {
181  const config = {
182    parseable: true,
183  }
184  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
185    config,
186  })
187
188  await hook.exec(['add', '@npmcli', 'https://google.com', 'some-secret'])
189
190  t.match(
191    hookArgs(),
192    {
193      pkg: '@npmcli',
194      uri: 'https://google.com',
195      secret: 'some-secret',
196      opts: npm.flatOptions,
197    },
198    'provided the correct arguments to libnpmhook'
199  )
200
201  t.strictSame(
202    outputs[0][0].split(/\t/),
203    ['id', 'name', 'type', 'endpoint'],
204    'prints the correct parseable output headers'
205  )
206  t.strictSame(
207    outputs[1][0].split(/\t/),
208    ['1', '@npmcli', 'scope', 'https://google.com'],
209    'prints the correct parseable values'
210  )
211})
212
213t.test('npm hook add - silent output', async t => {
214  const config = { loglevel: 'silent' }
215  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
216    config,
217  })
218
219  await hook.exec(['add', '@npmcli', 'https://google.com', 'some-secret'])
220
221  t.match(
222    hookArgs(),
223    {
224      pkg: '@npmcli',
225      uri: 'https://google.com',
226      secret: 'some-secret',
227      opts: npm.flatOptions,
228    },
229    'provided the correct arguments to libnpmhook'
230  )
231  t.strictSame(outputs, [], 'printed no output')
232})
233
234t.test('npm hook ls', async t => {
235  const { npm, hook, outputs, hookArgs } = await mockHook(t)
236  await hook.exec(['ls'])
237
238  t.match(
239    hookArgs(),
240    {
241      ...npm.flatOptions,
242      package: undefined,
243    },
244    'received the correct arguments'
245  )
246  t.equal(outputs[0][0], 'You have 3 hooks configured.', 'prints the correct header')
247  const out = stripVTControlCharacters(outputs[1][0])
248  t.match(out, /semver.*https:\/\/google.com.*\n.*\n.*never triggered/, 'prints package hook')
249  t.match(out, /@npmcli.*https:\/\/google.com.*\n.*\n.*triggered just now/, 'prints scope hook')
250  t.match(out, /~npm.*https:\/\/google.com.*\n.*\n.*never triggered/, 'prints owner hook')
251})
252
253t.test('npm hook ls, no results', async t => {
254  const hookResponse = []
255  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
256    hookResponse,
257  })
258
259  await hook.exec(['ls'])
260
261  t.match(
262    hookArgs(),
263    {
264      ...npm.flatOptions,
265      package: undefined,
266    },
267    'received the correct arguments'
268  )
269  t.equal(outputs[0][0], "You don't have any hooks configured yet.", 'prints the correct result')
270})
271
272t.test('npm hook ls, single result', async t => {
273  const hookResponse = [
274    {
275      id: 1,
276      name: 'semver',
277      type: 'package',
278      endpoint: 'https://google.com',
279    },
280  ]
281  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
282    hookResponse,
283  })
284
285  await hook.exec(['ls'])
286
287  t.match(
288    hookArgs(),
289    {
290      ...npm.flatOptions,
291      package: undefined,
292    },
293    'received the correct arguments'
294  )
295  t.equal(outputs[0][0], 'You have one hook configured.', 'prints the correct header')
296  const out = stripVTControlCharacters(outputs[1][0])
297  t.match(out, /semver.*https:\/\/google.com.*\n.*\n.*never triggered/, 'prints package hook')
298})
299
300t.test('npm hook ls - json output', async t => {
301  const config = {
302    json: true,
303  }
304  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
305    config,
306  })
307
308  await hook.exec(['ls'])
309
310  t.match(
311    hookArgs(),
312    {
313      ...npm.flatOptions,
314      package: undefined,
315    },
316    'received the correct arguments'
317  )
318  const out = JSON.parse(outputs[0])
319  t.match(
320    out,
321    [
322      {
323        id: 1,
324        name: 'semver',
325        type: 'package',
326        endpoint: 'https://google.com',
327      },
328      {
329        id: 2,
330        name: 'npmcli',
331        type: 'scope',
332        endpoint: 'https://google.com',
333      },
334      {
335        id: 3,
336        name: 'npm',
337        type: 'owner',
338        endpoint: 'https://google.com',
339      },
340    ],
341    'prints the correct output'
342  )
343})
344
345t.test('npm hook ls - parseable output', async t => {
346  const config = {
347    parseable: true,
348  }
349  const { npm, hook, outputs, hookArgs, now } = await mockHook(t, {
350    config,
351  })
352
353  await hook.exec(['ls'])
354
355  t.match(
356    hookArgs(),
357    {
358      ...npm.flatOptions,
359      package: undefined,
360    },
361    'received the correct arguments'
362  )
363  t.strictSame(
364    outputs.map(line => line[0].split(/\t/)),
365    [
366      ['id', 'name', 'type', 'endpoint', 'last_delivery'],
367      ['1', 'semver', 'package', 'https://google.com', ''],
368      ['2', '@npmcli', 'scope', 'https://google.com', `${now}`],
369      ['3', 'npm', 'owner', 'https://google.com', ''],
370    ],
371    'prints the correct result'
372  )
373})
374
375t.test('npm hook ls - silent output', async t => {
376  const config = { loglevel: 'silent' }
377  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
378    config,
379  })
380
381  await hook.exec(['ls'])
382
383  t.match(
384    hookArgs(),
385    {
386      ...npm.flatOptions,
387      package: undefined,
388    },
389    'received the correct arguments'
390  )
391  t.strictSame(outputs, [], 'printed no output')
392})
393
394t.test('npm hook rm', async t => {
395  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
396  })
397  await hook.exec(['rm', '1'])
398
399  t.match(
400    hookArgs(),
401    {
402      id: '1',
403      opts: npm.flatOptions,
404    },
405    'received the correct arguments'
406  )
407  t.strictSame(outputs[0], ['- semver  X  https://google.com'], 'printed the correct output')
408})
409
410t.test('npm hook rm - unicode output', async t => {
411  const config = {
412    unicode: true,
413  }
414  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
415    config,
416  })
417
418  await hook.exec(['rm', '1'])
419
420  t.match(
421    hookArgs(),
422    {
423      id: '1',
424      opts: npm.flatOptions,
425    },
426    'received the correct arguments'
427  )
428  t.strictSame(outputs[0], ['- semver  ✘  https://google.com'], 'printed the correct output')
429})
430
431t.test('npm hook rm - silent output', async t => {
432  const config = { loglevel: 'silent' }
433  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
434    config,
435  })
436
437  await hook.exec(['rm', '1'])
438
439  t.match(
440    hookArgs(),
441    {
442      id: '1',
443      opts: npm.flatOptions,
444    },
445    'received the correct arguments'
446  )
447  t.strictSame(outputs, [], 'printed no output')
448})
449
450t.test('npm hook rm - json output', async t => {
451  const config = {
452    json: true,
453  }
454  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
455    config,
456  })
457
458  await hook.exec(['rm', '1'])
459
460  t.match(
461    hookArgs(),
462    {
463      id: '1',
464      opts: npm.flatOptions,
465    },
466    'received the correct arguments'
467  )
468  t.strictSame(
469    JSON.parse(outputs[0]),
470    {
471      id: 1,
472      name: 'semver',
473      type: 'package',
474      endpoint: 'https://google.com',
475    },
476    'printed correct output'
477  )
478})
479
480t.test('npm hook rm - parseable output', async t => {
481  const config = {
482    parseable: true,
483  }
484  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
485    config,
486  })
487
488  await hook.exec(['rm', '1'])
489
490  t.match(
491    hookArgs(),
492    {
493      id: '1',
494      opts: npm.flatOptions,
495    },
496    'received the correct arguments'
497  )
498  t.strictSame(
499    outputs.map(line => line[0].split(/\t/)),
500    [
501      ['id', 'name', 'type', 'endpoint'],
502      ['1', 'semver', 'package', 'https://google.com'],
503    ],
504    'printed correct output'
505  )
506})
507
508t.test('npm hook update', async t => {
509  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
510  })
511  await hook.exec(['update', '1', 'https://google.com', 'some-secret'])
512
513  t.match(
514    hookArgs(),
515    {
516      id: '1',
517      uri: 'https://google.com',
518      secret: 'some-secret',
519      opts: npm.flatOptions,
520    },
521    'received the correct arguments'
522  )
523  t.strictSame(outputs[0], ['+ semver  ->  https://google.com'], 'printed the correct output')
524})
525
526t.test('npm hook update - unicode', async t => {
527  const config = {
528    unicode: true,
529  }
530  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
531    config,
532  })
533
534  await hook.exec(['update', '1', 'https://google.com', 'some-secret'])
535
536  t.match(
537    hookArgs(),
538    {
539      id: '1',
540      uri: 'https://google.com',
541      secret: 'some-secret',
542      opts: npm.flatOptions,
543    },
544    'received the correct arguments'
545  )
546  t.strictSame(outputs[0], ['+ semver  ➜  https://google.com'], 'printed the correct output')
547})
548
549t.test('npm hook update - json output', async t => {
550  const config = {
551    json: true,
552  }
553  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
554    config,
555  })
556
557  await hook.exec(['update', '1', 'https://google.com', 'some-secret'])
558
559  t.match(
560    hookArgs(),
561    {
562      id: '1',
563      uri: 'https://google.com',
564      secret: 'some-secret',
565      opts: npm.flatOptions,
566    },
567    'received the correct arguments'
568  )
569  t.strictSame(
570    JSON.parse(outputs[0]),
571    {
572      id: '1',
573      name: 'semver',
574      type: 'package',
575      endpoint: 'https://google.com',
576    },
577    'printed the correct output'
578  )
579})
580
581t.test('npm hook update - parseable output', async t => {
582  const config = {
583    parseable: true,
584  }
585  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
586    config,
587  })
588
589  await hook.exec(['update', '1', 'https://google.com', 'some-secret'])
590
591  t.match(
592    hookArgs(),
593    {
594      id: '1',
595      uri: 'https://google.com',
596      secret: 'some-secret',
597      opts: npm.flatOptions,
598    },
599    'received the correct arguments'
600  )
601  t.strictSame(
602    outputs.map(line => line[0].split(/\t/)),
603    [
604      ['id', 'name', 'type', 'endpoint'],
605      ['1', 'semver', 'package', 'https://google.com'],
606    ],
607    'printed the correct output'
608  )
609})
610
611t.test('npm hook update - silent output', async t => {
612  const config = { loglevel: 'silent' }
613  const { npm, hook, outputs, hookArgs } = await mockHook(t, {
614    config,
615  })
616
617  await hook.exec(['update', '1', 'https://google.com', 'some-secret'])
618
619  t.match(
620    hookArgs(),
621    {
622      id: '1',
623      uri: 'https://google.com',
624      secret: 'some-secret',
625      opts: npm.flatOptions,
626    },
627    'received the correct arguments'
628  )
629  t.strictSame(outputs, [], 'printed no output')
630})
631