1const t = require('tap') 2const { load: loadMockNpm } = require('../../fixtures/mock-npm') 3 4const MockRegistry = require('@npmcli/mock-registry') 5const user = 'test-user' 6const pkg = 'test-package' 7const auth = { '//registry.npmjs.org/:_authToken': 'test-auth-token' } 8 9t.test('no args --force success', async t => { 10 const { joinedOutput, npm } = await loadMockNpm(t, { 11 config: { 12 force: true, 13 ...auth, 14 }, 15 prefixDir: { 16 'package.json': JSON.stringify({ 17 name: pkg, 18 version: '1.0.0', 19 }, null, 2), 20 }, 21 }) 22 23 const registry = new MockRegistry({ 24 tap: t, 25 registry: npm.config.get('registry'), 26 authorization: 'test-auth-token', 27 }) 28 const manifest = registry.manifest({ name: pkg }) 29 await registry.package({ manifest, query: { write: true }, times: 2 }) 30 registry.unpublish({ manifest }) 31 await npm.exec('unpublish', []) 32 t.equal(joinedOutput(), '- test-package') 33}) 34 35t.test('no args --force missing package.json', async t => { 36 const { npm } = await loadMockNpm(t, { 37 config: { 38 force: true, 39 }, 40 }) 41 42 await t.rejects( 43 npm.exec('unpublish', []), 44 { code: 'EUSAGE' }, 45 'should throw usage instructions on missing package.json' 46 ) 47}) 48 49t.test('no args --force error reading package.json', async t => { 50 const { npm } = await loadMockNpm(t, { 51 config: { 52 force: true, 53 }, 54 prefixDir: { 55 'package.json': '{ not valid json ]', 56 }, 57 }) 58 59 await t.rejects( 60 npm.exec('unpublish', []), 61 /Invalid package.json/, 62 'should throw error from reading package.json' 63 ) 64}) 65 66t.test('with args --force error reading package.json', async t => { 67 const { npm } = await loadMockNpm(t, { 68 config: { 69 force: true, 70 }, 71 prefixDir: { 72 'package.json': '{ not valid json ]', 73 }, 74 }) 75 76 await t.rejects( 77 npm.exec('unpublish', [pkg]), 78 /Invalid package.json/, 79 'should throw error from reading package.json' 80 ) 81}) 82 83t.test('no force entire project', async t => { 84 const { npm } = await loadMockNpm(t) 85 86 await t.rejects( 87 npm.exec('unpublish', ['@npmcli/unpublish-test']), 88 /Refusing to delete entire project/ 89 ) 90}) 91 92t.test('too many args', async t => { 93 const { npm } = await loadMockNpm(t) 94 95 await t.rejects( 96 npm.exec('unpublish', ['a', 'b']), 97 { code: 'EUSAGE' }, 98 'should throw usage instructions if too many args' 99 ) 100}) 101 102t.test('range', async t => { 103 const { npm } = await loadMockNpm(t) 104 105 await t.rejects( 106 npm.exec('unpublish', ['a@>1.0.0']), 107 { code: 'EUSAGE' }, 108 /single version/ 109 ) 110}) 111 112t.test('tag', async t => { 113 const { npm } = await loadMockNpm(t) 114 115 await t.rejects( 116 npm.exec('unpublish', ['a@>1.0.0']), 117 { code: 'EUSAGE' }, 118 /single version/ 119 ) 120}) 121 122t.test('unpublish <pkg>@version not the last version', async t => { 123 const { joinedOutput, npm } = await loadMockNpm(t, { 124 config: { 125 force: true, 126 ...auth, 127 }, 128 }) 129 const registry = new MockRegistry({ 130 tap: t, 131 registry: npm.config.get('registry'), 132 authorization: 'test-auth-token', 133 }) 134 const manifest = registry.manifest({ 135 name: pkg, 136 packuments: ['1.0.0', '1.0.1'], 137 }) 138 await registry.package({ manifest, query: { write: true }, times: 3 }) 139 registry.nock.put(`/${pkg}/-rev/${manifest._rev}`, body => { 140 // sets latest and deletes version 1.0.1 141 return body['dist-tags'].latest === '1.0.0' && body.versions['1.0.1'] === undefined 142 }).reply(201) 143 .intercept(`/${pkg}/-/${pkg}-1.0.1.tgz/-rev/${manifest._rev}`, 'DELETE').reply(201) 144 145 await npm.exec('unpublish', ['test-package@1.0.1']) 146 t.equal(joinedOutput(), '- test-package@1.0.1') 147}) 148 149t.test('unpublish <pkg>@version last version', async t => { 150 const { npm } = await loadMockNpm(t, { 151 config: { 152 ...auth, 153 }, 154 }) 155 const registry = new MockRegistry({ 156 tap: t, 157 registry: npm.config.get('registry'), 158 authorization: 'test-auth-token', 159 }) 160 const manifest = registry.manifest({ name: pkg }) 161 await registry.package({ manifest, query: { write: true } }) 162 163 await t.rejects( 164 npm.exec('unpublish', ['test-package@1.0.0']), 165 /Refusing to delete the last version of the package/ 166 ) 167}) 168 169t.test('no version found in package.json no force', async t => { 170 const { npm } = await loadMockNpm(t, { 171 config: { 172 ...auth, 173 }, 174 prefixDir: { 175 'package.json': JSON.stringify({ 176 name: pkg, 177 }, null, 2), 178 }, 179 }) 180 await t.rejects( 181 npm.exec('unpublish', []), 182 /Refusing to delete entire project/ 183 ) 184}) 185 186t.test('no version found in package.json with force', async t => { 187 const { joinedOutput, npm } = await loadMockNpm(t, { 188 config: { 189 force: true, 190 ...auth, 191 }, 192 prefixDir: { 193 'package.json': JSON.stringify({ 194 name: pkg, 195 }, null, 2), 196 }, 197 }) 198 const registry = new MockRegistry({ 199 tap: t, 200 registry: npm.config.get('registry'), 201 authorization: 'test-auth-token', 202 }) 203 const manifest = registry.manifest({ name: pkg }) 204 await registry.package({ manifest, query: { write: true }, times: 2 }) 205 registry.unpublish({ manifest }) 206 207 await npm.exec('unpublish', []) 208 t.equal(joinedOutput(), '- test-package') 209}) 210 211t.test('unpublish <pkg> --force no version set', async t => { 212 const { joinedOutput, npm } = await loadMockNpm(t, { 213 config: { 214 force: true, 215 ...auth, 216 }, 217 }) 218 const registry = new MockRegistry({ 219 tap: t, 220 registry: npm.config.get('registry'), 221 authorization: 'test-auth-token', 222 }) 223 const manifest = registry.manifest({ name: pkg }) 224 await registry.package({ manifest, query: { write: true }, times: 2 }) 225 registry.unpublish({ manifest }) 226 227 await npm.exec('unpublish', ['test-package']) 228 t.equal(joinedOutput(), '- test-package') 229}) 230 231t.test('silent', async t => { 232 const { joinedOutput, npm } = await loadMockNpm(t, { 233 config: { 234 force: true, 235 loglevel: 'silent', 236 ...auth, 237 }, 238 }) 239 const registry = new MockRegistry({ 240 tap: t, 241 registry: npm.config.get('registry'), 242 authorization: 'test-auth-token', 243 }) 244 const manifest = registry.manifest({ 245 name: pkg, 246 packuments: ['1.0.0', '1.0.1'], 247 }) 248 await registry.package({ manifest, query: { write: true }, times: 3 }) 249 registry.nock.put(`/${pkg}/-rev/${manifest._rev}`, body => { 250 // sets latest and deletes version 1.0.1 251 return body['dist-tags'].latest === '1.0.0' && body.versions['1.0.1'] === undefined 252 }).reply(201) 253 .delete(`/${pkg}/-/${pkg}-1.0.1.tgz/-rev/${manifest._rev}`).reply(201) 254 255 await npm.exec('unpublish', ['test-package@1.0.1']) 256 t.equal(joinedOutput(), '') 257}) 258 259t.test('workspaces', async t => { 260 const prefixDir = { 261 'package.json': JSON.stringify({ 262 name: pkg, 263 version: '1.0.0', 264 workspaces: ['workspace-a', 'workspace-b', 'workspace-c'], 265 }, null, 2), 266 'workspace-a': { 267 'package.json': JSON.stringify({ 268 name: 'workspace-a', 269 version: '1.2.3-a', 270 repository: 'http://repo.workspace-a/', 271 }), 272 }, 273 'workspace-b': { 274 'package.json': JSON.stringify({ 275 name: 'workspace-b', 276 version: '1.2.3-b', 277 repository: 'https://github.com/npm/workspace-b', 278 }), 279 }, 280 'workspace-c': { 281 'package.json': JSON.stringify({ 282 name: 'workspace-n', 283 version: '1.2.3-n', 284 }), 285 }, 286 } 287 288 t.test('with package name no force', async t => { 289 const { npm } = await loadMockNpm(t, { 290 config: { 291 workspace: ['workspace-a'], 292 }, 293 prefixDir, 294 }) 295 await t.rejects( 296 npm.exec('unpublish', ['workspace-a']), 297 /Refusing to delete entire project/ 298 ) 299 }) 300 301 t.test('all workspaces last version --force', async t => { 302 const { joinedOutput, npm } = await loadMockNpm(t, { 303 config: { 304 workspaces: true, 305 force: true, 306 ...auth, 307 }, 308 prefixDir, 309 }) 310 const registry = new MockRegistry({ 311 tap: t, 312 registry: npm.config.get('registry'), 313 authorization: 'test-auth-token', 314 }) 315 const manifestA = registry.manifest({ name: 'workspace-a', versions: ['1.2.3-a'] }) 316 const manifestB = registry.manifest({ name: 'workspace-b', versions: ['1.2.3-b'] }) 317 const manifestN = registry.manifest({ name: 'workspace-n', versions: ['1.2.3-n'] }) 318 await registry.package({ manifest: manifestA, query: { write: true }, times: 2 }) 319 await registry.package({ manifest: manifestB, query: { write: true }, times: 2 }) 320 await registry.package({ manifest: manifestN, query: { write: true }, times: 2 }) 321 registry.nock.delete(`/workspace-a/-rev/${manifestA._rev}`).reply(201) 322 .delete(`/workspace-b/-rev/${manifestB._rev}`).reply(201) 323 .delete(`/workspace-n/-rev/${manifestN._rev}`).reply(201) 324 325 await npm.exec('unpublish', []) 326 t.equal(joinedOutput(), '- workspace-a\n- workspace-b\n- workspace-n') 327 }) 328}) 329 330t.test('dryRun with spec', async t => { 331 const { joinedOutput, npm } = await loadMockNpm(t, { 332 config: { 333 'dry-run': true, 334 ...auth, 335 }, 336 }) 337 const registry = new MockRegistry({ 338 tap: t, 339 registry: npm.config.get('registry'), 340 authorization: 'test-auth-token', 341 }) 342 const manifest = registry.manifest({ 343 name: pkg, 344 packuments: ['1.0.0', '1.0.1'], 345 }) 346 await registry.package({ manifest, query: { write: true } }) 347 348 await npm.exec('unpublish', ['test-package@1.0.1']) 349 t.equal(joinedOutput(), '- test-package@1.0.1') 350}) 351 352t.test('dryRun with no args', async t => { 353 const { joinedOutput, npm } = await loadMockNpm(t, { 354 config: { 355 force: true, 356 'dry-run': true, 357 ...auth, 358 }, 359 prefixDir: { 360 'package.json': JSON.stringify({ 361 name: pkg, 362 version: '1.0.0', 363 }, null, 2), 364 }, 365 }) 366 const registry = new MockRegistry({ 367 tap: t, 368 registry: npm.config.get('registry'), 369 authorization: 'test-auth-token', 370 }) 371 const manifest = registry.manifest({ 372 name: pkg, 373 packuments: ['1.0.0', '1.0.1'], 374 }) 375 await registry.package({ manifest, query: { write: true } }) 376 377 await npm.exec('unpublish', []) 378 t.equal(joinedOutput(), '- test-package@1.0.0') 379}) 380 381t.test('publishConfig no spec', async t => { 382 const alternateRegistry = 'https://other.registry.npmjs.org' 383 const { joinedOutput, npm } = await loadMockNpm(t, { 384 config: { 385 force: true, 386 '//other.registry.npmjs.org/:_authToken': 'test-other-token', 387 }, 388 prefixDir: { 389 'package.json': JSON.stringify({ 390 name: pkg, 391 version: '1.0.0', 392 publishConfig: { 393 registry: alternateRegistry, 394 }, 395 }, null, 2), 396 }, 397 }) 398 399 const registry = new MockRegistry({ 400 tap: t, 401 registry: alternateRegistry, 402 authorization: 'test-other-token', 403 }) 404 const manifest = registry.manifest({ name: pkg }) 405 await registry.package({ manifest, query: { write: true }, times: 2 }) 406 registry.unpublish({ manifest }) 407 await npm.exec('unpublish', []) 408 t.equal(joinedOutput(), '- test-package') 409}) 410 411t.test('publishConfig with spec', async t => { 412 const alternateRegistry = 'https://other.registry.npmjs.org' 413 const { joinedOutput, npm } = await loadMockNpm(t, { 414 config: { 415 force: true, 416 '//other.registry.npmjs.org/:_authToken': 'test-other-token', 417 }, 418 prefixDir: { 419 'package.json': JSON.stringify({ 420 name: pkg, 421 version: '1.0.0', 422 publishConfig: { 423 registry: alternateRegistry, 424 }, 425 }, null, 2), 426 }, 427 }) 428 429 const registry = new MockRegistry({ 430 tap: t, 431 registry: alternateRegistry, 432 authorization: 'test-other-token', 433 }) 434 const manifest = registry.manifest({ name: pkg }) 435 await registry.package({ manifest, query: { write: true }, times: 2 }) 436 registry.unpublish({ manifest }) 437 await npm.exec('unpublish', ['test-package']) 438 t.equal(joinedOutput(), '- test-package') 439}) 440 441t.test('scoped registry config', async t => { 442 const scopedPkg = `@npm/test-package` 443 const alternateRegistry = 'https://other.registry.npmjs.org' 444 const { npm } = await loadMockNpm(t, { 445 config: { 446 force: true, 447 '@npm:registry': alternateRegistry, 448 '//other.registry.npmjs.org/:_authToken': 'test-other-token', 449 }, 450 prefixDir: { 451 'package.json': JSON.stringify({ 452 name: pkg, 453 version: '1.0.0', 454 publishConfig: { 455 registry: alternateRegistry, 456 }, 457 }, null, 2), 458 }, 459 }) 460 const registry = new MockRegistry({ 461 tap: t, 462 registry: alternateRegistry, 463 authorization: 'test-other-token', 464 }) 465 const manifest = registry.manifest({ name: scopedPkg }) 466 await registry.package({ manifest, query: { write: true }, times: 2 }) 467 registry.unpublish({ manifest }) 468 await npm.exec('unpublish', [scopedPkg]) 469}) 470 471t.test('completion', async t => { 472 const { npm, unpublish } = await loadMockNpm(t, { 473 command: 'unpublish', 474 config: { 475 ...auth, 476 }, 477 }) 478 479 const testComp = 480 async (t, { argv, partialWord, expect, title }) => { 481 const res = await unpublish.completion( 482 { conf: { argv: { remain: argv } }, partialWord } 483 ) 484 t.strictSame(res, expect, title || argv.join(' ')) 485 } 486 487 t.test('completing with multiple versions from the registry', async t => { 488 const registry = new MockRegistry({ 489 tap: t, 490 registry: npm.config.get('registry'), 491 authorization: 'test-auth-token', 492 }) 493 const manifest = registry.manifest({ 494 name: pkg, 495 packuments: ['1.0.0', '1.0.1'], 496 }) 497 await registry.package({ manifest, query: { write: true } }) 498 registry.whoami({ username: user }) 499 registry.getPackages({ team: user, packages: { [pkg]: 'write' } }) 500 501 await testComp(t, { 502 argv: ['npm', 'unpublish'], 503 partialWord: 'test-package', 504 expect: [ 505 'test-package@1.0.0', 506 'test-package@1.0.1', 507 ], 508 }) 509 }) 510 511 t.test('no versions retrieved', async t => { 512 const registry = new MockRegistry({ 513 tap: t, 514 registry: npm.config.get('registry'), 515 authorization: 'test-auth-token', 516 }) 517 const manifest = registry.manifest({ name: pkg }) 518 manifest.versions = {} 519 await registry.package({ manifest, query: { write: true } }) 520 registry.whoami({ username: user }) 521 registry.getPackages({ team: user, packages: { [pkg]: 'write' } }) 522 523 await testComp(t, { 524 argv: ['npm', 'unpublish'], 525 partialWord: pkg, 526 expect: [ 527 pkg, 528 ], 529 title: 'should autocomplete package name only', 530 }) 531 }) 532 533 t.test('packages starting with same letters', async t => { 534 const registry = new MockRegistry({ 535 tap: t, 536 registry: npm.config.get('registry'), 537 authorization: 'test-auth-token', 538 }) 539 registry.whoami({ username: user }) 540 registry.getPackages({ team: user, 541 packages: { 542 [pkg]: 'write', 543 [`${pkg}a`]: 'write', 544 [`${pkg}b`]: 'write', 545 } }) 546 547 await testComp(t, { 548 argv: ['npm', 'unpublish'], 549 partialWord: pkg, 550 expect: [ 551 pkg, 552 `${pkg}a`, 553 `${pkg}b`, 554 ], 555 }) 556 }) 557 558 t.test('no packages retrieved', async t => { 559 const registry = new MockRegistry({ 560 tap: t, 561 registry: npm.config.get('registry'), 562 authorization: 'test-auth-token', 563 }) 564 registry.whoami({ username: user }) 565 registry.getPackages({ team: user, packages: {} }) 566 567 await testComp(t, { 568 argv: ['npm', 'unpublish'], 569 partialWord: 'pkg', 570 expect: [], 571 title: 'should have no autocompletion', 572 }) 573 }) 574 575 t.test('no pkg name to complete', async t => { 576 const registry = new MockRegistry({ 577 tap: t, 578 registry: npm.config.get('registry'), 579 authorization: 'test-auth-token', 580 }) 581 registry.whoami({ username: user }) 582 registry.getPackages({ team: user, 583 packages: { 584 [pkg]: 'write', 585 [`${pkg}a`]: 'write', 586 } }) 587 588 await testComp(t, { 589 argv: ['npm', 'unpublish'], 590 partialWord: undefined, 591 expect: [pkg, `${pkg}a`], 592 title: 'should autocomplete with available package names from user', 593 }) 594 }) 595 596 t.test('logged out user', async t => { 597 const registry = new MockRegistry({ 598 tap: t, 599 registry: npm.config.get('registry'), 600 authorization: 'test-auth-token', 601 }) 602 registry.whoami({ responseCode: 404 }) 603 604 await testComp(t, { 605 argv: ['npm', 'unpublish'], 606 partialWord: pkg, 607 expect: [], 608 }) 609 }) 610 611 t.test('too many args', async t => { 612 await testComp(t, { 613 argv: ['npm', 'unpublish', 'foo'], 614 partialWord: undefined, 615 expect: [], 616 }) 617 }) 618}) 619