From 6f869b8d00fd2ff960c34769a121573761f8aaac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jirka=20Ba=C5=BEant?= Date: Mon, 20 Jan 2025 19:40:54 +0100 Subject: [PATCH] test(suite-native): introduce deviceAuthorizationSlice tests --- .../deviceAuthorizationSlice.test.ts | 343 ++++++++++++++++++ .../src/deviceAuthorizationSlice.ts | 32 +- 2 files changed, 358 insertions(+), 17 deletions(-) create mode 100644 suite-native/device-authorization/src/__tests__/deviceAuthorizationSlice.test.ts diff --git a/suite-native/device-authorization/src/__tests__/deviceAuthorizationSlice.test.ts b/suite-native/device-authorization/src/__tests__/deviceAuthorizationSlice.test.ts new file mode 100644 index 000000000000..17e244be7a43 --- /dev/null +++ b/suite-native/device-authorization/src/__tests__/deviceAuthorizationSlice.test.ts @@ -0,0 +1,343 @@ +import { UI } from '@trezor/connect'; +import { createDeviceInstanceThunk } from '@suite-common/wallet-core'; + +import { + deviceAuthorizationInitialState, + deviceAuthorizationReducer, + DeviceAuthorizationState, +} from '../deviceAuthorizationSlice'; +import { + cancelPassphraseAndSelectStandardDeviceThunk, + retryPassphraseAuthenticationThunk, + verifyPassphraseOnEmptyWalletThunk, +} from '../passphraseThunks'; + +describe('deviceAuthorizationSlice', () => { + const getDeviceAuthorizationState = ( + partialState: Partial, + ): DeviceAuthorizationState => ({ ...deviceAuthorizationInitialState, ...partialState }); + + describe('initial state', () => { + it('should have correct initial state', () => { + expect(deviceAuthorizationReducer(undefined, { type: 'unknown' })).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + }); + + describe('UI.REQUEST_PIN', () => { + it('should set `hasDeviceRequestedPin`', () => { + expect(deviceAuthorizationReducer(undefined, { type: UI.REQUEST_PIN })).toEqual({ + hasDeviceRequestedPin: true, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + }); + describe('UI.REQUEST_PASSPHRASE', () => { + it('should set hasDeviceRequestedPassphrase', () => { + const prevState = getDeviceAuthorizationState({ hasDeviceRequestedPin: true }); + + const state = deviceAuthorizationReducer(prevState, { type: UI.REQUEST_PASSPHRASE }); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: true, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + }); + + describe('UI.REQUEST_BUTTON', () => { + it('should react to code `ButtonRequest_PinEntry`', () => { + const prevState = getDeviceAuthorizationState({ + hasDeviceRequestedPin: true, + hasDeviceRequestedPassphrase: true, + }); + const action = { type: UI.REQUEST_BUTTON, payload: { code: 'ButtonRequest_PinEntry' } }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: true, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + + it('should react to code `PinMatrixRequestType_Current`', () => { + const prevState = getDeviceAuthorizationState({ + hasDeviceRequestedPin: true, + hasDeviceRequestedPassphrase: true, + }); + const action = { + type: UI.REQUEST_BUTTON, + payload: { code: 'PinMatrixRequestType_Current' }, + }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: true, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + + it('should react to code `ButtonRequest_Other`', () => { + const prevState = getDeviceAuthorizationState({ + hasDeviceRequestedPin: true, + hasDeviceRequestedPassphrase: true, + }); + const action = { + type: UI.REQUEST_BUTTON, + payload: { code: 'ButtonRequest_Other' }, + }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: true, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + }); + + describe('UI.CLOSE_UI_WINDOW', () => { + it('should set correct state when `isCreatingNewWalletInstance` is `true`', () => { + const prevState = getDeviceAuthorizationState({ + hasDeviceRequestedPin: true, + hasDeviceRequestedPassphrase: true, + isCreatingNewWalletInstance: true, + }); + const action = { type: UI.CLOSE_UI_WINDOW }; + + expect(deviceAuthorizationReducer(prevState, action)).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: true, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: true, + }); + }); + + it('should set correct state when `isCreatingNewWalletInstance` is `false`', () => { + const prevState = getDeviceAuthorizationState({ + hasDeviceRequestedPin: true, + hasDeviceRequestedPassphrase: true, + isCreatingNewWalletInstance: false, + }); + const action = { type: UI.CLOSE_UI_WINDOW }; + + expect(deviceAuthorizationReducer(prevState, action)).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + }); + + describe('verifyPassphraseOnEmptyWalletThunk', () => { + it('on pending should set `isVerifyingPassphraseOnEmptyWallet`', () => { + const prevState = getDeviceAuthorizationState({ + isVerifyingPassphraseOnEmptyWallet: false, + }); + + const action = { type: verifyPassphraseOnEmptyWalletThunk.pending.type }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: true, + isCreatingNewWalletInstance: false, + }); + }); + + it('should set `isVerifyingPassphraseOnEmptyWallet` to `false` when resolved', () => { + const prevState = getDeviceAuthorizationState({ + isVerifyingPassphraseOnEmptyWallet: true, + isCreatingNewWalletInstance: true, + }); + + const action = { type: verifyPassphraseOnEmptyWalletThunk.fulfilled.type }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + + it('should set `passphraseError` on rejection with error', () => { + const prevState = getDeviceAuthorizationState({ + isVerifyingPassphraseOnEmptyWallet: true, + isCreatingNewWalletInstance: true, + }); + + const action = { + type: verifyPassphraseOnEmptyWalletThunk.rejected.type, + payload: { error: 'no-device' }, + }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: { error: 'no-device' }, + isVerifyingPassphraseOnEmptyWallet: true, + isCreatingNewWalletInstance: true, + }); + }); + + it('should set `isVerifyingPassphraseOnEmptyWallet` to `false` when resolved', () => { + const prevState = getDeviceAuthorizationState({ + isVerifyingPassphraseOnEmptyWallet: true, + isCreatingNewWalletInstance: true, + }); + + const action = { type: verifyPassphraseOnEmptyWalletThunk.rejected.type }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + }); + + describe('cancelPassphraseAndSelectStandardDeviceThunk', () => { + it('should reset state while pending', () => { + const prevState = getDeviceAuthorizationState({ + isCreatingNewWalletInstance: true, + hasDeviceRequestedPassphrase: true, + passphraseError: { error: 'no-device' }, + }); + + const action = { type: cancelPassphraseAndSelectStandardDeviceThunk.pending.type }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + }); + + describe('retryPassphraseAuthenticationThunk', () => { + it('should reset passphraseError while pending', () => { + const prevState = getDeviceAuthorizationState({ + passphraseError: { error: 'no-device' }, + }); + + const action = { type: retryPassphraseAuthenticationThunk.pending.type }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + }); + + describe('createDeviceInstanceThunk', () => { + it('should set `isCreatingNewWalletInstance` while pending', () => { + const prevState = getDeviceAuthorizationState({ + isCreatingNewWalletInstance: false, + passphraseError: { error: 'no-device' }, + }); + + const action = { type: createDeviceInstanceThunk.pending.type }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: null, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: true, + }); + }); + + it('should set error on rejection', () => { + const prevState = getDeviceAuthorizationState({ + isCreatingNewWalletInstance: true, + hasDeviceRequestedPassphrase: true, + }); + const action = { + type: createDeviceInstanceThunk.rejected.type, + payload: { error: 'auth-failed' }, + }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: { error: 'auth-failed' }, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + }); + + describe('authorizeDeviceThunk', () => { + it('should set error on rejection', () => { + const prevState = getDeviceAuthorizationState({ + isCreatingNewWalletInstance: true, + hasDeviceRequestedPassphrase: true, + }); + const action = { + type: createDeviceInstanceThunk.rejected.type, + payload: { error: 'auth-failed' }, + }; + + const state = deviceAuthorizationReducer(prevState, action); + + expect(state).toEqual({ + hasDeviceRequestedPin: false, + hasDeviceRequestedPassphrase: false, + passphraseError: { error: 'auth-failed' }, + isVerifyingPassphraseOnEmptyWallet: false, + isCreatingNewWalletInstance: false, + }); + }); + }); +}); diff --git a/suite-native/device-authorization/src/deviceAuthorizationSlice.ts b/suite-native/device-authorization/src/deviceAuthorizationSlice.ts index e0aa0457b397..20504936db5e 100644 --- a/suite-native/device-authorization/src/deviceAuthorizationSlice.ts +++ b/suite-native/device-authorization/src/deviceAuthorizationSlice.ts @@ -1,4 +1,4 @@ -import { createSlice, isAnyOf, isRejected } from '@reduxjs/toolkit'; +import { createSlice, isAnyOf } from '@reduxjs/toolkit'; import { UI } from '@trezor/connect'; import { @@ -16,7 +16,7 @@ import { verifyPassphraseOnEmptyWalletThunk, } from './passphraseThunks'; -type DeviceAuthorizationState = { +export type DeviceAuthorizationState = { hasDeviceRequestedPin: boolean; hasDeviceRequestedPassphrase: boolean; passphraseError: @@ -32,7 +32,7 @@ type DeviceAuthorizationRootState = { deviceAuthorization: DeviceAuthorizationState; }; -const deviceAuthorizationInitialState: DeviceAuthorizationState = { +export const deviceAuthorizationInitialState: DeviceAuthorizationState = { hasDeviceRequestedPin: false, hasDeviceRequestedPassphrase: false, passphraseError: null, @@ -96,20 +96,18 @@ export const deviceAuthorizationSlice = createSlice({ state.isCreatingNewWalletInstance = true; state.passphraseError = null; }) - .addMatcher( - isAnyOf( - verifyPassphraseOnEmptyWalletThunk.fulfilled, - verifyPassphraseOnEmptyWalletThunk.rejected, - ), - (state, action) => { - if (isRejected(action) && action.payload) { - state.passphraseError = action.payload; - } else { - state.isVerifyingPassphraseOnEmptyWallet = false; - state.isCreatingNewWalletInstance = false; - } - }, - ) + .addCase(verifyPassphraseOnEmptyWalletThunk.fulfilled, state => { + state.isVerifyingPassphraseOnEmptyWallet = false; + state.isCreatingNewWalletInstance = false; + }) + .addCase(verifyPassphraseOnEmptyWalletThunk.rejected, (state, action) => { + if (action.payload) { + state.passphraseError = action.payload; + } else { + state.isVerifyingPassphraseOnEmptyWallet = false; + state.isCreatingNewWalletInstance = false; + } + }) .addMatcher( isAnyOf(authorizeDeviceThunk.rejected, createDeviceInstanceThunk.rejected), (state, { payload }) => {