Merge branch 'MAILWEB-6014-add-mail-web-eldt' into 'main'
[ProtonMail-WebClient.git] / packages / wallet / hooks / useWalletAutoCreate.test.ts
blob0d7f6c5c61a84b8420a7b5f3347d39142731a0bd
1 import { renderHook } from '@testing-library/react-hooks';
3 import * as andromedaModule from '@proton/andromeda';
4 import { CryptoProxy } from '@proton/crypto';
5 import { Api as CryptoApi } from '@proton/crypto/lib/worker/api';
6 import { wait } from '@proton/shared/lib/helpers/promise';
7 import type { Address, DecryptedAddressKey } from '@proton/shared/lib/interfaces';
8 import {
9     mockUseAuthentication,
10     mockUseConfig,
11     mockUseGetAddresses,
12     mockUseGetOrganization,
13     mockUseGetUserKeys,
14     mockUseUser,
15 } from '@proton/testing/lib/vitest';
17 import {
18     apiWalletAccountOneA,
19     apiWalletsData,
20     expectSignedBy,
21     getAddressKey,
22     getUserKeys,
23     userWalletSettings,
24 } from '../tests';
25 import * as wasmUtilsModule from '../utils/wasm';
26 import { useWalletAutoCreate } from './useWalletAutoCreate';
28 vi.mock('@proton/andromeda', async () => {
29     const andromeda = await vi.importActual('@proton/andromeda');
30     return {
31         ...andromeda,
32         WasmMnemonic: vi.fn(() =>
33             (andromeda.WasmMnemonic as any).fromString(
34                 'benefit indoor helmet wine exist height grain spot rely half beef nothing'
35             )
36         ),
37     };
38 });
40 describe('useWalletAutoCreate', () => {
41     let addressWithKey: { address: Address; keys: DecryptedAddressKey[] };
43     const mockedCreateWallet = vi.fn(async () => apiWalletsData[0]);
44     const mockedCreateWalletAccount = vi.fn(async () => ({ Data: apiWalletAccountOneA }));
45     const mockedAddEmailAddress = vi.fn();
46     const mockedAddBitcoinAddresses = vi.fn();
47     const mockedUpdateWalletAccountFiatCurrency = vi.fn();
48     const mockedGetUserWalletSettings = vi.fn(async () => [{ ...userWalletSettings, WalletCreated: 0 }]);
50     const mockedIsWasmSupported = vi.spyOn(wasmUtilsModule, 'isWasmSupported');
51     const mockedLoadWasmModule = vi.spyOn(wasmUtilsModule, 'loadWasmModule');
53     beforeAll(async () => {
54         await CryptoProxy.setEndpoint(new CryptoApi(), (endpoint) => endpoint.clearKeyStore());
55         mockUseConfig();
56     });
58     beforeEach(async () => {
59         vi.clearAllMocks();
61         mockedIsWasmSupported.mockReturnValue(true);
62         mockedGetUserWalletSettings.mockResolvedValue([{ ...userWalletSettings, WalletCreated: 0 }]);
64         vi.spyOn(andromedaModule, 'WasmProtonWalletApiClient').mockReturnValue({
65             clients: vi.fn(() => {
66                 return {
67                     network: { getNetwork: vi.fn(() => andromedaModule.WasmNetwork.Testnet) },
68                     wallet: {
69                         createWallet: mockedCreateWallet,
70                         createWalletAccount: mockedCreateWalletAccount,
71                         addEmailAddress: mockedAddEmailAddress,
72                         updateWalletAccountFiatCurrency: mockedUpdateWalletAccountFiatCurrency,
73                     },
74                     bitcoin_address: {
75                         addBitcoinAddresses: mockedAddBitcoinAddresses,
76                     },
77                     settings: {
78                         getUserSettings: mockedGetUserWalletSettings,
79                     },
80                 };
81             }),
82         } as unknown as andromedaModule.WasmProtonWalletApiClient);
84         mockUseGetUserKeys(await getUserKeys());
85         mockUseUser();
86         mockUseGetOrganization();
87         mockUseAuthentication({ getPassword: vi.fn(() => 'testtest') });
89         addressWithKey = await getAddressKey();
90         mockUseGetAddresses([addressWithKey.address]);
91     });
93     describe('when user wallet has already created a wallet before', () => {
94         beforeEach(() => {
95             mockedGetUserWalletSettings.mockResolvedValue([{ ...userWalletSettings, WalletCreated: 1 }]);
96         });
98         it('should not autocreate wallet', async () => {
99             const { waitFor } = renderHook(() => useWalletAutoCreate({}));
101             await waitFor(() => expect(mockedGetUserWalletSettings).toHaveBeenCalled());
102             expect(mockedGetUserWalletSettings).toHaveBeenCalledWith();
104             // We need to wait some time to assert no call where sent, there is no way to do such an assertion without that
105             await wait(100);
107             expect(mockedCreateWallet).not.toHaveBeenCalled();
108         });
109     });
111     describe('when wasm is not supported on browser', () => {
112         it('should not autocreate wallet', async () => {
113             mockedIsWasmSupported.mockReturnValue(false);
114             renderHook(() => useWalletAutoCreate({ higherLevelPilot: true }));
116             // We need to wait some time to assert no call where sent, there is no way to do such an assertion without that
117             await wait(100);
119             expect(mockedLoadWasmModule).not.toHaveBeenCalled();
121             expect(mockedGetUserWalletSettings).not.toHaveBeenCalled();
122             expect(mockedCreateWallet).not.toHaveBeenCalled();
123         });
124     });
126     describe('when higher level pilot is false', () => {
127         it('should not autocreate wallet', async () => {
128             renderHook(() => useWalletAutoCreate({ higherLevelPilot: false }));
130             // We need to wait some time to assert no call where sent, there is no way to do such an assertion without that
131             await wait(100);
133             expect(mockedLoadWasmModule).not.toHaveBeenCalled();
135             expect(mockedGetUserWalletSettings).not.toHaveBeenCalled();
136             expect(mockedCreateWallet).not.toHaveBeenCalled();
137         });
138     });
140     describe('when user is not free and is part of org with > 1 max member', () => {
141         beforeEach(() => {
142             mockUseUser([{ isFree: false }]);
143             mockUseGetOrganization({ MaxMembers: 3 });
144         });
146         it('should not autocreate wallet', async () => {
147             renderHook(() => useWalletAutoCreate({}));
149             // We need to wait some time to assert no call where sent, there is no way to do such an assertion without that
150             await wait(100);
152             expect(mockedLoadWasmModule).not.toHaveBeenCalled();
154             expect(mockedGetUserWalletSettings).not.toHaveBeenCalled();
155             expect(mockedCreateWallet).not.toHaveBeenCalled();
156         });
157     });
159     describe('when user has not created a wallet yet', () => {
160         it('should autocreate wallet', async () => {
161             const [primaryKey] = addressWithKey.keys;
162             const { waitFor } = renderHook(() => useWalletAutoCreate({}));
164             await waitFor(() => expect(mockedCreateWallet).toHaveBeenCalled());
166             expect(mockedGetUserWalletSettings).toHaveBeenCalled();
167             expect(mockedGetUserWalletSettings).toHaveBeenCalledWith();
169             expect(mockedCreateWallet).toHaveBeenCalledWith(
170                 expect.any(String),
171                 false,
172                 1,
173                 false,
174                 'WALLET_TEST',
175                 expect.any(String),
176                 expect.any(String),
177                 expect.any(String),
178                 expect.any(String),
179                 undefined,
180                 true
181             );
183             await waitFor(() => expect(mockedCreateWalletAccount).toHaveBeenCalled());
184             expect(mockedCreateWalletAccount).toHaveBeenCalledWith(
185                 '0',
186                 // TODO: check derivation path when toString is impl for WasmDerivationPath
187                 expect.any(andromedaModule.WasmDerivationPath),
188                 expect.any(String),
189                 3
190             );
192             await waitFor(() => expect(mockedAddEmailAddress).toHaveBeenCalled());
193             expect(mockedAddEmailAddress).toHaveBeenCalledWith('0', '8', '0000001');
195             // Check if bitcoin address pool was filled
196             await waitFor(() => expect(mockedAddBitcoinAddresses).toHaveBeenCalled());
197             expect(mockedAddBitcoinAddresses).toHaveBeenCalledTimes(1);
198             expect(mockedAddBitcoinAddresses).toHaveBeenCalledWith(
199                 '0',
200                 '8',
201                 expect.any(andromedaModule.WasmApiBitcoinAddressesCreationPayload)
202             );
204             const bitcoinAddressesPayload = mockedAddBitcoinAddresses.mock.calls[0][2];
205             const bitcoinAddressesPayloadInner = bitcoinAddressesPayload[0];
207             expect(bitcoinAddressesPayloadInner[0].Data).toStrictEqual({
208                 BitcoinAddress: 'tb1q9zt888mn6ujzz3xkrsf8v73ngslfxgwqkng0lq',
209                 BitcoinAddressIndex: 0,
210                 BitcoinAddressSignature: expect.any(String),
211             });
212             await expectSignedBy(
213                 primaryKey,
214                 bitcoinAddressesPayloadInner[0].Data.BitcoinAddress,
215                 bitcoinAddressesPayloadInner[0].Data.BitcoinAddressSignature
216             );
218             expect(bitcoinAddressesPayloadInner[1].Data).toStrictEqual({
219                 BitcoinAddress: 'tb1qlsckafxe0v6xe8kt94svx77ld38qw2yhz7cakz',
220                 BitcoinAddressIndex: 1,
221                 BitcoinAddressSignature: expect.any(String),
222             });
223             await expectSignedBy(
224                 primaryKey,
225                 bitcoinAddressesPayloadInner[1].Data.BitcoinAddress,
226                 bitcoinAddressesPayloadInner[1].Data.BitcoinAddressSignature
227             );
229             expect(bitcoinAddressesPayloadInner[2].Data).toStrictEqual({
230                 BitcoinAddress: 'tb1qelseqz73w6p65s4a2pmfm0w48tjsvp54u2v9k3',
231                 BitcoinAddressIndex: 2,
232                 BitcoinAddressSignature: expect.any(String),
233             });
234             await expectSignedBy(
235                 primaryKey,
236                 bitcoinAddressesPayloadInner[2].Data.BitcoinAddress,
237                 bitcoinAddressesPayloadInner[2].Data.BitcoinAddressSignature
238             );
239         });
240     });