From 973ac99ca37f94b4e13268632715f5ded9d40264 Mon Sep 17 00:00:00 2001 From: gabrieledm Date: Tue, 4 Aug 2026 22:31:16 +0200 Subject: [PATCH 1/3] test: removed beforeEach usage from fetchNewTransactionsForAccount tests --- .../transactions/TransactionsService.test.ts | 1051 ++++++++++------- 1 file changed, 620 insertions(+), 431 deletions(-) diff --git a/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts b/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts index bd5d9f7f..e06e7eed 100644 --- a/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts +++ b/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts @@ -24,6 +24,7 @@ import { TransactionsService } from './TransactionsService'; type WithTransactionServiceCallback = (payload: { transactionsService: TransactionsService; + mockLogger: ILogger; mockTransactionsRepository: jest.Mocked< Pick< TransactionsRepository, @@ -122,6 +123,7 @@ async function withTransactionService( return await testFunction({ transactionsService, + mockLogger, mockTransactionsRepository, mockTrongridApiClient, mockTronHttpClient, @@ -285,367 +287,450 @@ describe('TransactionsService', () => { describe('fetchNewTransactionsForAccount', () => { it('returns mapped transactions with spam removed', async () => { - const nativeAsset = ( - amount: string, - ): Transaction['to'][number]['asset'] => ({ - type: Networks[Network.Mainnet].nativeToken.id, - amount, - unit: Networks[Network.Mainnet].nativeToken.symbol, - fungible: true, - }); + await withTransactionService( + async ({ mockTrongridApiClient, transactionsService }) => { + const nativeAsset = ( + amount: string, + ): Transaction['to'][number]['asset'] => ({ + type: Networks[Network.Mainnet].nativeToken.id, + amount, + unit: Networks[Network.Mainnet].nativeToken.symbol, + fungible: true, + }); - const spamTransaction: Transaction = { - id: 'spam-tx-id', - type: TransactionType.Receive, - account: mockAccount.id, - chain: Network.Mainnet, - status: TransactionStatus.Confirmed, - timestamp: 1, - from: [{ address: 'sender-address', asset: nativeAsset('0.0005') }], - to: [{ address: mockAccount.address, asset: nativeAsset('0.0005') }], - events: [], - fees: [], - }; + const spamTransaction: Transaction = { + id: 'spam-tx-id', + type: TransactionType.Receive, + account: mockAccount.id, + chain: Network.Mainnet, + status: TransactionStatus.Confirmed, + timestamp: 1, + from: [{ address: 'sender-address', asset: nativeAsset('0.0005') }], + to: [ + { address: mockAccount.address, asset: nativeAsset('0.0005') }, + ], + events: [], + fees: [], + }; + + const keptTransaction: Transaction = { + ...spamTransaction, + id: 'kept-tx-id', + to: [{ address: mockAccount.address, asset: nativeAsset('0.001') }], + }; - const keptTransaction: Transaction = { - ...spamTransaction, - id: 'kept-tx-id', - to: [{ address: mockAccount.address, asset: nativeAsset('0.001') }], - }; + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ + { + ...nativeTransferMock, + txID: 'spam-raw-tx-id', + }, + { + ...nativeTransferMock, + txID: 'kept-raw-tx-id', + }, + ] as TransactionInfo[]); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ - { - ...nativeTransferMock, - txID: 'spam-raw-tx-id', - }, - { - ...nativeTransferMock, - txID: 'kept-raw-tx-id', + const mapTransactionsSpy = jest + .spyOn(TransactionMapper, 'mapTransactions') + .mockReturnValue([spamTransaction, keptTransaction]); + + try { + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); + + expect(mapTransactionsSpy).toHaveBeenCalledWith( + expect.objectContaining({ + scope: Network.Mainnet, + account: mockAccount, + trc20Transactions: [], + }), + ); + expect(result).toStrictEqual([keptTransaction]); + } finally { + mapTransactionsSpy.mockRestore(); + } }, - ] as TransactionInfo[]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - [], ); - - const mapTransactionsSpy = jest - .spyOn(TransactionMapper, 'mapTransactions') - .mockReturnValue([spamTransaction, keptTransaction]); - - try { - const result = await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, - ); - - expect(mapTransactionsSpy).toHaveBeenCalledWith( - expect.objectContaining({ - scope: Network.Mainnet, - account: mockAccount, - trc20Transactions: [], - }), - ); - expect(result).toStrictEqual([keptTransaction]); - } finally { - mapTransactionsSpy.mockRestore(); - } }); it('should fetch and map transactions for an account using native transfers mock data', async () => { - // Setup mock responses with simplified single-transaction structure - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ - nativeTransferMock, - ] as TransactionInfo[]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - contractInfoMock.data as ContractTransactionInfo[], - ); + await withTransactionService( + async ({ mockTrongridApiClient, transactionsService, mockLogger }) => { + // Setup mock responses with simplified single-transaction structure + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ + nativeTransferMock, + ] as TransactionInfo[]); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + contractInfoMock.data as ContractTransactionInfo[], + ); - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, - ); + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); - // Verify API calls were made - expect( - mockTrongridApiClient.getTransactionInfoByAddress, - ).toHaveBeenCalledWith(Network.Mainnet, mockAccount.address); - expect( - mockTrongridApiClient.getContractTransactionInfoByAddress, - ).toHaveBeenCalledWith(Network.Mainnet, mockAccount.address); - - // Verify logger calls - expect(mockLogger.info).toHaveBeenCalledWith( - '[๐Ÿงพ TransactionsService]', - expect.stringContaining('Fetching new transactions for account'), - ); + // Verify API calls were made + expect( + mockTrongridApiClient.getTransactionInfoByAddress, + ).toHaveBeenCalledWith(Network.Mainnet, mockAccount.address); + expect( + mockTrongridApiClient.getContractTransactionInfoByAddress, + ).toHaveBeenCalledWith(Network.Mainnet, mockAccount.address); - expect(true).toBe(true); + // Verify logger calls + expect(mockLogger.info).toHaveBeenCalledWith( + '[๐Ÿงพ TransactionsService]', + expect.stringContaining('Fetching new transactions for account'), + ); + + expect(true).toBe(true); + }, + ); }); it('fetches and maps TRC10 transactions with token metadata', async () => { - // Setup mock responses with simplified single-transaction structure - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ - trc10TransferMock, - ] as TransactionInfo[]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - [], - ); - // Mock TRC10 token metadata response with 3 decimals - mockTronHttpClient.getTRC10TokenMetadata.mockResolvedValue({ - name: 'BestAdsCoin', - symbol: 'TRC20AdsCOM', - decimals: 3, - }); - // Token has price data โ€” should not be filtered as spam - mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({ - [`${Network.Mainnet}/trc10:1005119`]: { id: '1005119', price: 0.001 }, - } as never); + await withTransactionService( + async ({ + mockTrongridApiClient, + mockTronHttpClient, + mockPriceApiClient, + transactionsService, + }) => { + // Setup mock responses with simplified single-transaction structure + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ + trc10TransferMock, + ] as TransactionInfo[]); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); + // Mock TRC10 token metadata response with 3 decimals + mockTronHttpClient.getTRC10TokenMetadata.mockResolvedValue({ + name: 'BestAdsCoin', + symbol: 'TRC20AdsCOM', + decimals: 3, + }); + // Token has price data โ€” should not be filtered as spam + mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({ + [`${Network.Mainnet}/trc10:1005119`]: { + id: '1005119', + price: 0.001, + }, + } as never); - const transactions = - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount2, - ); + const transactions = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount2, + ); - // Verify API calls were made - expect( - mockTrongridApiClient.getTransactionInfoByAddress, - ).toHaveBeenCalledWith(Network.Mainnet, mockAccount2.address); - expect( - mockTrongridApiClient.getContractTransactionInfoByAddress, - ).toHaveBeenCalledWith(Network.Mainnet, mockAccount2.address); - - // Verify TRC10 token metadata was fetched for the token ID in the transaction - expect(mockTronHttpClient.getTRC10TokenMetadata).toHaveBeenCalledWith( - '1005119', - Network.Mainnet, - ); + // Verify API calls were made + expect( + mockTrongridApiClient.getTransactionInfoByAddress, + ).toHaveBeenCalledWith(Network.Mainnet, mockAccount2.address); + expect( + mockTrongridApiClient.getContractTransactionInfoByAddress, + ).toHaveBeenCalledWith(Network.Mainnet, mockAccount2.address); - // Verify the amount and symbol are from fetched metadata - // Raw amount is 88888888, with 3 decimals: 88888888 / 10^3 = 88888.888 - expect(transactions).toHaveLength(1); - const fromAsset = transactions[0]!.from[0]!.asset as { - amount: string; - unit: string; - }; - const toAsset = transactions[0]!.to[0]!.asset as { - amount: string; - unit: string; - }; - expect(fromAsset.amount).toBe('88888.888'); - expect(fromAsset.unit).toBe('TRC20AdsCOM'); - expect(toAsset.amount).toBe('88888.888'); - expect(toAsset.unit).toBe('TRC20AdsCOM'); + // Verify TRC10 token metadata was fetched for the token ID in the transaction + expect(mockTronHttpClient.getTRC10TokenMetadata).toHaveBeenCalledWith( + '1005119', + Network.Mainnet, + ); + + // Verify the amount and symbol are from fetched metadata + // Raw amount is 88888888, with 3 decimals: 88888888 / 10^3 = 88888.888 + expect(transactions).toHaveLength(1); + const fromAsset = transactions[0]!.from[0]!.asset as { + amount: string; + unit: string; + }; + const toAsset = transactions[0]!.to[0]!.asset as { + amount: string; + unit: string; + }; + expect(fromAsset.amount).toBe('88888.888'); + expect(fromAsset.unit).toBe('TRC20AdsCOM'); + expect(toAsset.amount).toBe('88888.888'); + expect(toAsset.unit).toBe('TRC20AdsCOM'); + }, + ); }); it('handles decimal TRC10 asset_name values without throwing', async () => { - const trc10TransferWithDecimalTokenId = JSON.parse( - JSON.stringify(trc10TransferMock), - ) as TransactionInfo; - - const transferAssetContract = trc10TransferWithDecimalTokenId.raw_data - .contract[0] as unknown as { - parameter: { value: Record }; - }; - transferAssetContract.parameter.value.asset_name = '1005119'; + await withTransactionService( + async ({ + mockTrongridApiClient, + mockTronHttpClient, + mockPriceApiClient, + transactionsService, + }) => { + const trc10TransferWithDecimalTokenId = JSON.parse( + JSON.stringify(trc10TransferMock), + ) as TransactionInfo; + + const transferAssetContract = trc10TransferWithDecimalTokenId.raw_data + .contract[0] as unknown as { + parameter: { value: Record }; + }; + transferAssetContract.parameter.value.asset_name = '1005119'; - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ - trc10TransferWithDecimalTokenId, - ]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - [], - ); - mockTronHttpClient.getTRC10TokenMetadata.mockResolvedValue({ - name: 'BestAdsCoin', - symbol: 'TRC20AdsCOM', - decimals: 3, - }); - // Token has price data โ€” should not be filtered as spam - mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({ - [`${Network.Mainnet}/trc10:1005119`]: { id: '1005119', price: 0.001 }, - } as never); + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ + trc10TransferWithDecimalTokenId, + ]); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); + mockTronHttpClient.getTRC10TokenMetadata.mockResolvedValue({ + name: 'BestAdsCoin', + symbol: 'TRC20AdsCOM', + decimals: 3, + }); + // Token has price data โ€” should not be filtered as spam + mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({ + [`${Network.Mainnet}/trc10:1005119`]: { + id: '1005119', + price: 0.001, + }, + } as never); - const transactions = - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount2, - ); + const transactions = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount2, + ); - expect(mockTronHttpClient.getTRC10TokenMetadata).toHaveBeenCalledWith( - '1005119', - Network.Mainnet, + expect(mockTronHttpClient.getTRC10TokenMetadata).toHaveBeenCalledWith( + '1005119', + Network.Mainnet, + ); + expect(transactions).toHaveLength(1); + }, ); - expect(transactions).toHaveLength(1); }); it('falls back to defaults when TRC10 metadata fetch fails', async () => { - // Setup mock responses with simplified single-transaction structure - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ - trc10TransferMock, - ] as TransactionInfo[]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - [], - ); - // Mock TRC10 token metadata to fail - mockTronHttpClient.getTRC10TokenMetadata.mockRejectedValue( - new Error('Token not found'), - ); - // Token has price data โ€” should not be filtered as spam despite metadata failure - mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({ - [`${Network.Mainnet}/trc10:1005119`]: { id: '1005119', price: 0.001 }, - } as never); + await withTransactionService( + async ({ + mockTrongridApiClient, + mockTronHttpClient, + mockPriceApiClient, + transactionsService, + }) => { + // Setup mock responses with simplified single-transaction structure + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ + trc10TransferMock, + ] as TransactionInfo[]); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); + // Mock TRC10 token metadata to fail + mockTronHttpClient.getTRC10TokenMetadata.mockRejectedValue( + new Error('Token not found'), + ); + // Token has price data โ€” should not be filtered as spam despite metadata failure + mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({ + [`${Network.Mainnet}/trc10:1005119`]: { + id: '1005119', + price: 0.001, + }, + } as never); - const transactions = - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount2, - ); + const transactions = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount2, + ); - // Verify TRC10 token metadata fetch was attempted - expect(mockTronHttpClient.getTRC10TokenMetadata).toHaveBeenCalledWith( - '1005119', - Network.Mainnet, - ); + // Verify TRC10 token metadata fetch was attempted + expect(mockTronHttpClient.getTRC10TokenMetadata).toHaveBeenCalledWith( + '1005119', + Network.Mainnet, + ); - // Verify the amount falls back to default 6 decimals and symbol to UNKNOWN - // Raw amount is 88888888, with 6 decimals: 88888888 / 10^6 = 88.888888 - expect(transactions).toHaveLength(1); - const fromAsset = transactions[0]!.from[0]!.asset as { - amount: string; - unit: string; - }; - const toAsset = transactions[0]!.to[0]!.asset as { - amount: string; - unit: string; - }; - expect(fromAsset.amount).toBe('88.888888'); - expect(fromAsset.unit).toBe('UNKNOWN'); - expect(toAsset.amount).toBe('88.888888'); - expect(toAsset.unit).toBe('UNKNOWN'); + // Verify the amount falls back to default 6 decimals and symbol to UNKNOWN + // Raw amount is 88888888, with 6 decimals: 88888888 / 10^6 = 88.888888 + expect(transactions).toHaveLength(1); + const fromAsset = transactions[0]!.from[0]!.asset as { + amount: string; + unit: string; + }; + const toAsset = transactions[0]!.to[0]!.asset as { + amount: string; + unit: string; + }; + expect(fromAsset.amount).toBe('88.888888'); + expect(fromAsset.unit).toBe('UNKNOWN'); + expect(toAsset.amount).toBe('88.888888'); + expect(toAsset.unit).toBe('UNKNOWN'); + }, + ); }); it('should handle network parameter correctly for different networks', async () => { - // Setup mock responses - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - [], - ); + await withTransactionService( + async ({ mockTrongridApiClient, transactionsService }) => { + // Setup mock responses + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue( + [], + ); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - await transactionsService.fetchNewTransactionsForAccount( - Network.Shasta, - mockAccount, - ); + await transactionsService.fetchNewTransactionsForAccount( + Network.Shasta, + mockAccount, + ); - // Verify API calls were made with correct network - expect( - mockTrongridApiClient.getTransactionInfoByAddress, - ).toHaveBeenCalledWith(Network.Shasta, mockAccount.address); - expect( - mockTrongridApiClient.getContractTransactionInfoByAddress, - ).toHaveBeenCalledWith(Network.Shasta, mockAccount.address); + // Verify API calls were made with correct network + expect( + mockTrongridApiClient.getTransactionInfoByAddress, + ).toHaveBeenCalledWith(Network.Shasta, mockAccount.address); + expect( + mockTrongridApiClient.getContractTransactionInfoByAddress, + ).toHaveBeenCalledWith(Network.Shasta, mockAccount.address); - expect(true).toBe(true); + expect(true).toBe(true); + }, + ); }); it('should handle empty responses from API', async () => { - // Setup empty mock responses - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - [], - ); + await withTransactionService( + async ({ mockTrongridApiClient, transactionsService }) => { + // Setup empty mock responses + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue( + [], + ); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - const result = await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, - ); + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); - expect(result).toStrictEqual([]); - expect(true).toBe(true); + expect(result).toStrictEqual([]); + expect(true).toBe(true); + }, + ); }); it('should handle API errors gracefully', async () => { - // Setup API to throw error - const apiError = new Error('API request failed'); - mockTrongridApiClient.getTransactionInfoByAddress.mockRejectedValue( - apiError, - ); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockRejectedValue( - apiError, - ); + await withTransactionService( + async ({ mockTrongridApiClient, transactionsService, mockLogger }) => { + // Setup API to throw error + const apiError = new Error('API request failed'); + mockTrongridApiClient.getTransactionInfoByAddress.mockRejectedValue( + apiError, + ); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockRejectedValue( + apiError, + ); - const result = await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, - ); + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); - expect(result).toStrictEqual([]); - expect(mockLogger.error).toHaveBeenCalledWith( - '[๐Ÿงพ TransactionsService]', - 'Failed to fetch raw transactions for address TGJn1wnUYHJbvN88cynZbsAz2EMeZq73yx on network tron:728126428', - ); - expect(mockLogger.error).toHaveBeenCalledWith( - '[๐Ÿงพ TransactionsService]', - 'Failed to fetch TRC20 transactions for address TGJn1wnUYHJbvN88cynZbsAz2EMeZq73yx on network tron:728126428', + expect(result).toStrictEqual([]); + expect(mockLogger.error).toHaveBeenCalledWith( + '[๐Ÿงพ TransactionsService]', + 'Failed to fetch raw transactions for address TGJn1wnUYHJbvN88cynZbsAz2EMeZq73yx on network tron:728126428', + ); + expect(mockLogger.error).toHaveBeenCalledWith( + '[๐Ÿงพ TransactionsService]', + 'Failed to fetch TRC20 transactions for address TGJn1wnUYHJbvN88cynZbsAz2EMeZq73yx on network tron:728126428', + ); + expect(true).toBe(true); + }, ); - expect(true).toBe(true); }); it('skips already confirmed transactions but allows pending transactions to be updated', async () => { - // Simulate a confirmed transaction ID that should be skipped - const confirmedTxId = 'confirmed-tx-id'; - mockTransactionsRepository.getConfirmedTransactionIds.mockResolvedValue( - new Set([confirmedTxId]), - ); + await withTransactionService( + async ({ + mockTrongridApiClient, + mockTransactionsRepository, + transactionsService, + }) => { + // Simulate a confirmed transaction ID that should be skipped + const confirmedTxId = 'confirmed-tx-id'; + mockTransactionsRepository.getConfirmedTransactionIds.mockResolvedValue( + new Set([confirmedTxId]), + ); - // API returns both the confirmed tx and a new tx - const confirmedTx = { - ...nativeTransferMock, - txID: confirmedTxId, - }; - const newTx = { - ...nativeTransferMock, - txID: 'new-tx-id', - }; - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ - confirmedTx, - newTx, - ] as TransactionInfo[]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - [], - ); + // API returns both the confirmed tx and a new tx + const confirmedTx = { + ...nativeTransferMock, + txID: confirmedTxId, + }; + const newTx = { + ...nativeTransferMock, + txID: 'new-tx-id', + }; + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ + confirmedTx, + newTx, + ] as TransactionInfo[]); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - const result = await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, - ); + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); - // Should only return the new transaction, not the confirmed one - expect(result).toHaveLength(1); - expect(result[0]!.id).toBe('new-tx-id'); + // Should only return the new transaction, not the confirmed one + expect(result).toHaveLength(1); + expect(result[0]!.id).toBe('new-tx-id'); + }, + ); }); it('re-fetches pending transactions so they can be updated to confirmed status', async () => { - // Simulate a pending transaction that exists in state but is NOT in confirmed set - const pendingTxId = nativeTransferMock.txID; - mockTransactionsRepository.getConfirmedTransactionIds.mockResolvedValue( - new Set(), // Pending tx ID is not in confirmed set - ); + await withTransactionService( + async ({ + mockTrongridApiClient, + mockTransactionsRepository, + transactionsService, + }) => { + // Simulate a pending transaction that exists in state but is NOT in confirmed set + const pendingTxId = nativeTransferMock.txID; + mockTransactionsRepository.getConfirmedTransactionIds.mockResolvedValue( + new Set(), // Pending tx ID is not in confirmed set + ); - // API returns the same transaction (now confirmed on network) - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ - nativeTransferMock, - ] as TransactionInfo[]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - [], - ); + // API returns the same transaction (now confirmed on network) + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ + nativeTransferMock, + ] as TransactionInfo[]); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - const result = await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, - ); + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); - // The pending transaction should be returned so it can be updated - expect(result).toHaveLength(1); - expect(result[0]!.id).toBe(pendingTxId); + // The pending transaction should be returned so it can be updated + expect(result).toHaveLength(1); + expect(result[0]!.id).toBe(pendingTxId); + }, + ); }); describe('filters unpriced received token transactions as spam', () => { @@ -693,163 +778,267 @@ describe('TransactionsService', () => { fees: [], }); - beforeEach(() => { - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ - nativeTransferMock, - ] as TransactionInfo[]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - [], - ); - }); + // beforeEach(() => { + // mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ + // nativeTransferMock, + // ] as TransactionInfo[]); + // mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + // [], + // ); + // }); it('filters received TRC10 tokens with no price data', async () => { - const spamTx = receiveTransaction('spam-trc10', trc10Asset('2.2222')); - const mapSpy = jest - .spyOn(TransactionMapper, 'mapTransactions') - .mockReturnValue([spamTx]); - mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({}); + await withTransactionService( + async ({ + mockPriceApiClient, + mockTrongridApiClient, + transactionsService, + }) => { + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue( + [nativeTransferMock] as TransactionInfo[], + ); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - try { - const result = - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, + const spamTx = receiveTransaction( + 'spam-trc10', + trc10Asset('2.2222'), ); - expect(result).toHaveLength(0); - } finally { - mapSpy.mockRestore(); - } + const mapSpy = jest + .spyOn(TransactionMapper, 'mapTransactions') + .mockReturnValue([spamTx]); + mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({}); + + try { + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); + expect(result).toHaveLength(0); + } finally { + mapSpy.mockRestore(); + } + }, + ); }); it('filters received TRC20 tokens with no price data', async () => { - const spamTx = receiveTransaction( - 'spam-trc20', - trc20Asset('4444.4444'), - ); - const mapSpy = jest - .spyOn(TransactionMapper, 'mapTransactions') - .mockReturnValue([spamTx]); - mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({}); + await withTransactionService( + async ({ + mockPriceApiClient, + mockTrongridApiClient, + transactionsService, + }) => { + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue( + [nativeTransferMock] as TransactionInfo[], + ); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - try { - const result = - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, + const spamTx = receiveTransaction( + 'spam-trc20', + trc20Asset('4444.4444'), ); - expect(result).toHaveLength(0); - } finally { - mapSpy.mockRestore(); - } + const mapSpy = jest + .spyOn(TransactionMapper, 'mapTransactions') + .mockReturnValue([spamTx]); + mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({}); + + try { + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); + expect(result).toHaveLength(0); + } finally { + mapSpy.mockRestore(); + } + }, + ); }); it('keeps received tokens that have price data', async () => { - const legitimateTx = receiveTransaction('legit-usdt', trc20Asset('10')); - const mapSpy = jest - .spyOn(TransactionMapper, 'mapTransactions') - .mockReturnValue([legitimateTx]); - mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({ - [`${Network.Mainnet}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`]: { - id: 'tether', - price: 1.0, - }, - } as never); + await withTransactionService( + async ({ + mockPriceApiClient, + mockTrongridApiClient, + transactionsService, + }) => { + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue( + [nativeTransferMock] as TransactionInfo[], + ); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - try { - const result = - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, + const legitimateTx = receiveTransaction( + 'legit-usdt', + trc20Asset('10'), ); - expect(result).toHaveLength(1); - expect(result[0]!.id).toBe('legit-usdt'); - } finally { - mapSpy.mockRestore(); - } + const mapSpy = jest + .spyOn(TransactionMapper, 'mapTransactions') + .mockReturnValue([legitimateTx]); + mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue({ + [`${Network.Mainnet}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`]: { + id: 'tether', + price: 1.0, + }, + } as never); + + try { + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); + expect(result).toHaveLength(1); + expect(result[0]!.id).toBe('legit-usdt'); + } finally { + mapSpy.mockRestore(); + } + }, + ); }); it('always keeps received native TRX transactions regardless of price', async () => { - const nativeTx = receiveTransaction('native-trx', nativeAsset('10')); - const mapSpy = jest - .spyOn(TransactionMapper, 'mapTransactions') - .mockReturnValue([nativeTx]); - // No price API call expected for native TRX โ€” getMultipleSpotPrices default returns {} + await withTransactionService( + async ({ + mockPriceApiClient, + mockTrongridApiClient, + transactionsService, + }) => { + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue( + [nativeTransferMock] as TransactionInfo[], + ); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - try { - const result = - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, + const nativeTx = receiveTransaction( + 'native-trx', + nativeAsset('10'), ); - expect(result).toHaveLength(1); - expect( - mockPriceApiClient.getMultipleSpotPrices, - ).not.toHaveBeenCalled(); - } finally { - mapSpy.mockRestore(); - } + const mapSpy = jest + .spyOn(TransactionMapper, 'mapTransactions') + .mockReturnValue([nativeTx]); + // No price API call expected for native TRX โ€” getMultipleSpotPrices default returns {} + + try { + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); + expect(result).toHaveLength(1); + expect( + mockPriceApiClient.getMultipleSpotPrices, + ).not.toHaveBeenCalled(); + } finally { + mapSpy.mockRestore(); + } + }, + ); }); it('always keeps non-receive transactions regardless of price', async () => { - const sendTx: Transaction = { - id: 'send-trc20', - type: TransactionType.Send, - account: mockAccount.id, - chain: Network.Mainnet, - status: TransactionStatus.Confirmed, - timestamp: 1, - from: [{ address: mockAccount.address, asset: trc20Asset('10') }], - to: [{ address: 'recipient', asset: trc20Asset('10') }], - events: [], - fees: [], - }; - const mapSpy = jest - .spyOn(TransactionMapper, 'mapTransactions') - .mockReturnValue([sendTx]); - // No price API call expected โ€” getMultipleSpotPrices default returns {} - - try { - const result = - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, + await withTransactionService( + async ({ + mockPriceApiClient, + mockTrongridApiClient, + transactionsService, + }) => { + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue( + [nativeTransferMock] as TransactionInfo[], ); - expect(result).toHaveLength(1); - expect( - mockPriceApiClient.getMultipleSpotPrices, - ).not.toHaveBeenCalled(); - } finally { - mapSpy.mockRestore(); - } + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); + + const sendTx: Transaction = { + id: 'send-trc20', + type: TransactionType.Send, + account: mockAccount.id, + chain: Network.Mainnet, + status: TransactionStatus.Confirmed, + timestamp: 1, + from: [{ address: mockAccount.address, asset: trc20Asset('10') }], + to: [{ address: 'recipient', asset: trc20Asset('10') }], + events: [], + fees: [], + }; + const mapSpy = jest + .spyOn(TransactionMapper, 'mapTransactions') + .mockReturnValue([sendTx]); + // No price API call expected โ€” getMultipleSpotPrices default returns {} + + try { + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); + expect(result).toHaveLength(1); + expect( + mockPriceApiClient.getMultipleSpotPrices, + ).not.toHaveBeenCalled(); + } finally { + mapSpy.mockRestore(); + } + }, + ); }); it('keeps all transactions when the price API call fails', async () => { - const spamTx = receiveTransaction('spam-trc10', trc10Asset('2.2222')); - const mapSpy = jest - .spyOn(TransactionMapper, 'mapTransactions') - .mockReturnValue([spamTx]); - mockPriceApiClient.getMultipleSpotPrices.mockRejectedValue( - new Error('Price API unavailable'), - ); + await withTransactionService( + async ({ + mockPriceApiClient, + mockTrongridApiClient, + mockLogger, + mockSnapClient, + transactionsService, + }) => { + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue( + [nativeTransferMock] as TransactionInfo[], + ); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - try { - const result = - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, + const spamTx = receiveTransaction( + 'spam-trc10', + trc10Asset('2.2222'), ); - expect(result).toHaveLength(1); - expect(mockLogger.warn).toHaveBeenCalledWith( - '[๐Ÿงพ TransactionsService]', - expect.objectContaining({ error: expect.any(Error) }), - 'Failed to fetch spot prices for spam filtering, keeping all transactions', - ); - expect(mockSnapClient.trackError).toHaveBeenCalledWith( - expect.any(Error), - ); - } finally { - mapSpy.mockRestore(); - } + const mapSpy = jest + .spyOn(TransactionMapper, 'mapTransactions') + .mockReturnValue([spamTx]); + mockPriceApiClient.getMultipleSpotPrices.mockRejectedValue( + new Error('Price API unavailable'), + ); + + try { + const result = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); + expect(result).toHaveLength(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + '[๐Ÿงพ TransactionsService]', + expect.objectContaining({ error: expect.any(Error) }), + 'Failed to fetch spot prices for spam filtering, keeping all transactions', + ); + expect(mockSnapClient.trackError).toHaveBeenCalledWith( + expect.any(Error), + ); + } finally { + mapSpy.mockRestore(); + } + }, + ); }); }); }); From 0064fca5523fa6cafc71d825e956d812a2d901d2 Mon Sep 17 00:00:00 2001 From: gabrieledm Date: Tue, 4 Aug 2026 22:41:14 +0200 Subject: [PATCH 2/3] fix: removed old commented code --- .../services/transactions/TransactionsService.test.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts b/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts index e06e7eed..79c52415 100644 --- a/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts +++ b/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts @@ -778,15 +778,6 @@ describe('TransactionsService', () => { fees: [], }); - // beforeEach(() => { - // mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ - // nativeTransferMock, - // ] as TransactionInfo[]); - // mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - // [], - // ); - // }); - it('filters received TRC10 tokens with no price data', async () => { await withTransactionService( async ({ From 5f62a55ffe785b2d8ac5507ad220016bcd062927 Mon Sep 17 00:00:00 2001 From: Gabriele Del Monte <37625739+gabrieledm@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:06:24 +0200 Subject: [PATCH 3/3] test: removed beforeEach usage from findByAccounts tests (#114) ## Explanation Fourth PR of Stacked pull requests to remove the usage of `beforeEach` and follow the [Unit Testing Guidelines ](https://github.com/MetaMask/contributor-docs/blob/main/docs/testing/unit-testing.md#avoid-the-use-of-beforeeach) in the file `packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts`. The base PR is: https://github.com/MetaMask/internal-snaps/pull/110 To split the PRs is used the [Stacked pull requests](https://docs.github.com/en/pull-requests/reference/stacked-pull-requests) feature from GitHub ## References ## Checklist - [X] I've updated the test suite for new or updated code as appropriate - [X] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/internal-snaps/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/internal-snaps/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them --- eslint-suppressions.json | 5 - .../transactions/TransactionsService.test.ts | 765 +++++++++--------- 2 files changed, 371 insertions(+), 399 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 44ec2b25..8d0f6735 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1702,11 +1702,6 @@ "count": 4 } }, - "packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/services/wallet/WalletService.test.ts": { "@typescript-eslint/explicit-function-return-type": { "count": 2 diff --git a/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts b/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts index 79c52415..9c59145e 100644 --- a/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts +++ b/packages/tron-wallet-snap/src/services/transactions/TransactionsService.test.ts @@ -62,6 +62,13 @@ type WithTransactionServiceCallback = (payload: { async function withTransactionService( testFunction: WithTransactionServiceCallback, ): Promise { + // Mock the global snap object + Object.defineProperty(globalThis, 'snap', { + value: { request: jest.fn() }, + writable: true, + configurable: true, + }); + const mockTransactionsRepository: jest.Mocked< Pick< TransactionsRepository, @@ -135,14 +142,6 @@ async function withTransactionService( // Import simplified mock data (each file now contains only one transaction) describe('TransactionsService', () => { - let transactionsService: TransactionsService; - let mockLogger: jest.Mocked; - let mockTransactionsRepository: jest.Mocked; - let mockTrongridApiClient: jest.Mocked; - let mockTronHttpClient: jest.Mocked; - let mockPriceApiClient: jest.Mocked; - let mockSnapClient: jest.Mocked; - const mockAccount: TronKeyringAccount = { id: 'test-account-id', address: 'TGJn1wnUYHJbvN88cynZbsAz2EMeZq73yx', @@ -167,65 +166,6 @@ describe('TransactionsService', () => { index: 1, }; - beforeEach(() => { - // Mock the global snap object - const snap = { - request: jest.fn(), - }; - (globalThis as any).snap = snap; - - // Create mocks - mockLogger = { - log: jest.fn(), - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - // Create mock repository - mockTransactionsRepository = { - getAll: jest.fn(), - findByAccountId: jest.fn().mockResolvedValue([]), - getTransactionIdsByAccountId: jest.fn().mockResolvedValue(new Set()), - getConfirmedTransactionIds: jest.fn().mockResolvedValue(new Set()), - save: jest.fn(), - saveMany: jest.fn(), - } as unknown as jest.Mocked; - - // Create mock API client - mockTrongridApiClient = { - getAccountInfoByAddress: jest.fn(), - getTransactionInfoByAddress: jest.fn(), - getContractTransactionInfoByAddress: jest.fn(), - } as unknown as jest.Mocked; - - // Create mock TronHttpClient - mockTronHttpClient = { - getTRC10TokenMetadata: jest.fn(), - getTransactionInfoById: jest.fn(), - } as unknown as jest.Mocked; - - // Create mock PriceApiClient โ€” default: no price data (all tokens filtered unless overridden) - mockPriceApiClient = { - getMultipleSpotPrices: jest.fn().mockResolvedValue({}), - } as unknown as jest.Mocked; - - mockSnapClient = { - trackError: jest.fn().mockResolvedValue(undefined), - } as unknown as jest.Mocked; - - // Create service instance - transactionsService = new TransactionsService({ - logger: mockLogger, - transactionsRepository: mockTransactionsRepository, - trongridApiClient: mockTrongridApiClient, - tronHttpClient: mockTronHttpClient, - priceApiClient: mockPriceApiClient, - snapClient: mockSnapClient, - }); - }); - describe('checkAddressActivity', () => { it('returns true when the address has at least one transaction', async () => { await withTransactionService( @@ -361,7 +301,7 @@ describe('TransactionsService', () => { it('should fetch and map transactions for an account using native transfers mock data', async () => { await withTransactionService( - async ({ mockTrongridApiClient, transactionsService, mockLogger }) => { + async ({ mockTrongridApiClient, transactionsService }) => { // Setup mock responses with simplified single-transaction structure mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ nativeTransferMock, @@ -626,7 +566,7 @@ describe('TransactionsService', () => { it('should handle API errors gracefully', async () => { await withTransactionService( - async ({ mockTrongridApiClient, transactionsService, mockLogger }) => { + async ({ mockTrongridApiClient, transactionsService }) => { // Setup API to throw error const apiError = new Error('API request failed'); mockTrongridApiClient.getTransactionInfoByAddress.mockRejectedValue( @@ -988,7 +928,6 @@ describe('TransactionsService', () => { async ({ mockPriceApiClient, mockTrongridApiClient, - mockLogger, mockSnapClient, transactionsService, }) => { @@ -1036,391 +975,429 @@ describe('TransactionsService', () => { describe('findByAccounts', () => { it('should find transactions for multiple accounts', async () => { - const mockTransactions1: Transaction[] = [ - { - id: 'tx1', - type: 'send', - account: mockAccount.id, - chain: Network.Mainnet, - status: 'confirmed', - timestamp: Math.floor(Date.now() / 1000), - from: [ - { - address: mockAccount.address, - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '100', - unit: 'TRX', - fungible: true, - }, - }, - ], - to: [ - { - address: 'other-address', - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '100', - unit: 'TRX', - fungible: true, - }, - }, - ], - events: [], - fees: [], - }, - ]; - - const mockTransactions2: Transaction[] = [ - { - id: 'tx2', - type: 'receive', - account: mockAccount2.id, - chain: Network.Mainnet, - status: 'confirmed', - timestamp: Math.floor(Date.now() / 1000), - from: [ + await withTransactionService( + async ({ mockTransactionsRepository, transactionsService }) => { + const mockTransactions1: Transaction[] = [ { - address: 'other-address', - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '50', - unit: 'TRX', - fungible: true, - }, + id: 'tx1', + type: 'send', + account: mockAccount.id, + chain: Network.Mainnet, + status: 'confirmed', + timestamp: Math.floor(Date.now() / 1000), + from: [ + { + address: mockAccount.address, + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '100', + unit: 'TRX', + fungible: true, + }, + }, + ], + to: [ + { + address: 'other-address', + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '100', + unit: 'TRX', + fungible: true, + }, + }, + ], + events: [], + fees: [], }, - ], - to: [ + ]; + + const mockTransactions2: Transaction[] = [ { - address: mockAccount2.address, - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '50', - unit: 'TRX', - fungible: true, - }, + id: 'tx2', + type: 'receive', + account: mockAccount2.id, + chain: Network.Mainnet, + status: 'confirmed', + timestamp: Math.floor(Date.now() / 1000), + from: [ + { + address: 'other-address', + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '50', + unit: 'TRX', + fungible: true, + }, + }, + ], + to: [ + { + address: mockAccount2.address, + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '50', + unit: 'TRX', + fungible: true, + }, + }, + ], + events: [], + fees: [], }, - ], - events: [], - fees: [], - }, - ]; + ]; - mockTransactionsRepository.findByAccountId - .mockResolvedValueOnce(mockTransactions1) - .mockResolvedValueOnce(mockTransactions2); + mockTransactionsRepository.findByAccountId + .mockResolvedValueOnce(mockTransactions1) + .mockResolvedValueOnce(mockTransactions2); - const result = await transactionsService.findByAccounts([ - mockAccount, - mockAccount2, - ]); + const result = await transactionsService.findByAccounts([ + mockAccount, + mockAccount2, + ]); - expect(mockTransactionsRepository.findByAccountId).toHaveBeenCalledTimes( - 2, - ); - expect(mockTransactionsRepository.findByAccountId).toHaveBeenCalledWith( - mockAccount.id, - ); - expect(mockTransactionsRepository.findByAccountId).toHaveBeenCalledWith( - mockAccount2.id, + expect( + mockTransactionsRepository.findByAccountId, + ).toHaveBeenCalledTimes(2); + expect( + mockTransactionsRepository.findByAccountId, + ).toHaveBeenCalledWith(mockAccount.id); + expect( + mockTransactionsRepository.findByAccountId, + ).toHaveBeenCalledWith(mockAccount2.id); + expect(result).toHaveLength(2); + expect(true).toBe(true); + }, ); - expect(result).toHaveLength(2); - expect(true).toBe(true); }); it('should handle empty accounts array', async () => { - const result = await transactionsService.findByAccounts([]); + await withTransactionService( + async ({ mockTransactionsRepository, transactionsService }) => { + const result = await transactionsService.findByAccounts([]); - expect(result).toStrictEqual([]); - expect(mockTransactionsRepository.findByAccountId).not.toHaveBeenCalled(); - expect(true).toBe(true); + expect(result).toStrictEqual([]); + expect( + mockTransactionsRepository.findByAccountId, + ).not.toHaveBeenCalled(); + expect(true).toBe(true); + }, + ); }); }); describe('save', () => { it('should save a single transaction', async () => { - const mockTransaction: Transaction = { - id: 'tx-save-test', - type: 'send', - account: mockAccount.id, - chain: Network.Mainnet, - status: 'confirmed', - timestamp: Math.floor(Date.now() / 1000), - from: [ - { - address: mockAccount.address, - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '100', - unit: 'TRX', - fungible: true, - }, - }, - ], - to: [ - { - address: 'other-address', - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '100', - unit: 'TRX', - fungible: true, - }, - }, - ], - events: [], - fees: [], - }; + await withTransactionService( + async ({ mockTransactionsRepository, transactionsService }) => { + const mockTransaction: Transaction = { + id: 'tx-save-test', + type: 'send', + account: mockAccount.id, + chain: Network.Mainnet, + status: 'confirmed', + timestamp: Math.floor(Date.now() / 1000), + from: [ + { + address: mockAccount.address, + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '100', + unit: 'TRX', + fungible: true, + }, + }, + ], + to: [ + { + address: 'other-address', + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '100', + unit: 'TRX', + fungible: true, + }, + }, + ], + events: [], + fees: [], + }; - await transactionsService.save(mockTransaction); + await transactionsService.save(mockTransaction); - expect(mockTransactionsRepository.saveMany).toHaveBeenCalledWith([ - mockTransaction, - ]); - expect(true).toBe(true); + expect(mockTransactionsRepository.saveMany).toHaveBeenCalledWith([ + mockTransaction, + ]); + expect(true).toBe(true); + }, + ); }); }); describe('saveMany', () => { it('should save multiple transactions and emit keyring event', async () => { - const mockTransactions: Transaction[] = [ - { - id: 'tx-bulk-1', - type: 'send', - account: mockAccount.id, - chain: Network.Mainnet, - status: 'confirmed', - timestamp: Math.floor(Date.now() / 1000), - from: [ - { - address: mockAccount.address, - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '100', - unit: 'TRX', - fungible: true, - }, - }, - ], - to: [ - { - address: 'other-address', - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '100', - unit: 'TRX', - fungible: true, - }, - }, - ], - events: [], - fees: [], - }, - { - id: 'tx-bulk-2', - type: 'receive', - account: mockAccount.id, - chain: Network.Mainnet, - status: 'confirmed', - timestamp: Math.floor(Date.now() / 1000), - from: [ + await withTransactionService( + async ({ mockTransactionsRepository, transactionsService }) => { + const mockTransactions: Transaction[] = [ { - address: 'other-address', - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '50', - unit: 'TRX', - fungible: true, - }, + id: 'tx-bulk-1', + type: 'send', + account: mockAccount.id, + chain: Network.Mainnet, + status: 'confirmed', + timestamp: Math.floor(Date.now() / 1000), + from: [ + { + address: mockAccount.address, + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '100', + unit: 'TRX', + fungible: true, + }, + }, + ], + to: [ + { + address: 'other-address', + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '100', + unit: 'TRX', + fungible: true, + }, + }, + ], + events: [], + fees: [], }, - ], - to: [ { - address: mockAccount.address, - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '50', - unit: 'TRX', - fungible: true, - }, + id: 'tx-bulk-2', + type: 'receive', + account: mockAccount.id, + chain: Network.Mainnet, + status: 'confirmed', + timestamp: Math.floor(Date.now() / 1000), + from: [ + { + address: 'other-address', + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '50', + unit: 'TRX', + fungible: true, + }, + }, + ], + to: [ + { + address: mockAccount.address, + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '50', + unit: 'TRX', + fungible: true, + }, + }, + ], + events: [], + fees: [], }, - ], - events: [], - fees: [], - }, - ]; + ]; - await transactionsService.saveMany(mockTransactions); + await transactionsService.saveMany(mockTransactions); - expect(mockTransactionsRepository.saveMany).toHaveBeenCalledWith( - mockTransactions, + expect(mockTransactionsRepository.saveMany).toHaveBeenCalledWith( + mockTransactions, + ); + expect(true).toBe(true); + }, ); - expect(true).toBe(true); }); it('should handle empty transactions array', async () => { - await transactionsService.saveMany([]); + await withTransactionService( + async ({ mockTransactionsRepository, transactionsService }) => { + await transactionsService.saveMany([]); - expect(mockTransactionsRepository.saveMany).toHaveBeenCalledWith([]); - expect(true).toBe(true); + expect(mockTransactionsRepository.saveMany).toHaveBeenCalledWith([]); + expect(true).toBe(true); + }, + ); }); it('should group transactions by account ID correctly', async () => { - const mockTransactions: Transaction[] = [ - { - id: 'tx-account1-1', - type: 'send', - account: mockAccount.id, - chain: Network.Mainnet, - status: 'confirmed', - timestamp: Math.floor(Date.now() / 1000), - from: [ - { - address: mockAccount.address, - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '100', - unit: 'TRX', - fungible: true, - }, - }, - ], - to: [ - { - address: 'other-address', - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '100', - unit: 'TRX', - fungible: true, - }, - }, - ], - events: [], - fees: [], - }, - { - id: 'tx-account1-2', - type: 'receive', - account: mockAccount.id, - chain: Network.Mainnet, - status: 'confirmed', - timestamp: Math.floor(Date.now() / 1000), - from: [ - { - address: 'other-address', - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '25', - unit: 'TRX', - fungible: true, - }, - }, - ], - to: [ + await withTransactionService( + async ({ mockTransactionsRepository, transactionsService }) => { + const mockTransactions: Transaction[] = [ { - address: mockAccount.address, - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '25', - unit: 'TRX', - fungible: true, - }, + id: 'tx-account1-1', + type: 'send', + account: mockAccount.id, + chain: Network.Mainnet, + status: 'confirmed', + timestamp: Math.floor(Date.now() / 1000), + from: [ + { + address: mockAccount.address, + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '100', + unit: 'TRX', + fungible: true, + }, + }, + ], + to: [ + { + address: 'other-address', + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '100', + unit: 'TRX', + fungible: true, + }, + }, + ], + events: [], + fees: [], }, - ], - events: [], - fees: [], - }, - { - id: 'tx-account2-1', - type: 'send', - account: mockAccount2.id, - chain: Network.Mainnet, - status: 'confirmed', - timestamp: Math.floor(Date.now() / 1000), - from: [ { - address: mockAccount2.address, - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '75', - unit: 'TRX', - fungible: true, - }, + id: 'tx-account1-2', + type: 'receive', + account: mockAccount.id, + chain: Network.Mainnet, + status: 'confirmed', + timestamp: Math.floor(Date.now() / 1000), + from: [ + { + address: 'other-address', + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '25', + unit: 'TRX', + fungible: true, + }, + }, + ], + to: [ + { + address: mockAccount.address, + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '25', + unit: 'TRX', + fungible: true, + }, + }, + ], + events: [], + fees: [], }, - ], - to: [ { - address: 'other-address', - asset: { - type: KnownCaip19Id.TrxMainnet, - amount: '75', - unit: 'TRX', - fungible: true, - }, + id: 'tx-account2-1', + type: 'send', + account: mockAccount2.id, + chain: Network.Mainnet, + status: 'confirmed', + timestamp: Math.floor(Date.now() / 1000), + from: [ + { + address: mockAccount2.address, + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '75', + unit: 'TRX', + fungible: true, + }, + }, + ], + to: [ + { + address: 'other-address', + asset: { + type: KnownCaip19Id.TrxMainnet, + amount: '75', + unit: 'TRX', + fungible: true, + }, + }, + ], + events: [], + fees: [], }, - ], - events: [], - fees: [], - }, - ]; + ]; - await transactionsService.saveMany(mockTransactions); + await transactionsService.saveMany(mockTransactions); - expect(mockTransactionsRepository.saveMany).toHaveBeenCalledWith( - mockTransactions, + expect(mockTransactionsRepository.saveMany).toHaveBeenCalledWith( + mockTransactions, + ); + expect(true).toBe(true); + }, ); - expect(true).toBe(true); }); }); describe('Integration scenarios', () => { it('should handle a complete flow: fetch, process, and save transactions', async () => { - // Setup API responses with simplified single-transaction structure - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ - nativeTransferMock, - ] as TransactionInfo[]); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - contractInfoMock.data.slice(0, 1) as ContractTransactionInfo[], - ); + await withTransactionService( + async ({ + mockTransactionsRepository, + mockTrongridApiClient, + transactionsService, + }) => { + // Setup API responses with simplified single-transaction structure + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue([ + nativeTransferMock, + ] as TransactionInfo[]); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + contractInfoMock.data.slice(0, 1) as ContractTransactionInfo[], + ); - // Fetch transactions - const fetchedTransactions = - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount, - ); + // Fetch transactions + const fetchedTransactions = + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount, + ); - // Save the fetched transactions - await transactionsService.saveMany(fetchedTransactions); + // Save the fetched transactions + await transactionsService.saveMany(fetchedTransactions); - expect(mockTransactionsRepository.saveMany).toHaveBeenCalledWith( - fetchedTransactions, + expect(mockTransactionsRepository.saveMany).toHaveBeenCalledWith( + fetchedTransactions, + ); + expect(true).toBe(true); + }, ); - expect(true).toBe(true); }); it('should handle mixed transaction types from different mock data sources', async () => { - // Mix different types of transactions with simplified structure - const mixedRawTransactions = [ - nativeTransferMock, // Native TRX transfer - trc10TransferMock, // TRC10 transfer - trc20TransferMock, // TRC20 transfer - ] as TransactionInfo[]; - - mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue( - mixedRawTransactions, - ); - mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( - [], - ); + await withTransactionService( + async ({ mockTrongridApiClient, transactionsService }) => { + // Mix different types of transactions with simplified structure + const mixedRawTransactions = [ + nativeTransferMock, // Native TRX transfer + trc10TransferMock, // TRC10 transfer + trc20TransferMock, // TRC20 transfer + ] as TransactionInfo[]; - await transactionsService.fetchNewTransactionsForAccount( - Network.Mainnet, - mockAccount2, - ); + mockTrongridApiClient.getTransactionInfoByAddress.mockResolvedValue( + mixedRawTransactions, + ); + mockTrongridApiClient.getContractTransactionInfoByAddress.mockResolvedValue( + [], + ); - expect(true).toBe(true); + await transactionsService.fetchNewTransactionsForAccount( + Network.Mainnet, + mockAccount2, + ); + + expect(true).toBe(true); + }, + ); }); }); });