1const t = require('tap') 2const mockNpm = require('../../fixtures/mock-npm') 3 4const mockProfile = async (t, { npmProfile, readUserInfo, qrcode, config, ...opts } = {}) => { 5 const mocks = { 6 'npm-profile': npmProfile || { 7 async get () {}, 8 async set () {}, 9 async createToken () {}, 10 }, 11 'qrcode-terminal': qrcode || { generate: (url, cb) => cb() }, 12 'cli-table3': class extends Array { 13 toString () { 14 return this.filter(Boolean) 15 .map(i => [...Object.entries(i)].map(v => v.join(': '))) 16 .join('\n') 17 } 18 }, 19 '{LIB}/utils/read-user-info.js': readUserInfo || { 20 async password () {}, 21 async otp () {}, 22 }, 23 } 24 25 const mock = await mockNpm(t, { 26 ...opts, 27 command: 'profile', 28 config: { 29 color: false, 30 ...config, 31 }, 32 mocks: { 33 ...mocks, 34 ...opts.mocks, 35 }, 36 }) 37 38 return { 39 ...mock, 40 result: () => mock.joinedOutput(), 41 } 42} 43 44const userProfile = { 45 tfa: { pending: false, mode: 'auth-and-writes' }, 46 name: 'foo', 47 email: 'foo@github.com', 48 email_verified: true, 49 created: '2015-02-26T01:26:37.384Z', 50 updated: '2020-08-12T16:19:35.326Z', 51 cidr_whitelist: null, 52 fullname: 'Foo Bar', 53 homepage: 'https://github.com', 54 freenode: 'foobar', 55 twitter: 'https://twitter.com/npmjs', 56 github: 'https://github.com/npm', 57} 58 59t.test('no args', async t => { 60 const { profile } = await mockProfile(t) 61 await t.rejects(profile.exec([]), await profile.usage) 62}) 63 64t.test('profile get no args', async t => { 65 const defaultNpmProfile = { 66 async get () { 67 return userProfile 68 }, 69 } 70 71 t.test('default output', async t => { 72 const { profile, result } = await mockProfile(t, { npmProfile: defaultNpmProfile }) 73 await profile.exec(['get']) 74 75 t.matchSnapshot(result(), 'should output table with contents') 76 }) 77 78 t.test('--json', async t => { 79 const { profile, result } = await mockProfile(t, { 80 npmProfile: defaultNpmProfile, 81 config: { json: true }, 82 }) 83 84 await profile.exec(['get']) 85 86 t.same(JSON.parse(result()), userProfile, 'should output json profile result') 87 }) 88 89 t.test('--parseable', async t => { 90 const { profile, result } = await mockProfile(t, { 91 npmProfile: defaultNpmProfile, 92 config: { parseable: true }, 93 }) 94 95 await profile.exec(['get']) 96 t.matchSnapshot(result(), 'should output all profile info as parseable result') 97 }) 98 99 t.test('--color', async t => { 100 const { profile, result } = await mockProfile(t, { 101 npmProfile: defaultNpmProfile, 102 config: { color: 'always' }, 103 }) 104 105 await profile.exec(['get']) 106 t.matchSnapshot(result(), 'should output all profile info with color result') 107 }) 108 109 t.test('no tfa enabled', async t => { 110 const npmProfile = { 111 async get () { 112 return { 113 ...userProfile, 114 tfa: null, 115 } 116 }, 117 } 118 const { profile, result } = await mockProfile(t, { npmProfile }) 119 120 await profile.exec(['get']) 121 t.matchSnapshot(result(), 'should output expected profile values') 122 }) 123 124 t.test('unverified email', async t => { 125 const npmProfile = { 126 async get () { 127 return { 128 ...userProfile, 129 email_verified: false, 130 } 131 }, 132 } 133 134 const { profile, result } = await mockProfile(t, { npmProfile }) 135 136 await profile.exec(['get']) 137 138 t.matchSnapshot(result(), 'should output table with contents') 139 }) 140 141 t.test('profile has cidr_whitelist item', async t => { 142 const npmProfile = { 143 async get () { 144 return { 145 ...userProfile, 146 cidr_whitelist: ['192.168.1.1'], 147 } 148 }, 149 } 150 151 const { profile, result } = await mockProfile(t, { npmProfile }) 152 153 await profile.exec(['get']) 154 155 t.matchSnapshot(result(), 'should output table with contents') 156 }) 157}) 158 159t.test('profile get <key>', async t => { 160 const npmProfile = { 161 async get () { 162 return userProfile 163 }, 164 } 165 166 t.test('default output', async t => { 167 const { profile, result } = await mockProfile(t, { npmProfile }) 168 169 await profile.exec(['get', 'name']) 170 171 t.equal(result(), 'foo', 'should output value result') 172 }) 173 174 t.test('--json', async t => { 175 const { profile, result } = await mockProfile(t, { 176 npmProfile, 177 config: { json: true }, 178 }) 179 180 await profile.exec(['get', 'name']) 181 182 t.same( 183 JSON.parse(result()), 184 userProfile, 185 'should output json profile result ignoring args filter' 186 ) 187 }) 188 189 t.test('--parseable', async t => { 190 const { profile, result } = await mockProfile(t, { 191 npmProfile, 192 config: { parseable: true }, 193 }) 194 195 await profile.exec(['get', 'name']) 196 197 t.matchSnapshot(result(), 'should output parseable result value') 198 }) 199}) 200 201t.test('profile get multiple args', async t => { 202 const npmProfile = { 203 async get () { 204 return userProfile 205 }, 206 } 207 208 t.test('default output', async t => { 209 const { profile, result } = await mockProfile(t, { 210 npmProfile, 211 }) 212 await profile.exec(['get', 'name', 'email', 'github']) 213 214 t.matchSnapshot(result(), 'should output all keys') 215 }) 216 217 t.test('--json', async t => { 218 const config = { json: true } 219 const { profile, result } = await mockProfile(t, { 220 npmProfile, 221 config, 222 }) 223 224 await profile.exec(['get', 'name', 'email', 'github']) 225 226 t.same(JSON.parse(result()), userProfile, 'should output json profile result and ignore args') 227 }) 228 229 t.test('--parseable', async t => { 230 const config = { parseable: true } 231 const { profile, result } = await mockProfile(t, { 232 npmProfile, 233 config, 234 }) 235 236 await profile.exec(['get', 'name', 'email', 'github']) 237 238 t.matchSnapshot(result(), 'should output parseable profile value results') 239 }) 240 241 t.test('comma separated', async t => { 242 const { profile, result } = await mockProfile(t, { 243 npmProfile, 244 }) 245 246 await profile.exec(['get', 'name,email,github']) 247 248 t.matchSnapshot(result(), 'should output all keys') 249 }) 250}) 251 252t.test('profile set <key> <value>', async t => { 253 t.test('no key', async t => { 254 const { profile } = await mockProfile(t) 255 256 await t.rejects( 257 profile.exec(['set']), 258 /npm profile set <prop> <value>/, 259 'should throw proper usage message' 260 ) 261 }) 262 263 t.test('no value', async t => { 264 const { profile } = await mockProfile(t) 265 await t.rejects( 266 profile.exec(['set', 'email']), 267 /npm profile set <prop> <value>/, 268 'should throw proper usage message' 269 ) 270 }) 271 272 t.test('set password', async t => { 273 const { profile } = await mockProfile(t) 274 await t.rejects( 275 profile.exec(['set', 'password', '1234']), 276 /Do not include your current or new passwords on the command line./, 277 'should throw an error refusing to set password from args' 278 ) 279 }) 280 281 t.test('unwritable key', async t => { 282 const { profile } = await mockProfile(t) 283 await await t.rejects( 284 profile.exec(['set', 'name', 'foo']), 285 /"name" is not a property we can set./, 286 'should throw the unwritable key error' 287 ) 288 }) 289 290 const defaultNpmProfile = t => ({ 291 async get () { 292 return userProfile 293 }, 294 async set (newUser) { 295 t.match( 296 newUser, 297 { 298 fullname: 'Lorem Ipsum', 299 }, 300 'should set new value to key' 301 ) 302 return { 303 ...userProfile, 304 ...newUser, 305 } 306 }, 307 }) 308 309 t.test('writable key', async t => { 310 t.test('default output', async t => { 311 t.plan(2) 312 313 const { profile, result } = await mockProfile(t, { 314 npmProfile: defaultNpmProfile(t), 315 }) 316 317 await profile.exec(['set', 'fullname', 'Lorem Ipsum']) 318 t.equal(result(), 'Set fullname to Lorem Ipsum', 'should output set key success msg') 319 }) 320 321 t.test('--json', async t => { 322 t.plan(2) 323 324 const config = { json: true } 325 326 const { profile, result } = await mockProfile(t, { 327 npmProfile: defaultNpmProfile(t), 328 config, 329 }) 330 331 await profile.exec(['set', 'fullname', 'Lorem Ipsum']) 332 333 t.same( 334 JSON.parse(result()), 335 { 336 fullname: 'Lorem Ipsum', 337 }, 338 'should output json set key success msg' 339 ) 340 }) 341 342 t.test('--parseable', async t => { 343 t.plan(2) 344 345 const config = { parseable: true } 346 const { profile, result } = await mockProfile(t, { 347 npmProfile: defaultNpmProfile(t), 348 config, 349 }) 350 351 await profile.exec(['set', 'fullname', 'Lorem Ipsum']) 352 353 t.matchSnapshot(result(), 'should output parseable set key success msg') 354 }) 355 }) 356 357 t.test('write new email', async t => { 358 t.plan(2) 359 360 const npmProfile = { 361 async get () { 362 return userProfile 363 }, 364 async set (newUser) { 365 t.match( 366 newUser, 367 { 368 email: 'foo@npmjs.com', 369 }, 370 'should set new value to email' 371 ) 372 return { 373 ...userProfile, 374 ...newUser, 375 } 376 }, 377 } 378 379 const { profile, result } = await mockProfile(t, { 380 npmProfile, 381 }) 382 383 await profile.exec(['set', 'email', 'foo@npmjs.com']) 384 t.equal(result(), 'Set email to foo@npmjs.com', 'should output set key success msg') 385 }) 386 387 t.test('change password', async t => { 388 t.plan(5) 389 390 const npmProfile = { 391 async get () { 392 return userProfile 393 }, 394 async set (newUser) { 395 t.match( 396 newUser, 397 { 398 password: { 399 old: 'currentpassword1234', 400 new: 'newpassword1234', 401 }, 402 }, 403 'should set new password' 404 ) 405 return { 406 ...userProfile, 407 } 408 }, 409 } 410 411 const readUserInfo = { 412 async password (label) { 413 if (label === 'Current password: ') { 414 t.ok('should interactively ask for password confirmation') 415 } else if (label === 'New password: ') { 416 t.ok('should interactively ask for new password') 417 } else if (label === ' Again: ') { 418 t.ok('should interactively ask for new password confirmation') 419 } else { 420 throw new Error('Unexpected label: ' + label) 421 } 422 423 return label === 'Current password: ' ? 'currentpassword1234' : 'newpassword1234' 424 }, 425 } 426 427 const { profile, result } = await mockProfile(t, { 428 npmProfile, 429 readUserInfo, 430 }) 431 432 await profile.exec(['set', 'password']) 433 434 t.equal(result(), 'Set password', 'should output set password success msg') 435 }) 436 437 t.test('password confirmation mismatch', async t => { 438 t.plan(2) 439 440 let passwordPromptCount = 0 441 442 const npmProfile = { 443 async get () { 444 return userProfile 445 }, 446 async set () { 447 return { ...userProfile } 448 }, 449 } 450 451 const readUserInfo = { 452 async password (label) { 453 passwordPromptCount++ 454 455 switch (label) { 456 case 'Current password: ': 457 return 'currentpassword1234' 458 case 'New password: ': 459 return passwordPromptCount < 3 ? 'password-that-will-not-be-confirmed' : 'newpassword' 460 case ' Again: ': 461 return 'newpassword' 462 default: 463 return 'password1234' 464 } 465 }, 466 } 467 468 const { profile, result, logs } = await mockProfile(t, { 469 npmProfile, 470 readUserInfo, 471 }) 472 473 await profile.exec(['set', 'password']) 474 475 t.equal( 476 logs.warn[0][1], 477 'Passwords do not match, please try again.', 478 'should log password mismatch message' 479 ) 480 481 t.equal(result(), 'Set password', 'should output set password success msg') 482 }) 483}) 484 485t.test('enable-2fa', async t => { 486 t.test('invalid args', async t => { 487 const { profile } = await mockProfile(t) 488 await t.rejects( 489 profile.exec(['enable-2fa', 'foo', 'bar']), 490 /npm profile enable-2fa \[auth-and-writes|auth-only\]/, 491 'should throw usage error' 492 ) 493 }) 494 495 t.test('invalid two factor auth mode', async t => { 496 const { profile } = await mockProfile(t) 497 await t.rejects( 498 profile.exec(['enable-2fa', 'foo']), 499 /Invalid two-factor authentication mode "foo"/, 500 'should throw invalid auth mode error' 501 ) 502 }) 503 504 t.test('no support for --json output', async t => { 505 const config = { json: true } 506 const { profile } = await mockProfile(t, { config }) 507 508 await t.rejects( 509 profile.exec(['enable-2fa', 'auth-only']), 510 'Enabling two-factor authentication is an interactive ' + 511 'operation and JSON output mode is not available', 512 'should throw no support msg' 513 ) 514 }) 515 516 t.test('no support for --parseable output', async t => { 517 const config = { parseable: true } 518 const { profile } = await mockProfile(t, { config }) 519 520 await t.rejects( 521 profile.exec(['enable-2fa', 'auth-only']), 522 'Enabling two-factor authentication is an interactive ' + 523 'operation and parseable output mode is not available', 524 'should throw no support msg' 525 ) 526 }) 527 528 t.test('no bearer tokens returned by registry', async t => { 529 t.plan(3) 530 531 const npmProfile = { 532 async createToken (pass) { 533 t.match(pass, 'bar', 'should use password for basic auth') 534 return {} 535 }, 536 } 537 538 const { npm, profile } = await mockProfile(t, { 539 npmProfile, 540 }) 541 542 // mock legacy basic auth style 543 // XXX: use mock registry 544 npm.config.getCredentialsByURI = reg => { 545 t.equal(reg, npm.flatOptions.registry, 'should use expected registry') 546 return { auth: Buffer.from('foo:bar').toString('base64') } 547 } 548 549 await t.rejects( 550 profile.exec(['enable-2fa', 'auth-only']), 551 'Your registry https://registry.npmjs.org/ does ' + 552 'not seem to support bearer tokens. Bearer tokens ' + 553 'are required for two-factor authentication', 554 'should throw no support msg' 555 ) 556 }) 557 558 t.test('from basic username/password auth', async t => { 559 const npmProfile = { 560 async createToken (pass) { 561 return {} 562 }, 563 } 564 565 const { npm, profile } = await mockProfile(t, { 566 npmProfile, 567 }) 568 569 // mock legacy basic auth style with user/pass 570 // XXX: use mock registry 571 npm.config.getCredentialsByURI = () => { 572 return { username: 'foo', password: 'bar' } 573 } 574 575 await t.rejects( 576 profile.exec(['enable-2fa', 'auth-only']), 577 'Your registry https://registry.npmjs.org/ does ' + 578 'not seem to support bearer tokens. Bearer tokens ' + 579 'are required for two-factor authentication', 580 'should throw no support msg' 581 ) 582 }) 583 584 t.test('no auth found', async t => { 585 const { npm, profile } = await mockProfile(t) 586 587 // XXX: use mock registry 588 npm.config.getCredentialsByURI = () => ({}) 589 590 await t.rejects( 591 profile.exec(['enable-2fa', 'auth-only']), 592 'You need to be logged in to registry ' + 'https://registry.npmjs.org/ in order to enable 2fa' 593 ) 594 }) 595 596 t.test('from basic auth, asks for otp', async t => { 597 t.plan(9) 598 599 const npmProfile = { 600 async createToken (pass) { 601 t.match(pass, 'bar', 'should use password for basic auth') 602 return { token: 'token' } 603 }, 604 async get () { 605 return userProfile 606 }, 607 async set (newProfile, conf) { 608 t.match( 609 newProfile, 610 { 611 tfa: { 612 mode: 'auth-only', 613 }, 614 }, 615 'should set tfa mode' 616 ) 617 return { 618 ...userProfile, 619 tfa: null, 620 } 621 }, 622 } 623 624 const readUserInfo = { 625 async password () { 626 t.ok('should interactively ask for password confirmation') 627 return 'password1234' 628 }, 629 async otp (label) { 630 t.equal( 631 label, 632 'Enter one-time password: ', 633 'should ask for otp confirmation' 634 ) 635 return '123456' 636 }, 637 } 638 639 const { npm, profile, result } = await mockProfile(t, { 640 npmProfile, 641 readUserInfo, 642 }) 643 644 // mock legacy basic auth style 645 // XXX: use mock registry 646 npm.config.getCredentialsByURI = reg => { 647 t.equal(reg, npm.flatOptions.registry, 'should use expected registry') 648 return { auth: Buffer.from('foo:bar').toString('base64') } 649 } 650 npm.config.setCredentialsByURI = (registry, { token }) => { 651 t.equal(registry, npm.flatOptions.registry, 'should set expected registry') 652 t.equal(token, 'token', 'should set expected token') 653 } 654 npm.config.save = type => { 655 t.equal(type, 'user', 'should save to user config') 656 } 657 658 await profile.exec(['enable-2fa', 'auth-only']) 659 t.equal( 660 result(), 661 'Two factor authentication mode changed to: auth-only', 662 'should output success msg' 663 ) 664 }) 665 666 t.test('from token and set otp, retries on pending and verifies with qrcode', async t => { 667 t.plan(4) 668 669 let setCount = 0 670 const npmProfile = { 671 async get () { 672 return { 673 ...userProfile, 674 tfa: { 675 pending: true, 676 }, 677 } 678 }, 679 async set (newProfile, conf) { 680 setCount++ 681 682 // when profile response shows that 2fa is pending the 683 // first time calling npm-profile.set should reset 2fa 684 if (setCount === 1) { 685 t.match( 686 newProfile, 687 { 688 tfa: { 689 password: 'password1234', 690 mode: 'disable', 691 }, 692 }, 693 'should reset 2fa' 694 ) 695 } else if (setCount === 2) { 696 t.match( 697 newProfile, 698 { 699 tfa: { 700 mode: 'auth-only', 701 }, 702 }, 703 'should set tfa mode approprietly in follow-up call' 704 ) 705 } else if (setCount === 3) { 706 t.match( 707 newProfile, 708 { 709 tfa: ['123456'], 710 }, 711 'should set tfa as otp code?' 712 ) 713 return { 714 ...userProfile, 715 tfa: ['123456', '789101'], 716 } 717 } 718 719 return { 720 ...userProfile, 721 tfa: 'otpauth://foo?secret=1234', 722 } 723 }, 724 } 725 726 const readUserInfo = { 727 async password () { 728 return 'password1234' 729 }, 730 async otp () { 731 return '123456' 732 }, 733 } 734 735 const qrcode = { 736 /* eslint-disable-next-line node/no-callback-literal */ 737 generate: (url, cb) => cb('qrcode'), 738 } 739 740 const { npm, profile, result } = await mockProfile(t, { 741 npmProfile, 742 qrcode, 743 readUserInfo, 744 config: { otp: '1234' }, 745 }) 746 747 // XXX: use mock registry 748 npm.config.getCredentialsByURI = () => { 749 return { token: 'token' } 750 } 751 752 await profile.exec(['enable-2fa', 'auth-only']) 753 754 t.matchSnapshot(result(), 'should output 2fa enablement success msgs') 755 }) 756 757 t.test('from token and set otp, retrieves invalid otp', async t => { 758 const npmProfile = { 759 async get () { 760 return { 761 ...userProfile, 762 tfa: { 763 pending: true, 764 }, 765 } 766 }, 767 async set (newProfile, conf) { 768 return { 769 ...userProfile, 770 tfa: 'http://foo?secret=1234', 771 } 772 }, 773 } 774 775 const readUserInfo = { 776 async password () { 777 return 'password1234' 778 }, 779 async otp (label) { 780 return '123456' 781 }, 782 } 783 784 const { npm, profile } = await mockProfile(t, { 785 npmProfile, 786 readUserInfo, 787 config: { otp: '1234' }, 788 }) 789 790 npm.config.getCredentialsByURI = () => { 791 return { token: 'token' } 792 } 793 794 await t.rejects( 795 profile.exec(['enable-2fa', 'auth-only']), 796 /Unknown error enabling two-factor authentication./, 797 'should throw invalid 2fa auth url error' 798 ) 799 }) 800 801 t.test('from token auth provides --otp config arg', async t => { 802 const npmProfile = { 803 async get () { 804 return userProfile 805 }, 806 async set (newProfile, conf) { 807 return { 808 ...userProfile, 809 tfa: null, 810 } 811 }, 812 } 813 814 const readUserInfo = { 815 async password () { 816 return 'password1234' 817 }, 818 async otp () { 819 throw new Error('should not ask for otp') 820 }, 821 } 822 823 const { npm, profile, result } = await mockProfile(t, { 824 npmProfile, 825 readUserInfo, 826 config: { otp: '123456' }, 827 }) 828 829 npm.config.getCredentialsByURI = reg => { 830 return { token: 'token' } 831 } 832 833 await profile.exec(['enable-2fa', 'auth-and-writes']) 834 835 t.equal( 836 result(), 837 'Two factor authentication mode changed to: auth-and-writes', 838 'should output success msg' 839 ) 840 }) 841 842 t.test('missing tfa from user profile', async t => { 843 const npmProfile = { 844 async get () { 845 return { 846 ...userProfile, 847 tfa: undefined, 848 } 849 }, 850 async set (newProfile, conf) { 851 return { 852 ...userProfile, 853 tfa: null, 854 } 855 }, 856 } 857 858 const readUserInfo = { 859 async password () { 860 return 'password1234' 861 }, 862 async otp () { 863 return '123456' 864 }, 865 } 866 867 const { npm, profile, result } = await mockProfile(t, { 868 npmProfile, 869 readUserInfo, 870 }) 871 872 npm.config.getCredentialsByURI = reg => { 873 return { token: 'token' } 874 } 875 876 await profile.exec(['enable-2fa', 'auth-only']) 877 878 t.equal( 879 result(), 880 'Two factor authentication mode changed to: auth-only', 881 'should output success msg' 882 ) 883 }) 884 885 t.test('defaults to auth-and-writes permission if no mode specified', async t => { 886 const npmProfile = { 887 async get () { 888 return { 889 ...userProfile, 890 tfa: undefined, 891 } 892 }, 893 async set (newProfile, conf) { 894 return { 895 ...userProfile, 896 tfa: null, 897 } 898 }, 899 } 900 901 const readUserInfo = { 902 async password () { 903 return 'password1234' 904 }, 905 async otp () { 906 return '123456' 907 }, 908 } 909 910 const { npm, profile, result } = await mockProfile(t, { 911 npmProfile, 912 readUserInfo, 913 }) 914 915 npm.config.getCredentialsByURI = reg => { 916 return { token: 'token' } 917 } 918 919 await profile.exec(['enable-2fa']) 920 t.equal( 921 result(), 922 'Two factor authentication mode changed to: auth-and-writes', 923 'should enable 2fa with auth-and-writes permission' 924 ) 925 }) 926}) 927 928t.test('disable-2fa', async t => { 929 t.test('no tfa enabled', async t => { 930 const npmProfile = { 931 async get () { 932 return { 933 ...userProfile, 934 tfa: null, 935 } 936 }, 937 } 938 939 const { profile, result } = await mockProfile(t, { 940 npmProfile, 941 }) 942 943 await profile.exec(['disable-2fa']) 944 t.equal(result(), 'Two factor authentication not enabled.', 945 'should output already disalbed msg') 946 }) 947 948 t.test('requests otp', async t => { 949 const npmProfile = t => ({ 950 async get () { 951 return userProfile 952 }, 953 async set (newProfile, conf) { 954 t.same( 955 newProfile, 956 { 957 tfa: { 958 password: 'password1234', 959 mode: 'disable', 960 }, 961 }, 962 'should send the new info for setting in profile' 963 ) 964 }, 965 }) 966 967 const readUserInfo = t => ({ 968 async password () { 969 t.ok('should interactively ask for password confirmation') 970 return 'password1234' 971 }, 972 async otp (label) { 973 t.equal( 974 label, 975 'Enter one-time password: ', 976 'should ask for otp confirmation' 977 ) 978 return '1234' 979 }, 980 }) 981 982 t.test('default output', async t => { 983 t.plan(4) 984 985 const { profile, result } = await mockProfile(t, { 986 npmProfile: npmProfile(t), 987 readUserInfo: readUserInfo(t), 988 }) 989 990 await profile.exec(['disable-2fa']) 991 t.equal(result(), 'Two factor authentication disabled.', 'should output already disabled msg') 992 }) 993 994 t.test('--json', async t => { 995 t.plan(4) 996 997 const config = { json: true } 998 999 const { profile, result } = await mockProfile(t, { 1000 npmProfile: npmProfile(t), 1001 readUserInfo: readUserInfo(t), 1002 config, 1003 }) 1004 1005 await profile.exec(['disable-2fa']) 1006 1007 t.same(JSON.parse(result()), { tfa: false }, 'should output json already disabled msg') 1008 }) 1009 1010 t.test('--parseable', async t => { 1011 t.plan(4) 1012 1013 const config = { parseable: true } 1014 1015 const { profile, result } = await mockProfile(t, { 1016 npmProfile: npmProfile(t), 1017 readUserInfo: readUserInfo(t), 1018 config, 1019 }) 1020 1021 await profile.exec(['disable-2fa']) 1022 1023 t.equal(result(), 'tfa\tfalse', 'should output parseable already disabled msg') 1024 }) 1025 }) 1026 1027 t.test('--otp config already set', async t => { 1028 t.plan(2) 1029 1030 const npmProfile = { 1031 async get () { 1032 return userProfile 1033 }, 1034 async set (newProfile, conf) { 1035 t.same( 1036 newProfile, 1037 { 1038 tfa: { 1039 password: 'password1234', 1040 mode: 'disable', 1041 }, 1042 }, 1043 'should send the new info for setting in profile' 1044 ) 1045 }, 1046 } 1047 1048 const readUserInfo = { 1049 async password () { 1050 return 'password1234' 1051 }, 1052 async otp (label) { 1053 throw new Error('should not ask for otp') 1054 }, 1055 } 1056 1057 const { profile, result } = await mockProfile(t, { 1058 npmProfile, 1059 readUserInfo, 1060 config: { otp: '123456' }, 1061 }) 1062 1063 await profile.exec(['disable-2fa']) 1064 1065 t.equal(result(), 'Two factor authentication disabled.', 'should output already disalbed msg') 1066 }) 1067}) 1068 1069t.test('unknown subcommand', async t => { 1070 const { profile } = await mockProfile(t) 1071 1072 await t.rejects( 1073 profile.exec(['asfd']), 1074 /Unknown profile command: asfd/, 1075 'should throw unknown cmd error' 1076 ) 1077}) 1078 1079t.test('completion', async t => { 1080 const testComp = async (t, { argv, expect, title } = {}) => { 1081 const { profile } = await mockProfile(t) 1082 t.resolveMatch(profile.completion({ conf: { argv: { remain: argv } } }), expect, title) 1083 } 1084 1085 t.test('npm profile autocomplete', async t => { 1086 await testComp(t, { 1087 argv: ['npm', 'profile'], 1088 expect: ['enable-2fa', 'disable-2fa', 'get', 'set'], 1089 title: 'should auto complete with subcommands', 1090 }) 1091 }) 1092 1093 t.test('npm profile enable autocomplete', async t => { 1094 await testComp(t, { 1095 argv: ['npm', 'profile', 'enable-2fa'], 1096 expect: ['auth-and-writes', 'auth-only'], 1097 title: 'should auto complete with auth types', 1098 }) 1099 }) 1100 1101 t.test('npm profile <subcmd> no autocomplete', async t => { 1102 const noAutocompleteCmds = ['disable-2fa', 'disable-tfa', 'get', 'set'] 1103 for (const subcmd of noAutocompleteCmds) { 1104 await t.test(subcmd, t => testComp(t, { 1105 argv: ['npm', 'profile', subcmd], 1106 expect: [], 1107 title: `${subcmd} should have no autocomplete`, 1108 })) 1109 } 1110 }) 1111 1112 t.test('npm profile unknown subcommand autocomplete', async t => { 1113 const { profile } = await mockProfile(t) 1114 t.rejects( 1115 profile.completion({ conf: { argv: { remain: ['npm', 'profile', 'asdf'] } } }), 1116 { message: 'asdf not recognized' }, 1117 'should throw unknown cmd error' 1118 ) 1119 }) 1120}) 1121