From 19202f30080428274f63d44171dd2fc51745caa8 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Wed, 8 Jan 2025 14:05:02 +0100 Subject: [PATCH] Add more tests --- packages/snaps-controllers/coverage.json | 6 +- .../src/snaps/SnapController.test.tsx | 71 +++++++++++++++++++ .../src/test-utils/controller.ts | 6 +- packages/snaps-utils/coverage.json | 6 +- packages/snaps-utils/src/mutex.test.ts | 38 ++++++++++ 5 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 packages/snaps-utils/src/mutex.test.ts diff --git a/packages/snaps-controllers/coverage.json b/packages/snaps-controllers/coverage.json index 357e17f097..1f48e5bdf6 100644 --- a/packages/snaps-controllers/coverage.json +++ b/packages/snaps-controllers/coverage.json @@ -1,6 +1,6 @@ { - "branches": 93.06, + "branches": 92.41, "functions": 96.54, - "lines": 98.02, - "statements": 97.74 + "lines": 97.99, + "statements": 97.71 } diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.tsx b/packages/snaps-controllers/src/snaps/SnapController.test.tsx index 0e28c6f207..9f6b494f9b 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.tsx +++ b/packages/snaps-controllers/src/snaps/SnapController.test.tsx @@ -60,6 +60,7 @@ import { AssertionError, base64ToBytes, stringToBytes, + createDeferredPromise, } from '@metamask/utils'; import { File } from 'buffer'; import { webcrypto } from 'crypto'; @@ -78,6 +79,7 @@ import { getNodeEESMessenger, getPersistedSnapsState, getSnapController, + getSnapControllerEncryptor, getSnapControllerMessenger, getSnapControllerOptions, getSnapControllerWithEES, @@ -9164,6 +9166,40 @@ describe('SnapController', () => { snapController.destroy(); }); + + it('logs an error message if the state fails to persist', async () => { + const messenger = getSnapControllerMessenger(); + + const errorValue = new Error('Failed to persist state.'); + const snapController = getSnapController( + getSnapControllerOptions({ + messenger, + state: { + snaps: getPersistedSnapsState(), + }, + // @ts-expect-error - Missing required properties. + encryptor: { + ...getSnapControllerEncryptor(), + encryptWithKey: jest.fn().mockRejectedValue(errorValue), + }, + }), + ); + + const { promise, resolve } = createDeferredPromise(); + const error = jest.spyOn(console, 'error').mockImplementation(resolve); + + await messenger.call( + 'SnapController:updateSnapState', + MOCK_SNAP_ID, + { foo: 'bar' }, + true, + ); + + await promise; + expect(error).toHaveBeenCalledWith(errorValue); + + snapController.destroy(); + }); }); describe('SnapController:clearSnapState', () => { @@ -9222,6 +9258,41 @@ describe('SnapController', () => { snapController.destroy(); }); + + it('logs an error message if the state fails to persist', async () => { + const messenger = getSnapControllerMessenger(); + + const errorValue = new Error('Failed to persist state.'); + const snapController = getSnapController( + getSnapControllerOptions({ + messenger, + state: { + snaps: getPersistedSnapsState(), + }, + // @ts-expect-error - Missing required properties. + encryptor: { + ...getSnapControllerEncryptor(), + encryptWithKey: jest.fn().mockRejectedValue(errorValue), + }, + }), + ); + + const { promise, resolve } = createDeferredPromise(); + const error = jest.spyOn(console, 'error').mockImplementation(resolve); + + // @ts-expect-error - Property `update` is protected. + // eslint-disable-next-line jest/prefer-spy-on + snapController.update = jest.fn().mockImplementation(() => { + throw errorValue; + }); + + await messenger.call('SnapController:clearSnapState', MOCK_SNAP_ID, true); + + await promise; + expect(error).toHaveBeenCalledWith(errorValue); + + snapController.destroy(); + }); }); describe('SnapController:updateBlockedSnaps', () => { diff --git a/packages/snaps-controllers/src/test-utils/controller.ts b/packages/snaps-controllers/src/test-utils/controller.ts index 9040a30d68..1602db2e66 100644 --- a/packages/snaps-controllers/src/test-utils/controller.ts +++ b/packages/snaps-controllers/src/test-utils/controller.ts @@ -11,6 +11,8 @@ import { exportKey, generateSalt, isVaultUpdated, + encrypt, + decrypt, } from '@metamask/browser-passworder'; import type { PermissionConstraint, @@ -540,8 +542,10 @@ export const DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS = { }, }; -const getSnapControllerEncryptor = () => { +export const getSnapControllerEncryptor = () => { return { + encrypt, + decrypt, encryptWithKey, decryptWithKey, keyFromPassword: async ( diff --git a/packages/snaps-utils/coverage.json b/packages/snaps-utils/coverage.json index a9248b7438..80c85671b3 100644 --- a/packages/snaps-utils/coverage.json +++ b/packages/snaps-utils/coverage.json @@ -1,6 +1,6 @@ { "branches": 99.74, - "functions": 98.93, - "lines": 99.46, - "statements": 96.31 + "functions": 98.95, + "lines": 99.47, + "statements": 96.33 } diff --git a/packages/snaps-utils/src/mutex.test.ts b/packages/snaps-utils/src/mutex.test.ts new file mode 100644 index 0000000000..794b500962 --- /dev/null +++ b/packages/snaps-utils/src/mutex.test.ts @@ -0,0 +1,38 @@ +import { createDeferredPromise } from '@metamask/utils'; + +import { withMutex } from './mutex'; + +describe('withMutex', () => { + it('runs the function with a mutex', async () => { + jest.useFakeTimers(); + + const { promise, resolve: resolveDeferred } = createDeferredPromise(); + + const fn = jest.fn().mockImplementation(async () => { + return await new Promise((resolve) => { + resolveDeferred(); + setTimeout(() => { + resolve(); + }, 1000); + }); + }); + + const wrappedFn = withMutex(fn); + + const first = wrappedFn(); + const second = wrappedFn(); + + await promise; + jest.advanceTimersByTime(1000); + + expect(fn).toHaveBeenCalledTimes(1); + + await first; + + jest.advanceTimersByTime(1000); + + await second; + + expect(fn).toHaveBeenCalledTimes(2); + }); +});