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