Skip to content

Commit

Permalink
chore: added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cazala committed Jan 13, 2025
1 parent 478aeda commit c12b2df
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 14 deletions.
222 changes: 222 additions & 0 deletions src/modules/transaction/sagas.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// Mock the network provider
jest.mock('../../lib/eth', () => ({
getNetworkProvider: () => ({
send: jest.fn(),
on: jest.fn(),
removeListener: jest.fn()
})
}))

// Mock the getFibonacciDelay function
jest.mock('./sagas', () => {
const actual = jest.requireActual('./sagas')
return {
...actual,
INITIAL_BACKOFF_DELAY: 100,
getFibonacciDelay: function*(attempt: number) {
const fib = [1, 1]
for (let i = 2; i <= attempt + 1; i++) {
fib[i] = fib[i - 1] + fib[i - 2]
}
if (attempt <= 1) {
return 100
}
return 100 * fib[attempt]
}
}
})

import { expectSaga } from 'redux-saga-test-plan'
import * as matchers from 'redux-saga-test-plan/matchers'
import { call, delay, select } from 'redux-saga/effects'
import { TransactionStatus, Transaction } from './types'
import { ChainId } from '@dcl/schemas/dist/dapps/chain-id'
import {
getFibonacciDelay,
handleRegularTransactionRequest,
handleWatchRevertedTransaction,
INITIAL_BACKOFF_DELAY
} from './sagas'
import {
fetchTransactionRequest,
fixRevertedTransaction,
WATCH_REVERTED_TRANSACTION,
fetchTransactionSuccess,
updateTransactionStatus
} from './actions'
import { getAddress } from '../wallet/selectors'
import { getTransaction as getTransactionInState } from './selectors'
import { buildTransactionPayload } from './utils'
import { getTransaction as getTransactionFromChain } from './txUtils'
import { FETCH_TRANSACTION_REQUEST } from './actions'

describe('when using fibonacci backoff for transaction polling', () => {
const MOCK_INITIAL_DELAY = 100 // 100ms for testing
jest.setTimeout(20000) // Increase global timeout

describe('when calculating fibonacci delay', () => {
const cases = [
{ attempt: 0, expected: MOCK_INITIAL_DELAY },
{ attempt: 1, expected: MOCK_INITIAL_DELAY },
{ attempt: 2, expected: MOCK_INITIAL_DELAY * 2 },
{ attempt: 3, expected: MOCK_INITIAL_DELAY * 3 },
{ attempt: 4, expected: MOCK_INITIAL_DELAY * 5 },
{ attempt: 5, expected: MOCK_INITIAL_DELAY * 8 },
{ attempt: 6, expected: MOCK_INITIAL_DELAY * 13 }
]

cases.forEach(({ attempt, expected }) => {
it(`should return ${expected}ms for attempt ${attempt}`, () => {
return expectSaga(getFibonacciDelay, attempt)
.returns(expected)
.run()
})
})
})

describe('when polling regular transaction status', () => {
let transaction: Transaction
let address: string
let hash: string
let chainId: ChainId

beforeEach(() => {
jest.setTimeout(20000) // Set timeout for each test in this suite
address = '0x123'
hash = '0x456'
chainId = ChainId.ETHEREUM_MAINNET
transaction = {
...buildTransactionPayload(chainId, hash, {}, chainId),
events: [],
hash,
from: address,
chainId,
status: null,
timestamp: Date.now(),
nonce: 0,
withReceipt: false,
isCrossChain: false,
actionType: 'SOME_ACTION',
url: '',
replacedBy: null
}
})

describe('and the transaction becomes confirmed', () => {
test('should use fibonacci backoff until confirmation and fix the transaction', async () => {
const { hash } = transaction
const mockReceipt = { logs: [] }
const revertedTx = {
...transaction,
status: TransactionStatus.REVERTED
}
const action = {
type: '[Request] Fetch Transaction' as const,
payload: {
hash,
address: '0x123',
action: {
type: 'SOME_ACTION',
payload: buildTransactionPayload(
transaction.chainId,
hash,
{},
transaction.chainId
)
}
}
}

return expectSaga(handleRegularTransactionRequest, action)
.provide([
[select(getTransactionInState, hash), revertedTx],
[
call(getTransactionFromChain, '0x123', transaction.chainId, hash),
{
...revertedTx,
status: TransactionStatus.CONFIRMED,
receipt: mockReceipt
}
]
])
.withState({
transaction: {
data: [revertedTx],
loading: [],
error: null
},
wallet: {
data: {
address: '0x123'
}
}
})
.put(
fetchTransactionSuccess({
...revertedTx,
status: TransactionStatus.CONFIRMED,
receipt: { logs: [] }
})
)
.run({ timeout: 15000 })
})
})
})

describe('when watching reverted transaction', () => {
let transaction: Transaction
let address: string
let hash: string
let chainId: ChainId

beforeEach(() => {
address = '0x123'
hash = '0x456'
chainId = ChainId.ETHEREUM_MAINNET
transaction = {
...buildTransactionPayload(chainId, hash, {}, chainId),
events: [],
hash,
from: address,
chainId,
status: TransactionStatus.REVERTED,
timestamp: Date.now(),
nonce: 0,
withReceipt: false,
isCrossChain: false,
replacedBy: null,
actionType: 'SOME_ACTION',
url: ''
}
})

describe('and the transaction expires', () => {
it('should stop polling after expiration threshold', () => {
const expiredTransaction = {
...transaction,
timestamp: Date.now() - 25 * 60 * 60 * 1000 // 25 hours ago
}

return expectSaga(handleWatchRevertedTransaction, {
type: WATCH_REVERTED_TRANSACTION,
payload: { hash }
})
.provide([
[matchers.select(getTransactionInState, hash), expiredTransaction]
])
.withState({
transaction: {
data: [expiredTransaction]
},
wallet: {
data: {
address
}
}
})
.not.call(getTransactionFromChain)
.run()
})
})
})
})
28 changes: 14 additions & 14 deletions src/modules/transaction/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ export function* transactionSaga(
}
}

