Skip to content

Commit

Permalink
CP-4906: Add tests for wallet connect v2 (#608)
Browse files Browse the repository at this point in the history
  • Loading branch information
atn4z7 authored Mar 14, 2023
1 parent d141163 commit 7b7ea8e
Show file tree
Hide file tree
Showing 45 changed files with 56,689 additions and 123 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ SENTRY_DSN=

WALLET_CONNECT_PROJECT_ID=

COINBASE_APP_ID=

# only required for development
TEST_MNEMONIC=

Expand Down
8 changes: 2 additions & 6 deletions __mocks__/@sentry/react-native.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
const SentryMock = {
init: () => jest.fn(),
captureException: jest.fn()
}

export default SentryMock
export const init = jest.fn()
export const captureException = jest.fn()
2 changes: 1 addition & 1 deletion app/screens/rpc/components/v2/SignTransaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { selectRequestStatus } from 'store/walletConnectV2'
import { useDappConnectionV2 } from 'hooks/useDappConnectionV2'
import { selectNetwork } from 'store/network'
import { NetworkLogo } from 'screens/network/NetworkLogo'
import { isAddressApproved } from 'store/walletConnectV2/handlers/eth_sign/utils'
import { isAddressApproved } from 'store/walletConnectV2/handlers/eth_sign/utils/isAddressApproved'
import { hexToBN } from '@avalabs/utils-sdk'
import RpcRequestBottomSheet from '../shared/RpcRequestBottomSheet'

Expand Down
2 changes: 1 addition & 1 deletion app/services/wallet/WalletService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import {
import { getEvmProvider } from 'services/network/utils/providerUtils'
import SentryWrapper from 'services/sentry/SentryWrapper'
import { Transaction } from '@sentry/types'
import { RpcMethod } from 'store/walletConnectV2'
import { Account } from 'store/account'
import { RpcMethod } from 'store/walletConnectV2/types'

class WalletService {
private mnemonic?: string
Expand Down
2 changes: 1 addition & 1 deletion app/store/bridge/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
BridgeTransaction,
CriticalConfig
} from '@avalabs/bridge-sdk'
import { selectActiveNetwork } from 'store/network'
import { selectActiveNetwork } from 'store/network/slice'
import { BridgeState, initialState } from 'store/bridge/types'

export const reducerName = 'bridge'
Expand Down
20 changes: 10 additions & 10 deletions app/store/middleware/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import {
TypedStartListening
} from '@reduxjs/toolkit'
import type { AppDispatch, RootState } from 'store'
import { addAppListeners } from 'store/app'
import { addBalanceListeners } from 'store/balance'
import { addAccountListeners } from 'store/account'
import { addNetworkListeners } from 'store/network'
import { addNetworkFeeListeners } from 'store/networkFee'
import { addBridgeListeners } from 'store/bridge'
import { addPosthogListeners } from 'store/posthog'
import { addWatchlistListeners } from 'store/watchlist'
import { addAppListeners } from 'store/app/listeners'
import { addBalanceListeners } from 'store/balance/listeners'
import { addAccountListeners } from 'store/account/listeners'
import { addNetworkListeners } from 'store/network/listeners'
import { addNetworkFeeListeners } from 'store/networkFee/index'
import { addBridgeListeners } from 'store/bridge/listeners'
import { addPosthogListeners } from 'store/posthog/listeners'
import { addWatchlistListeners } from 'store/watchlist/listeners'
import { addNftListeners } from 'store/nft/listeners'
import { addWCListeners as addWCListenersV1 } from 'store/walletConnect'
import { addWCListeners as addWCListenersV2 } from 'store/walletConnectV2'
import { addWCListeners as addWCListenersV1 } from 'store/walletConnect/listeners'
import { addWCListeners as addWCListenersV2 } from 'store/walletConnectV2/listeners'

export type AppStartListening = TypedStartListening<RootState, AppDispatch>
export type AppAddListener = TypedAddListener<RootState, AppDispatch>
Expand Down
2 changes: 1 addition & 1 deletion app/store/walletConnect/handlers/eth_sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import AppNavigation from 'navigation/AppNavigation'
import Logger from 'utils/Logger'
import * as Sentry from '@sentry/react-native'
import { RpcMethod } from 'store/walletConnectV2'
import { parseRequestParams } from 'store/walletConnectV2/handlers/eth_sign/utils'
import { parseRequestParams } from 'store/walletConnectV2/handlers/eth_sign/utils/parseRequestParams'
import {
TypedData,
OldTypedData
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { RpcMethod } from 'store/walletConnectV2'
import mockSession from 'tests/fixtures/walletConnect/session.json'
import mockAccounts from 'tests/fixtures/accounts.json'
import { avalancheGetAccountsHandler as handler } from './avalanche_getAccounts'

jest.mock('store/account', () => {
const actual = jest.requireActual('store/account')
return {
...actual,
selectAccounts: () => mockAccounts,
selectActiveAccount: () => mockAccounts[0]
}
})

const mockDispatch = jest.fn()
const mockListenerApi = {
getState: jest.fn(),
dispatch: mockDispatch
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any

const testMethod = 'avalanche_getAccounts' as RpcMethod.AVALANCHE_GET_ACCOUNTS

const testRequest = {
method: testMethod,
data: {
id: 1677366383831712,
topic: '3a094bf511357e0f48ff266f0b8d5b846fd3f7de4bd0824d976fdf4c5279b261',
params: {
request: {
method: testMethod,
params: {}
},
chainId: 'eip155:43113'
}
},
session: mockSession
}

describe('avalanche_getAccounts handler', () => {
it('should contain correct methods', () => {
expect(handler.methods).toEqual(['avalanche_getAccounts'])
})

describe('handle', () => {
it('should return success with the list of available accounts', async () => {
const result = await handler.handle(testRequest, mockListenerApi)

expect(result).toEqual({
success: true,
value: [
{
index: 0,
name: 'Account 1',
addressBTC: 'tb1qlzsvluv4cahzz8zzwud40x2hn3zq4c7zak6spw',
addressC: '0xcA0E993876152ccA6053eeDFC753092c8cE712D0',
active: true,
type: 'primary'
},
{
index: 1,
name: 'Account 2',
addressBTC: 'tb1qjmapax0vtca726g8kaermd5rzdljql66esxs49',
addressC: '0xC7E5ffBd7843EdB88cCB2ebaECAa07EC55c65318',
active: false,
type: 'primary'
}
]
})
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { ethErrors } from 'eth-rpc-errors'
import { RpcMethod } from 'store/walletConnectV2'
import mockSession from 'tests/fixtures/walletConnect/session.json'
import mockAccounts from 'tests/fixtures/accounts.json'
import AppNavigation from 'navigation/AppNavigation'
import * as Navigation from 'utils/Navigation'
import { setActiveAccountIndex } from 'store/account'
import { avalancheSelectAccountHandler as handler } from './avalanche_selectAccount'

jest.mock('store/account', () => {
const actual = jest.requireActual('store/account')
return {
...actual,
selectAccounts: () => mockAccounts,
selectActiveAccount: () => mockAccounts[0]
}
})

const mockNavigate = jest.fn()
jest.spyOn(Navigation, 'navigate').mockImplementation(mockNavigate)

const mockDispatch = jest.fn()
const mockListenerApi = {
getState: jest.fn(),
dispatch: mockDispatch
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any

const testMethod =
'avalanche_selectAccount' as RpcMethod.AVALANCHE_SELECT_ACCOUNT

const createRequest = (params: unknown) => {
return {
method: testMethod,
data: {
id: 1677366383831712,
topic: '3a094bf511357e0f48ff266f0b8d5b846fd3f7de4bd0824d976fdf4c5279b261',
params: {
request: {
method: testMethod,
params
},
chainId: 'eip155:43113'
}
},
session: mockSession
}
}

const testHandleInvalidParams = async (params: unknown) => {
const testRequest = createRequest(params)

const result = await handler.handle(testRequest, mockListenerApi)

expect(result).toEqual({
success: false,
error: ethErrors.rpc.invalidParams({
message: 'Account index is invalid'
})
})
}

const testApproveInvalidData = async (data: unknown) => {
const testRequest = createRequest([0])

const result = await handler.approve(
{ request: testRequest, data },
mockListenerApi
)

expect(result).toEqual({
success: false,
error: ethErrors.rpc.internal('Invalid approve data')
})
}

describe('avalanche_selectAccount handler', () => {
it('should contain correct methods', () => {
expect(handler.methods).toEqual(['avalanche_selectAccount'])
})

describe('handle', () => {
// eslint-disable-next-line jest/expect-expect
it('should return error when params are invalid', async () => {
const invalidParamsScenarios = [null, [], [null], [-1], ['1']]

for (const scenario of invalidParamsScenarios) {
await testHandleInvalidParams(scenario)
}
})

it('should return success when requested account is already active', async () => {
const testRequest = createRequest([0])

const result = await handler.handle(testRequest, mockListenerApi)

expect(result).toEqual({ success: true, value: null })
})

it('should return error when requested account does not exist', async () => {
const testRequest = createRequest([22])

const result = await handler.handle(testRequest, mockListenerApi)

expect(result).toEqual({
success: false,
error: ethErrors.rpc.resourceNotFound({
message: 'Requested account does not exist'
})
})
})

it('should display prompt and return success', async () => {
const testRequest = createRequest([1])

const result = await handler.handle(testRequest, mockListenerApi)

expect(mockNavigate).toHaveBeenCalledWith({
name: AppNavigation.Root.Wallet,
params: {
screen: AppNavigation.Modal.SelectAccountV2,
params: {
request: testRequest,
account: mockAccounts[1]
}
}
})

expect(result).toEqual({ success: true, value: expect.any(Symbol) })
})
})

describe('approve', () => {
// eslint-disable-next-line jest/expect-expect
it('should return error when approve data is invalid', async () => {
const invalidDataScenarios = [
null,
{},
{ account: null },
{ account: { address: '0x3B0d3329ec01047F1A03CcA8106f2915AdFDC3dD' } }
]

for (const scenario of invalidDataScenarios) {
await testApproveInvalidData(scenario)
}
})

it('should set requested account to active and return success', async () => {
const testRequest = createRequest([1])
const requestedAccount = mockAccounts[1]

const result = await handler.approve(
{ request: testRequest, data: { account: requestedAccount } },
mockListenerApi
)

expect(mockDispatch).toHaveBeenCalledWith(setActiveAccountIndex(1))

expect(result).toEqual({ success: true, value: [] })
})
})
})
Loading

0 comments on commit 7b7ea8e

Please sign in to comment.