From 78ef8d3a5a956cd7ec263028dce24533a4ae53d9 Mon Sep 17 00:00:00 2001 From: katspaugh <381895+katspaugh@users.noreply.github.com> Date: Fri, 22 Apr 2022 14:05:00 +0200 Subject: [PATCH] Tests for services (#22) * Add tests for formatters and extractTxInfo * Add path rewrite tests --- services/__tests__/extractTxInfo.test.ts | 118 ++++++++++++++++++++++ services/__tests__/formatters.test.ts | 32 ++++++ services/__tests__/useChains.test.ts | 2 +- services/__tests__/usePathRewrite.test.ts | 77 ++++++++++++++ services/__tests__/validation.test.ts | 14 +++ services/formatters.ts | 4 +- 6 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 services/__tests__/extractTxInfo.test.ts create mode 100644 services/__tests__/formatters.test.ts create mode 100644 services/__tests__/usePathRewrite.test.ts create mode 100644 services/__tests__/validation.test.ts diff --git a/services/__tests__/extractTxInfo.test.ts b/services/__tests__/extractTxInfo.test.ts new file mode 100644 index 0000000000..387562563e --- /dev/null +++ b/services/__tests__/extractTxInfo.test.ts @@ -0,0 +1,118 @@ +import type { TransactionDetails, TransactionSummary } from '@gnosis.pm/safe-react-gateway-sdk' +import extractTxInfo from '../extractTxInfo' + +describe('extractTxInfo', () => { + it('should extract tx info for an ETH transfer', () => { + const txSummary = { + txInfo: { + type: 'Transfer', + transferInfo: { + type: 'NATIVE_COIN', + value: '1000000000000000000', + }, + recipient: { + value: '0x1234567890123456789012345678901234567890', + }, + }, + executionInfo: { + nonce: '0', + }, + } as unknown as TransactionSummary + + const txDetails = { + txData: { + operation: 'CALL', + value: '1000000000000000000', + data: '0x1234567890123456789012345678901234567890', + }, + detailedExecutionInfo: { + baseGas: 21000, + gasPrice: '10000000000', + safeTxGas: 11000, + gasToken: '0x0000000000000000000000000000000000000000', + + refundReceiver: { + type: 'Address', + value: '0x1234567890123456789012345678901234567890', + }, + confirmations: [{ signer: { value: '0x1234567890123456789012345678901234567890' }, signature: '0x123' }], + }, + } as unknown as TransactionDetails + + expect(extractTxInfo(txSummary, txDetails)).toEqual({ + txParams: { + data: '0x', + baseGas: 21000, + gasPrice: 10000000000, + safeTxGas: 11000, + gasToken: '0x0000000000000000000000000000000000000000', + nonce: '0', + refundReceiver: '0x1234567890123456789012345678901234567890', + value: '1000000000000000000', + to: '0x1234567890123456789012345678901234567890', + operation: 'CALL', + }, + signatures: { + '0x1234567890123456789012345678901234567890': '0x123', + }, + }) + }) + + it('should extract tx info for an ERC20 token transfer', () => { + const txSummary = { + txInfo: { + type: 'Transfer', + transferInfo: { + type: 'ERC20', + value: '1000000000000000000', + tokenAddress: '0xa74476443119A942dE498590Fe1f2454d7D4aC0d', + }, + recipient: { + value: '0x1234567890123456789012345678901234567890', + }, + }, + executionInfo: { + nonce: '0', + }, + } as unknown as TransactionSummary + + const txDetails = { + txData: { + operation: 'CALL', + value: '0x0', + hexData: '0x546785', + data: '0x1234567890123456789012345678901234567890', + }, + detailedExecutionInfo: { + baseGas: 21000, + gasPrice: '10000000000', + safeTxGas: 11000, + gasToken: '0x0000000000000000000000000000000000000000', + + refundReceiver: { + type: 'Address', + value: '0x1234567890123456789012345678901234567890', + }, + confirmations: [{ signer: { value: '0x1234567890123456789012345678901234567890' }, signature: '0x123' }], + }, + } as unknown as TransactionDetails + + expect(extractTxInfo(txSummary, txDetails)).toEqual({ + txParams: { + data: '0x546785', + baseGas: 21000, + gasPrice: 10000000000, + safeTxGas: 11000, + gasToken: '0x0000000000000000000000000000000000000000', + nonce: '0', + refundReceiver: '0x1234567890123456789012345678901234567890', + value: '0x0', + to: '0xa74476443119A942dE498590Fe1f2454d7D4aC0d', + operation: 'CALL', + }, + signatures: { + '0x1234567890123456789012345678901234567890': '0x123', + }, + }) + }) +}) diff --git a/services/__tests__/formatters.test.ts b/services/__tests__/formatters.test.ts new file mode 100644 index 0000000000..9ac5ee353a --- /dev/null +++ b/services/__tests__/formatters.test.ts @@ -0,0 +1,32 @@ +import BigNumber from 'bignumber.js' +import * as formatters from '../formatters' + +describe('shortenAddress', () => { + it('should shorten an address', () => { + expect(formatters.shortenAddress('0x1234567890123456789012345678901234567890')).toEqual('0x1234...7890') + }) + + it('should shorten an address with custom length', () => { + expect(formatters.shortenAddress('0x1234567890123456789012345678901234567890', 5)).toEqual('0x12345...67890') + }) +}) + +describe('formatDecimals', () => { + it('should format decimals', () => { + expect(formatters.formatDecimals('100000000000000000')).toEqual('0.1') + }) + + it('should format decimals with custom decimals', () => { + expect(formatters.formatDecimals('1000000000000000000', 6)).toEqual('1000000000000') + }) +}) + +describe('toDecimals', () => { + it('should convert to decimals', () => { + expect(formatters.toDecimals('2.01')).toEqual(new BigNumber('2010000000000000000')) + }) + + it('should convert to decimals with custom decimals', () => { + expect(formatters.toDecimals('3', 6)).toEqual(new BigNumber('3000000')) + }) +}) diff --git a/services/__tests__/useChains.test.ts b/services/__tests__/useChains.test.ts index 89a38c23f5..84204f5446 100644 --- a/services/__tests__/useChains.test.ts +++ b/services/__tests__/useChains.test.ts @@ -53,7 +53,7 @@ describe('useInitChains hook', () => { it('should set the error state when the promise rejects', async () => { // Change the getChainsConfig mock to reject - ;(getChainsConfig as any).mockImplementation(() => Promise.reject('Something went wrong')) + ;(getChainsConfig as jest.Mock).mockImplementation(() => Promise.reject('Something went wrong')) // Render the hook and check that the loading state is true renderHook(() => useInitChains(), { wrapper: TestProviderWrapper }) diff --git a/services/__tests__/usePathRewrite.test.ts b/services/__tests__/usePathRewrite.test.ts new file mode 100644 index 0000000000..b7e54edaff --- /dev/null +++ b/services/__tests__/usePathRewrite.test.ts @@ -0,0 +1,77 @@ +import { act, renderHook } from '@testing-library/react-hooks' +import { useRouter } from 'next/router' +import usePathRewrite, { useQueryRewrite } from '../usePathRewrite' + +// Mock useRouter +jest.mock('next/router', () => ({ + useRouter: jest.fn(() => ({ + pathname: '/safe/balances', + query: { + safe: 'rin:0x0000000000000000000000000000000000000000', + }, + replace: jest.fn(), + })), +})) + +// mock window history replaceState +Object.defineProperty(window, 'history', { + writable: true, + value: { + replaceState: jest.fn(), + }, +}) + +describe('usePathRewrite', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('should rewrite the path if there is /safe/ in path and ?safe= in the query', () => { + renderHook(() => usePathRewrite()) + + expect(history.replaceState).toHaveBeenCalledWith( + undefined, + '', + '/rin:0x0000000000000000000000000000000000000000/balances', + ) + }) + + it('should not rewrite the path if there is no /safe/ in path or ?safe= in the query', () => { + ;(useRouter as jest.Mock).mockImplementationOnce(() => + jest.fn(() => ({ + pathname: '/welcome', + query: {}, + replace: jest.fn(), + })), + ) + + renderHook(() => usePathRewrite()) + + expect(history.replaceState).not.toHaveBeenCalled() + }) +}) + +describe('useQueryRewrite', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('should not redirect if there is no Safe address in the path', async () => { + const { result } = renderHook(() => useQueryRewrite()) + expect(result.current).toBe(false) + }) + + it('should redirect if there is a Safe address in the path', async () => { + Object.defineProperty(window, 'location', { + writable: true, + value: { + pathname: '/rin:0x0000000000000000000000000000000000000000/balances', + }, + }) + + const { result } = renderHook(() => useQueryRewrite()) + expect(result.current).toBe(true) + await act(() => Promise.resolve()) + expect(result.current).toBe(true) + }) +}) diff --git a/services/__tests__/validation.test.ts b/services/__tests__/validation.test.ts new file mode 100644 index 0000000000..593fc8c779 --- /dev/null +++ b/services/__tests__/validation.test.ts @@ -0,0 +1,14 @@ +import * as validators from '../validation' + +describe('validation', () => { + describe('Ethereum address validation', () => { + it('should return undefined if the address is valid', () => { + expect(validators.validateAddress('0x1234567890123456789012345678901234567890')).toBeUndefined() + }) + + it('should return an error if the address is invalid', () => { + expect(validators.validateAddress('0x1234567890123456789012345678901234567890x')).toBe('Invalid address format') + expect(validators.validateAddress('0x1234a67890123456789012345678901234567890')).toBe('Invalid address checksum') + }) + }) +}) diff --git a/services/formatters.ts b/services/formatters.ts index 0f02fec1e0..6486e9676f 100644 --- a/services/formatters.ts +++ b/services/formatters.ts @@ -8,6 +8,6 @@ export const toDecimals = (value: string, decimals = 18): BigNumber => { return new BigNumber(value).div(`1e-${decimals}`) } -export const shortenAddress = (address: string): string => { - return `${address.slice(0, 6)}...${address.slice(-4)}` +export const shortenAddress = (address: string, length = 4): string => { + return `${address.slice(0, length + 2)}...${address.slice(-length)}` }