1import { asMock } from '../../../__tests__/asMock';
2import { promptAsync } from '../../../utils/prompts';
3import { ApiV2Error } from '../../rest/client';
4import { showLoginPromptAsync } from '../actions';
5import { retryUsernamePasswordAuthWithOTPAsync, UserSecondFactorDeviceMethod } from '../otp';
6import { loginAsync, ssoLoginAsync } from '../user';
7
8jest.mock('../../../log');
9jest.mock('../../../utils/prompts');
10jest.mock('../../rest/client', () => {
11  const { ApiV2Error } = jest.requireActual('../../rest/client');
12  return {
13    ApiV2Error,
14  };
15});
16jest.mock('../otp');
17jest.mock('../user');
18
19beforeEach(() => {
20  asMock(promptAsync).mockClear();
21  asMock(promptAsync).mockImplementation(() => {
22    throw new Error('Should not be called');
23  });
24
25  asMock(loginAsync).mockClear();
26  asMock(ssoLoginAsync).mockClear();
27});
28
29describe(showLoginPromptAsync, () => {
30  it('prompts for OTP when 2FA is enabled', async () => {
31    asMock(promptAsync)
32      .mockImplementationOnce(async () => ({ username: 'hello', password: 'world' }))
33      .mockImplementationOnce(async () => ({ 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(async () => {});
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(async () => {});
77
78    await showLoginPromptAsync({ username: 'hello', password: 'world' });
79  });
80
81  it('calls regular login if the sso flag is false', async () => {
82    asMock(promptAsync).mockImplementationOnce(async () => ({
83      username: 'USERNAME',
84      password: 'PASSWORD',
85    }));
86
87    await showLoginPromptAsync({ username: 'hello', password: 'world', sso: false });
88
89    expect(loginAsync).toHaveBeenCalledTimes(1);
90  });
91
92  it('calls regular login if the sso flag is undefined', async () => {
93    asMock(promptAsync)
94      .mockImplementationOnce(async () => ({ username: 'USERNAME', password: 'PASSWORD' }))
95      .mockImplementation(() => {
96        throw new Error("shouldn't happen");
97      });
98
99    await showLoginPromptAsync({ username: 'hello', password: 'world' });
100
101    expect(loginAsync).toHaveBeenCalledTimes(1);
102  });
103
104  it('calls SSO login if the sso flag is true', async () => {
105    asMock(promptAsync);
106    await showLoginPromptAsync({ username: 'hello', password: 'world', sso: true });
107
108    expect(ssoLoginAsync).toHaveBeenCalledTimes(1);
109  });
110});
111