From cea4508695c3955a976bc547fd0ba6e54c309d9f Mon Sep 17 00:00:00 2001 From: Robert Schlittler Date: Tue, 17 Dec 2024 10:12:12 +0100 Subject: [PATCH 1/7] feat(frontend): add sol type to idb.ts --- src/frontend/src/lib/types/idb.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/lib/types/idb.ts b/src/frontend/src/lib/types/idb.ts index 5a4e576ba7..70e43e1f06 100644 --- a/src/frontend/src/lib/types/idb.ts +++ b/src/frontend/src/lib/types/idb.ts @@ -1,4 +1,4 @@ -import type { Address, BtcAddress, EthAddress } from '$lib/types/address'; +import type { Address, BtcAddress, EthAddress, SolAddress } from '$lib/types/address'; import type { Principal } from '@dfinity/principal'; export interface IdbAddress { @@ -11,6 +11,8 @@ export type IdbBtcAddress = IdbAddress; export type IdbEthAddress = IdbAddress; +export type IdbSolAddress = IdbAddress; + export interface SetIdbAddressParams { address: IdbAddress; principal: Principal; From cf5c907f38ff241e44e0e543b7bc3152cb19e98d Mon Sep 17 00:00:00 2001 From: Robert Schlittler Date: Tue, 17 Dec 2024 10:24:37 +0100 Subject: [PATCH 2/7] feat(frontend): add sol to idb.api, add tests for all networks in idb.api.spec.ts --- src/frontend/src/lib/api/idb.api.ts | 53 ++++- .../src/tests/lib/api/idb.api.spec.ts | 188 ++++++++++++++++++ 2 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 src/frontend/src/tests/lib/api/idb.api.spec.ts diff --git a/src/frontend/src/lib/api/idb.api.ts b/src/frontend/src/lib/api/idb.api.ts index 706bc901db..c4e0851bb9 100644 --- a/src/frontend/src/lib/api/idb.api.ts +++ b/src/frontend/src/lib/api/idb.api.ts @@ -1,8 +1,19 @@ import { browser } from '$app/environment'; import { ETHEREUM_NETWORK_SYMBOL } from '$env/networks/networks.env'; +import { + SOLANA_DEVNET_NETWORK_SYMBOL, + SOLANA_LOCAL_NETWORK_SYMBOL, + SOLANA_MAINNET_NETWORK_SYMBOL, + SOLANA_TESTNET_NETWORK_SYMBOL +} from '$env/networks/networks.sol.env'; import { BTC_MAINNET_SYMBOL, BTC_TESTNET_SYMBOL } from '$env/tokens/tokens.btc.env'; -import type { BtcAddress, EthAddress } from '$lib/types/address'; -import type { IdbBtcAddress, IdbEthAddress, SetIdbAddressParams } from '$lib/types/idb'; +import type { BtcAddress, EthAddress, SolAddress } from '$lib/types/address'; +import type { + IdbBtcAddress, + IdbEthAddress, + IdbSolAddress, + SetIdbAddressParams +} from '$lib/types/idb'; import type { Principal } from '@dfinity/principal'; import { isNullish } from '@dfinity/utils'; import { createStore, del, get, set, update, type UseStore } from 'idb-keyval'; @@ -16,6 +27,11 @@ const idbBtcAddressesStoreTestnet = idbAddressesStore(BTC_TESTNET_SYMBOL.toLower const idbEthAddressesStore = idbAddressesStore(ETHEREUM_NETWORK_SYMBOL.toLowerCase()); +const idbSolAddressesStoreMainnet = idbAddressesStore(SOLANA_MAINNET_NETWORK_SYMBOL.toLowerCase()); +const idbSolAddressesStoreTestnet = idbAddressesStore(SOLANA_TESTNET_NETWORK_SYMBOL.toLowerCase()); +const idbSolAddressesStoreDevnet = idbAddressesStore(SOLANA_DEVNET_NETWORK_SYMBOL.toLowerCase()); +const idbSolAddressesStoreLocal = idbAddressesStore(SOLANA_LOCAL_NETWORK_SYMBOL.toLowerCase()); + export const setIdbBtcAddressMainnet = ({ address, principal @@ -34,6 +50,30 @@ export const setIdbEthAddress = ({ }: SetIdbAddressParams): Promise => set(principal.toText(), address, idbEthAddressesStore); +export const setIdbSolAddressMainnet = ({ + address, + principal +}: SetIdbAddressParams): Promise => + set(principal.toText(), address, idbSolAddressesStoreMainnet); + +export const setIdbSolAddressTestnet = ({ + address, + principal +}: SetIdbAddressParams): Promise => + set(principal.toText(), address, idbSolAddressesStoreTestnet); + +export const setIdbSolAddressDevnet = ({ + address, + principal +}: SetIdbAddressParams): Promise => + set(principal.toText(), address, idbSolAddressesStoreDevnet); + +export const setIdbSolAddressLocal = ({ + address, + principal +}: SetIdbAddressParams): Promise => + set(principal.toText(), address, idbSolAddressesStoreLocal); + const updateIdbAddressLastUsage = ({ principal, idbAddressesStore @@ -62,14 +102,23 @@ export const updateIdbBtcAddressMainnetLastUsage = (principal: Principal): Promi export const updateIdbEthAddressLastUsage = (principal: Principal): Promise => updateIdbAddressLastUsage({ principal, idbAddressesStore: idbEthAddressesStore }); +export const updateIdbSolAddressMainnetLastUsage = (principal: Principal): Promise => + updateIdbAddressLastUsage({ principal, idbAddressesStore: idbSolAddressesStoreMainnet }); + export const getIdbBtcAddressMainnet = (principal: Principal): Promise => get(principal.toText(), idbBtcAddressesStoreMainnet); export const getIdbEthAddress = (principal: Principal): Promise => get(principal.toText(), idbEthAddressesStore); +export const getIdbSolAddressMainnet = (principal: Principal): Promise => + get(principal.toText(), idbSolAddressesStoreMainnet); + export const deleteIdbBtcAddressMainnet = (principal: Principal): Promise => del(principal.toText(), idbBtcAddressesStoreMainnet); export const deleteIdbEthAddress = (principal: Principal): Promise => del(principal.toText(), idbEthAddressesStore); + +export const deleteIdbSolAddressMainnet = (principal: Principal): Promise => + del(principal.toText(), idbSolAddressesStoreMainnet); diff --git a/src/frontend/src/tests/lib/api/idb.api.spec.ts b/src/frontend/src/tests/lib/api/idb.api.spec.ts new file mode 100644 index 0000000000..21d6c6ff7f --- /dev/null +++ b/src/frontend/src/tests/lib/api/idb.api.spec.ts @@ -0,0 +1,188 @@ +import { + deleteIdbBtcAddressMainnet, + deleteIdbEthAddress, + deleteIdbSolAddressMainnet, + getIdbBtcAddressMainnet, + getIdbEthAddress, + getIdbSolAddressMainnet, + setIdbBtcAddressMainnet, + setIdbEthAddress, + setIdbSolAddressMainnet, + updateIdbBtcAddressMainnetLastUsage, + updateIdbEthAddressLastUsage, + updateIdbSolAddressMainnetLastUsage +} from '$lib/api/idb.api'; +import { Principal } from '@dfinity/principal'; +import * as idbKeyval from 'idb-keyval'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('idb-keyval', () => ({ + createStore: vi.fn(() => ({ + /* mock store implementation */ + })), + set: vi.fn(), + get: vi.fn(), + del: vi.fn(), + update: vi.fn() +})); + +vi.mock('$app/environment', () => ({ + browser: true +})); + +describe('idb.api', () => { + const mockPrincipal = Principal.fromText('2vxsx-fae'); + const mockAddress = { + address: '0x123', + lastUsedTimestamp: Date.now(), + createdAtTimestamp: Date.now() + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('BTC operations', () => { + it('should set BTC address', async () => { + await setIdbBtcAddressMainnet({ + principal: mockPrincipal, + address: mockAddress + }); + + expect(idbKeyval.set).toHaveBeenCalledWith( + mockPrincipal.toText(), + mockAddress, + expect.any(Object) + ); + }); + + it('should get BTC address', async () => { + vi.mocked(idbKeyval.get).mockResolvedValue(mockAddress); + + const result = await getIdbBtcAddressMainnet(mockPrincipal); + + expect(result).toEqual(mockAddress); + expect(idbKeyval.get).toHaveBeenCalledWith(mockPrincipal.toText(), expect.any(Object)); + }); + + it('should delete BTC address', async () => { + await deleteIdbBtcAddressMainnet(mockPrincipal); + + expect(idbKeyval.del).toHaveBeenCalledWith(mockPrincipal.toText(), expect.any(Object)); + }); + + it('should update BTC address last usage', async () => { + vi.mocked(idbKeyval.update).mockImplementation(async (_, updater) => { + const updated = updater(mockAddress) as typeof mockAddress; + expect(updated.lastUsedTimestamp).toBeGreaterThan(mockAddress.lastUsedTimestamp); + }); + + await updateIdbBtcAddressMainnetLastUsage(mockPrincipal); + + expect(idbKeyval.update).toHaveBeenCalled(); + }); + }); + + describe('ETH operations', () => { + it('should set ETH address', async () => { + await setIdbEthAddress({ + principal: mockPrincipal, + address: mockAddress + }); + + expect(idbKeyval.set).toHaveBeenCalledWith( + mockPrincipal.toText(), + mockAddress, + expect.any(Object) + ); + }); + + it('should get ETH address', async () => { + vi.mocked(idbKeyval.get).mockResolvedValue(mockAddress); + + const result = await getIdbEthAddress(mockPrincipal); + + expect(result).toEqual(mockAddress); + expect(idbKeyval.get).toHaveBeenCalledWith(mockPrincipal.toText(), expect.any(Object)); + }); + + it('should delete ETH address', async () => { + await deleteIdbEthAddress(mockPrincipal); + + expect(idbKeyval.del).toHaveBeenCalledWith(mockPrincipal.toText(), expect.any(Object)); + }); + + it('should update ETH address last usage', async () => { + vi.mocked(idbKeyval.update).mockImplementation(async (_, updater) => { + const updated = updater(mockAddress) as typeof mockAddress; + expect(updated.lastUsedTimestamp).toBeGreaterThan(mockAddress.lastUsedTimestamp); + }); + + await updateIdbEthAddressLastUsage(mockPrincipal); + + expect(idbKeyval.update).toHaveBeenCalled(); + }); + }); + + describe('SOL operations', () => { + it('should set SOL address', async () => { + await setIdbSolAddressMainnet({ + principal: mockPrincipal, + address: mockAddress + }); + + expect(idbKeyval.set).toHaveBeenCalledWith( + mockPrincipal.toText(), + mockAddress, + expect.any(Object) + ); + }); + + it('should get SOL address', async () => { + vi.mocked(idbKeyval.get).mockResolvedValue(mockAddress); + + const result = await getIdbSolAddressMainnet(mockPrincipal); + + expect(result).toEqual(mockAddress); + expect(idbKeyval.get).toHaveBeenCalledWith(mockPrincipal.toText(), expect.any(Object)); + }); + + it('should delete SOL address', async () => { + await deleteIdbSolAddressMainnet(mockPrincipal); + + expect(idbKeyval.del).toHaveBeenCalledWith(mockPrincipal.toText(), expect.any(Object)); + }); + + it('should update SOL address last usage', async () => { + vi.mocked(idbKeyval.update).mockImplementation(async (_, updater) => { + const updated = updater(mockAddress) as typeof mockAddress; + expect(updated.lastUsedTimestamp).toBeGreaterThan(mockAddress.lastUsedTimestamp); + }); + + await updateIdbSolAddressMainnetLastUsage(mockPrincipal); + + expect(idbKeyval.update).toHaveBeenCalled(); + }); + }); + + describe('Edge cases', () => { + it('should handle undefined address when updating last usage', async () => { + vi.mocked(idbKeyval.update).mockImplementation(async (_, updater) => { + const result = updater(undefined); + expect(result).toBeUndefined(); + }); + + await updateIdbBtcAddressMainnetLastUsage(mockPrincipal); + + expect(idbKeyval.update).toHaveBeenCalled(); + }); + + it('should return undefined when getting non-existent address', async () => { + vi.mocked(idbKeyval.get).mockResolvedValue(undefined); + + const result = await getIdbBtcAddressMainnet(mockPrincipal); + + expect(result).toBeUndefined(); + }); + }); +}); From 8bfc6c28375815fc423888e5001aae54a9dfc51b Mon Sep 17 00:00:00 2001 From: Robert Schlittler Date: Tue, 17 Dec 2024 10:31:55 +0100 Subject: [PATCH 3/7] feat(frontend): fix linting --- src/frontend/src/tests/lib/api/idb.api.spec.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/tests/lib/api/idb.api.spec.ts b/src/frontend/src/tests/lib/api/idb.api.spec.ts index 21d6c6ff7f..4f3d667f17 100644 --- a/src/frontend/src/tests/lib/api/idb.api.spec.ts +++ b/src/frontend/src/tests/lib/api/idb.api.spec.ts @@ -72,9 +72,11 @@ describe('idb.api', () => { }); it('should update BTC address last usage', async () => { - vi.mocked(idbKeyval.update).mockImplementation(async (_, updater) => { + // eslint-disable-next-line local-rules/prefer-object-params + vi.mocked(idbKeyval.update).mockImplementation((_, updater) => { const updated = updater(mockAddress) as typeof mockAddress; expect(updated.lastUsedTimestamp).toBeGreaterThan(mockAddress.lastUsedTimestamp); + return Promise.resolve(); }); await updateIdbBtcAddressMainnetLastUsage(mockPrincipal); @@ -113,9 +115,11 @@ describe('idb.api', () => { }); it('should update ETH address last usage', async () => { - vi.mocked(idbKeyval.update).mockImplementation(async (_, updater) => { + // eslint-disable-next-line local-rules/prefer-object-params + vi.mocked(idbKeyval.update).mockImplementation((_, updater) => { const updated = updater(mockAddress) as typeof mockAddress; expect(updated.lastUsedTimestamp).toBeGreaterThan(mockAddress.lastUsedTimestamp); + return Promise.resolve(); }); await updateIdbEthAddressLastUsage(mockPrincipal); @@ -154,9 +158,11 @@ describe('idb.api', () => { }); it('should update SOL address last usage', async () => { - vi.mocked(idbKeyval.update).mockImplementation(async (_, updater) => { + // eslint-disable-next-line local-rules/prefer-object-params + vi.mocked(idbKeyval.update).mockImplementation((_, updater) => { const updated = updater(mockAddress) as typeof mockAddress; expect(updated.lastUsedTimestamp).toBeGreaterThan(mockAddress.lastUsedTimestamp); + return Promise.resolve(); }); await updateIdbSolAddressMainnetLastUsage(mockPrincipal); @@ -167,9 +173,11 @@ describe('idb.api', () => { describe('Edge cases', () => { it('should handle undefined address when updating last usage', async () => { - vi.mocked(idbKeyval.update).mockImplementation(async (_, updater) => { + // eslint-disable-next-line local-rules/prefer-object-params + vi.mocked(idbKeyval.update).mockImplementation((_, updater) => { const result = updater(undefined); expect(result).toBeUndefined(); + return Promise.resolve(); }); await updateIdbBtcAddressMainnetLastUsage(mockPrincipal); From b042a3e702a763b004c96e775a9fcba95275fab3 Mon Sep 17 00:00:00 2001 From: Robert Schlittler Date: Tue, 17 Dec 2024 10:42:41 +0100 Subject: [PATCH 4/7] feat(frontend): add usage in auth.services.ts --- src/frontend/src/lib/services/auth.services.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/lib/services/auth.services.ts b/src/frontend/src/lib/services/auth.services.ts index 7309d0c054..162e5219c7 100644 --- a/src/frontend/src/lib/services/auth.services.ts +++ b/src/frontend/src/lib/services/auth.services.ts @@ -1,4 +1,4 @@ -import { deleteIdbBtcAddressMainnet, deleteIdbEthAddress } from '$lib/api/idb.api'; +import { deleteIdbBtcAddressMainnet, deleteIdbEthAddress, deleteIdbSolAddressMainnet } from '$lib/api/idb.api'; import { TRACK_COUNT_SIGN_IN_SUCCESS, TRACK_SIGN_IN_CANCELLED_COUNT, @@ -110,6 +110,8 @@ const emptyIdbBtcAddressMainnet = (): Promise => emptyIdbAddress(deleteIdb const emptyIdbEthAddress = (): Promise => emptyIdbAddress(deleteIdbEthAddress); +const emptyIdbSolAddress = (): Promise => emptyIdbAddress(deleteIdbSolAddressMainnet); + // eslint-disable-next-line require-await const clearTestnetsOption = async () => { testnetsStore.reset({ key: 'testnets' }); @@ -128,7 +130,7 @@ const logout = async ({ busy.start(); if (clearStorages) { - await Promise.all([emptyIdbBtcAddressMainnet(), emptyIdbEthAddress(), clearTestnetsOption()]); + await Promise.all([emptyIdbBtcAddressMainnet(), emptyIdbEthAddress(), emptyIdbSolAddress(), clearTestnetsOption()]); } await authStore.signOut(); From bde0d46673858b717daa913d9c99141ea2947371 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:44:19 +0000 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=A4=96=20Apply=20formatting=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/lib/services/auth.services.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/lib/services/auth.services.ts b/src/frontend/src/lib/services/auth.services.ts index 162e5219c7..b819da1372 100644 --- a/src/frontend/src/lib/services/auth.services.ts +++ b/src/frontend/src/lib/services/auth.services.ts @@ -1,4 +1,8 @@ -import { deleteIdbBtcAddressMainnet, deleteIdbEthAddress, deleteIdbSolAddressMainnet } from '$lib/api/idb.api'; +import { + deleteIdbBtcAddressMainnet, + deleteIdbEthAddress, + deleteIdbSolAddressMainnet +} from '$lib/api/idb.api'; import { TRACK_COUNT_SIGN_IN_SUCCESS, TRACK_SIGN_IN_CANCELLED_COUNT, @@ -130,7 +134,12 @@ const logout = async ({ busy.start(); if (clearStorages) { - await Promise.all([emptyIdbBtcAddressMainnet(), emptyIdbEthAddress(), emptyIdbSolAddress(), clearTestnetsOption()]); + await Promise.all([ + emptyIdbBtcAddressMainnet(), + emptyIdbEthAddress(), + emptyIdbSolAddress(), + clearTestnetsOption() + ]); } await authStore.signOut(); From 54d0cac10c30e1bf3c3914091a172e9dea086969 Mon Sep 17 00:00:00 2001 From: Robert Schlittler Date: Tue, 17 Dec 2024 11:00:58 +0100 Subject: [PATCH 6/7] feat(frontend): MR feedback, reuse mock principal --- src/frontend/src/tests/lib/api/idb.api.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/frontend/src/tests/lib/api/idb.api.spec.ts b/src/frontend/src/tests/lib/api/idb.api.spec.ts index 4f3d667f17..95928b2342 100644 --- a/src/frontend/src/tests/lib/api/idb.api.spec.ts +++ b/src/frontend/src/tests/lib/api/idb.api.spec.ts @@ -12,9 +12,9 @@ import { updateIdbEthAddressLastUsage, updateIdbSolAddressMainnetLastUsage } from '$lib/api/idb.api'; -import { Principal } from '@dfinity/principal'; import * as idbKeyval from 'idb-keyval'; import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { mockPrincipal } from '$tests/mocks/identity.mock'; vi.mock('idb-keyval', () => ({ createStore: vi.fn(() => ({ @@ -31,7 +31,6 @@ vi.mock('$app/environment', () => ({ })); describe('idb.api', () => { - const mockPrincipal = Principal.fromText('2vxsx-fae'); const mockAddress = { address: '0x123', lastUsedTimestamp: Date.now(), From 4ff15e292f15b9533864f44b7eda6a6a4160660b Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:03:00 +0000 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=A4=96=20Apply=20formatting=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/tests/lib/api/idb.api.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/tests/lib/api/idb.api.spec.ts b/src/frontend/src/tests/lib/api/idb.api.spec.ts index 95928b2342..0877c663c8 100644 --- a/src/frontend/src/tests/lib/api/idb.api.spec.ts +++ b/src/frontend/src/tests/lib/api/idb.api.spec.ts @@ -12,9 +12,9 @@ import { updateIdbEthAddressLastUsage, updateIdbSolAddressMainnetLastUsage } from '$lib/api/idb.api'; +import { mockPrincipal } from '$tests/mocks/identity.mock'; import * as idbKeyval from 'idb-keyval'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { mockPrincipal } from '$tests/mocks/identity.mock'; vi.mock('idb-keyval', () => ({ createStore: vi.fn(() => ({