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';
12 mockUseGetOrganization,
15 } from '@proton/testing/lib/vitest';
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');
32 WasmMnemonic: vi.fn(() =>
33 (andromeda.WasmMnemonic as any).fromString(
34 'benefit indoor helmet wine exist height grain spot rely half beef nothing'
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());
58 beforeEach(async () => {
61 mockedIsWasmSupported.mockReturnValue(true);
62 mockedGetUserWalletSettings.mockResolvedValue([{ ...userWalletSettings, WalletCreated: 0 }]);
64 vi.spyOn(andromedaModule, 'WasmProtonWalletApiClient').mockReturnValue({
65 clients: vi.fn(() => {
67 network: { getNetwork: vi.fn(() => andromedaModule.WasmNetwork.Testnet) },
69 createWallet: mockedCreateWallet,
70 createWalletAccount: mockedCreateWalletAccount,
71 addEmailAddress: mockedAddEmailAddress,
72 updateWalletAccountFiatCurrency: mockedUpdateWalletAccountFiatCurrency,
75 addBitcoinAddresses: mockedAddBitcoinAddresses,
78 getUserSettings: mockedGetUserWalletSettings,
82 } as unknown as andromedaModule.WasmProtonWalletApiClient);
84 mockUseGetUserKeys(await getUserKeys());
86 mockUseGetOrganization();
87 mockUseAuthentication({ getPassword: vi.fn(() => 'testtest') });
89 addressWithKey = await getAddressKey();
90 mockUseGetAddresses([addressWithKey.address]);
93 describe('when user wallet has already created a wallet before', () => {
95 mockedGetUserWalletSettings.mockResolvedValue([{ ...userWalletSettings, WalletCreated: 1 }]);
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
107 expect(mockedCreateWallet).not.toHaveBeenCalled();
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
119 expect(mockedLoadWasmModule).not.toHaveBeenCalled();
121 expect(mockedGetUserWalletSettings).not.toHaveBeenCalled();
122 expect(mockedCreateWallet).not.toHaveBeenCalled();
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
133 expect(mockedLoadWasmModule).not.toHaveBeenCalled();
135 expect(mockedGetUserWalletSettings).not.toHaveBeenCalled();
136 expect(mockedCreateWallet).not.toHaveBeenCalled();
140 describe('when user is not free and is part of org with > 1 max member', () => {
142 mockUseUser([{ isFree: false }]);
143 mockUseGetOrganization({ MaxMembers: 3 });
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
152 expect(mockedLoadWasmModule).not.toHaveBeenCalled();
154 expect(mockedGetUserWalletSettings).not.toHaveBeenCalled();
155 expect(mockedCreateWallet).not.toHaveBeenCalled();
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(
183 await waitFor(() => expect(mockedCreateWalletAccount).toHaveBeenCalled());
184 expect(mockedCreateWalletAccount).toHaveBeenCalledWith(
186 // TODO: check derivation path when toString is impl for WasmDerivationPath
187 expect.any(andromedaModule.WasmDerivationPath),
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(
201 expect.any(andromedaModule.WasmApiBitcoinAddressesCreationPayload)
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),
212 await expectSignedBy(
214 bitcoinAddressesPayloadInner[0].Data.BitcoinAddress,
215 bitcoinAddressesPayloadInner[0].Data.BitcoinAddressSignature
218 expect(bitcoinAddressesPayloadInner[1].Data).toStrictEqual({
219 BitcoinAddress: 'tb1qlsckafxe0v6xe8kt94svx77ld38qw2yhz7cakz',
220 BitcoinAddressIndex: 1,
221 BitcoinAddressSignature: expect.any(String),
223 await expectSignedBy(
225 bitcoinAddressesPayloadInner[1].Data.BitcoinAddress,
226 bitcoinAddressesPayloadInner[1].Data.BitcoinAddressSignature
229 expect(bitcoinAddressesPayloadInner[2].Data).toStrictEqual({
230 BitcoinAddress: 'tb1qelseqz73w6p65s4a2pmfm0w48tjsvp54u2v9k3',
231 BitcoinAddressIndex: 2,
232 BitcoinAddressSignature: expect.any(String),
234 await expectSignedBy(
236 bitcoinAddressesPayloadInner[2].Data.BitcoinAddress,
237 bitcoinAddressesPayloadInner[2].Data.BitcoinAddressSignature