const BLOCKS_DEPTH = 100
const TRANSACTION_FETCH_RETIES = 120
const PENDING_TRANSACTION_THRESHOLD = 72 * 60 * 60 * 1000 // 72 hours
const REVERTED_TRANSACTION_THRESHOLD = 24 * 60 * 60 * 1000 // 24 hours
const DROPPED_TRANSACTION_THRESHOLD = 24 * 60 * 60 * 1000 // 24 hours
const INITIAL_BACKOFF_DELAY = 2 * 1000 // 2 seconds
export const BLOCKS_DEPTH = 100
export const TRANSACTION_FETCH_RETIES = 120
export const PENDING_TRANSACTION_THRESHOLD = 72 * 60 * 60 * 1000 // 72 hours
export const REVERTED_TRANSACTION_THRESHOLD = 24 * 60 * 60 * 1000 // 24 hours
export const DROPPED_TRANSACTION_THRESHOLD = 24 * 60 * 60 * 1000 // 24 hours
export const INITIAL_BACKOFF_DELAY = 2 * 1000 // 2 seconds

const isExpired = (transaction: Transaction, threshold: number) =>
Date.now() - transaction.timestamp > threshold
Expand All @@ -108,7 +108,7 @@ export class FailedTransactionError extends Error {
}
}

function* handleCrossChainTransactionRequest(
export function* handleCrossChainTransactionRequest(
action: FetchTransactionRequestAction,
config?: TransactionsConfig
) {
Expand Down Expand Up @@ -213,7 +213,7 @@ function* handleCrossChainTransactionRequest(
}
}

function* handleRegularTransactionRequest(
export function* handleRegularTransactionRequest(
action: FetchTransactionRequestAction
) {
const { hash, address } = action.payload
Expand Down Expand Up @@ -317,7 +317,7 @@ function* handleRegularTransactionRequest(
}
}

function* getFibonacciDelay(attempt: number) {
export function* getFibonacciDelay(attempt: number) {
if (attempt <= 1) return INITIAL_BACKOFF_DELAY

let prev = 1
Expand All @@ -330,7 +330,7 @@ function* getFibonacciDelay(attempt: number) {
return current * INITIAL_BACKOFF_DELAY
}

function* handleReplaceTransactionRequest(
export function* handleReplaceTransactionRequest(
action: ReplaceTransactionRequestAction
) {
const { hash, nonce, address: account } = action.payload
Expand Down Expand Up @@ -451,7 +451,7 @@ function* handleReplaceTransactionRequest(
delete watchDroppedIndex[action.payload.hash]
}

function* handleWatchPendingTransactions() {
export function* handleWatchPendingTransactions() {
const transactions: Transaction[] = yield select(getData)
const pendingTransactions = transactions.filter(transaction =>
isPending(transaction.status)
Expand All @@ -473,7 +473,7 @@ function* handleWatchPendingTransactions() {
}
}

function* handleWatchDroppedTransactions() {
export function* handleWatchDroppedTransactions() {
const transactions: Transaction[] = yield select(getData)
const droppedTransactions = transactions.filter(
transaction =>
Expand All @@ -492,7 +492,7 @@ function* handleWatchDroppedTransactions() {
}
}

function* handleWatchRevertedTransaction(
export function* handleWatchRevertedTransaction(
action: WatchRevertedTransactionAction
) {
const { hash } = action.payload
Expand Down Expand Up @@ -532,7 +532,7 @@ function* handleWatchRevertedTransaction(
} while (!isExpired(txInState, REVERTED_TRANSACTION_THRESHOLD))
}

function* handleConnectWalletSuccess(_: ConnectWalletSuccessAction) {
export function* handleConnectWalletSuccess(_: ConnectWalletSuccessAction) {
yield put(watchPendingTransactions())
yield put(watchDroppedTransactions())

Expand Down

0 comments on commit c12b2df

Please sign in to comment.