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); + }, + ); }); }); });