From 7ec754d46b75e713edfbb76b8211dba4a2311e27 Mon Sep 17 00:00:00 2001 From: Simon Schmell Date: Sun, 31 Mar 2024 14:42:43 +0200 Subject: [PATCH 1/3] test: add memoized keychain tests --- .../__tests__/memoized-keychain.test.js | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 features/keychain/module/__tests__/memoized-keychain.test.js diff --git a/features/keychain/module/__tests__/memoized-keychain.test.js b/features/keychain/module/__tests__/memoized-keychain.test.js new file mode 100644 index 00000000..f7a098ea --- /dev/null +++ b/features/keychain/module/__tests__/memoized-keychain.test.js @@ -0,0 +1,95 @@ +import KeyIdentifier from '@exodus/key-identifier' +import BJSON from 'buffer-json' +import stableStringify from 'json-stable-stringify' + +import memoizedKeychainDefinition, { CACHE_KEY } from '../memoized-keychain' +import { Keychain } from '../keychain' + +const tick = () => new Promise((resolve) => setTimeout(resolve, 0)) + +describe('MemoizedKeychain', () => { + const keyId = new KeyIdentifier({ + assetName: 'ethereum', + derivationAlgorithm: 'BIP32', + derivationPath: "m/44'/60'/0'/0/1", + }) + + const cachedKey = { + xpub: 'cached-xpub', + publicKey: 'cached-compressed-public-key', + } + const retrievedKey = { + xpub: 'retrieved-xpub', + publicKey: 'retrieved-compressed-public-key', + privateKey: 'retrieved-compressed-public-key', + } + + const setup = async ({ prefilledCache = true } = {}) => { + jest.spyOn(Keychain.prototype, 'exportKey').mockResolvedValue(retrievedKey) + + const storage = { + get: jest.fn(), + set: jest.fn(), + delete: jest.fn(), + } + + storage.get.mockResolvedValue( + BJSON.stringify( + prefilledCache + ? { + [stableStringify(keyId)]: cachedKey, + } + : {} + ) + ) + + const keychain = memoizedKeychainDefinition.factory({ storage }) + await tick() + + return { + keychain, + storage, + } + } + + it('should load storage into memory when initialized', async () => { + const { keychain } = await setup() + + await expect(keychain.exportKey(keyId)).resolves.toEqual(cachedKey) + }) + + it('should get cached value when available', async () => { + const { keychain } = await setup() + + await expect(keychain.exportKey(keyId)).resolves.toEqual(cachedKey) + }) + + it('should avoid cache when requesting private key', async () => { + const { keychain } = await setup() + + await expect(keychain.exportKey(keyId, { exportPrivate: true })).resolves.toEqual(retrievedKey) + }) + + it('should only cache public key', async () => { + const { keychain, storage } = await setup({ prefilledCache: false }) + + await expect(keychain.exportKey(keyId)).resolves.toEqual(retrievedKey) + expect(storage.set).toHaveBeenCalledWith(CACHE_KEY, BJSON.stringify({ + [stableStringify(keyId)]: { + xpub: retrievedKey.xpub, + publicKey: retrievedKey.publicKey, + }, + })) + }) + + it('should properly clear all caches', async () => { + const { keychain, storage } = await setup() + await expect(keychain.exportKey(keyId)).resolves.toEqual(cachedKey) + + await keychain.clear() + + const key = await keychain.exportKey(keyId) + expect(key).toEqual(retrievedKey) + expect(storage.delete).toHaveBeenCalledWith(CACHE_KEY) + }) +}) From 014890f85e8240fb3196ae749e705299c6bd6cf6 Mon Sep 17 00:00:00 2001 From: Simon Schmell Date: Sun, 31 Mar 2024 14:43:20 +0200 Subject: [PATCH 2/3] fix: include memory cache on clear --- features/keychain/module/memoized-keychain.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/keychain/module/memoized-keychain.js b/features/keychain/module/memoized-keychain.js index 409f7b21..cc896ccb 100644 --- a/features/keychain/module/memoized-keychain.js +++ b/features/keychain/module/memoized-keychain.js @@ -5,7 +5,7 @@ import { Keychain } from './keychain' const keyIdToCacheKey = stableStringify -const CACHE_KEY = 'data' +export const CACHE_KEY = 'data' const getPublicKeyData = ({ xpub, publicKey }) => ({ xpub, publicKey }) @@ -52,6 +52,7 @@ class MemoizedKeychain extends Keychain { clear = async () => { await super.clear() + this.#publicKeys = Object.create(null) await this.#storage.delete(CACHE_KEY) } } From eb691039c42f7d17b2ef0e42fda5164f81bdfd36 Mon Sep 17 00:00:00 2001 From: Simon Schmell Date: Tue, 2 Apr 2024 19:35:47 +0200 Subject: [PATCH 3/3] chore: fix lint --- .../module/__tests__/memoized-keychain.test.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/features/keychain/module/__tests__/memoized-keychain.test.js b/features/keychain/module/__tests__/memoized-keychain.test.js index f7a098ea..024ef6e4 100644 --- a/features/keychain/module/__tests__/memoized-keychain.test.js +++ b/features/keychain/module/__tests__/memoized-keychain.test.js @@ -74,12 +74,15 @@ describe('MemoizedKeychain', () => { const { keychain, storage } = await setup({ prefilledCache: false }) await expect(keychain.exportKey(keyId)).resolves.toEqual(retrievedKey) - expect(storage.set).toHaveBeenCalledWith(CACHE_KEY, BJSON.stringify({ - [stableStringify(keyId)]: { - xpub: retrievedKey.xpub, - publicKey: retrievedKey.publicKey, - }, - })) + expect(storage.set).toHaveBeenCalledWith( + CACHE_KEY, + BJSON.stringify({ + [stableStringify(keyId)]: { + xpub: retrievedKey.xpub, + publicKey: retrievedKey.publicKey, + }, + }) + ) }) it('should properly clear all caches', async () => {