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