1import { promptAsync } from '../../../utils/prompts';
2import { ApiV2Error } from '../../rest/client';
3import { showLoginPromptAsync } from '../actions';
4import { retryUsernamePasswordAuthWithOTPAsync, UserSecondFactorDeviceMethod } from '../otp';
5import { loginAsync } from '../user';
6
7jest.mock('../../../log');
8jest.mock('../../../utils/prompts');
9jest.mock('../../rest/client', () => {
10  const { ApiV2Error } = jest.requireActual('../../rest/client');
11  return {
12    ApiV2Error,
13  };
14});
15jest.mock('../otp');
16jest.mock('../user');
17
18const asMock = (fn: any): jest.Mock => fn;
19
20beforeEach(() => {
21  asMock(promptAsync).mockClear();
22  asMock(promptAsync).mockImplementation(() => {
23    throw new Error('Should not be called');
24  });
25
26  asMock(loginAsync).mockClear();
27});
28
29describe(showLoginPromptAsync, () => {
30  it('prompts for OTP when 2FA is enabled', async () => {
31    asMock(promptAsync)
32      .mockImplementationOnce(() => ({ username: 'hello', password: 'world' }))
33      .mockImplementationOnce(() => ({ otp: '123456' }))
34      .mockImplementation(() => {
35        throw new Error("shouldn't happen");
36      });
37    asMock(loginAsync)
38      .mockImplementationOnce(async () => {
39        throw new ApiV2Error({
40          message: 'An OTP is required',
41          code: 'ONE_TIME_PASSWORD_REQUIRED',
42          metadata: {
43            secondFactorDevices: [
44              {
45                id: 'p0',
46                is_primary: true,
47                method: UserSecondFactorDeviceMethod.SMS,
48                sms_phone_number: 'testphone',
49              },
50            ],
51            smsAutomaticallySent: true,
52          },
53        });
54      })
55      .mockImplementation(() => {});
56
57    await showLoginPromptAsync();
58
59    expect(retryUsernamePasswordAuthWithOTPAsync).toHaveBeenCalledWith('hello', 'world', {
60      secondFactorDevices: [
61        {
62          id: 'p0',
63          is_primary: true,
64          method: UserSecondFactorDeviceMethod.SMS,
65          sms_phone_number: 'testphone',
66        },
67      ],
68      smsAutomaticallySent: true,
69    });
70  });
71
72  it('does not prompt if all required credentials are provided', async () => {
73    asMock(promptAsync).mockImplementation(() => {
74      throw new Error("shouldn't happen");
75    });
76    asMock(loginAsync).mockImplementation(() => {});
77
78    await showLoginPromptAsync({ username: 'hello', password: 'world' });
79  });
80});
81