UNPKG

@lukasrosario/account

Version:

Base Account SDK

297 lines (260 loc) 8.64 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { getPaymentStatus } from './getPaymentStatus.js'; import type { PaymentStatus } from './types.js'; // Mock fetch globally global.fetch = vi.fn(); // Mock telemetry events vi.mock(':core/telemetry/events/payment.js', () => ({ logPaymentStatusCheckStarted: vi.fn(), logPaymentStatusCheckCompleted: vi.fn(), logPaymentStatusCheckError: vi.fn(), })); describe('getPaymentStatus', () => { beforeEach(() => { vi.clearAllMocks(); // Mock console methods to avoid noise in tests vi.spyOn(console, 'log').mockImplementation(() => {}); vi.spyOn(console, 'error').mockImplementation(() => {}); // Mock crypto.randomUUID vi.stubGlobal('crypto', { randomUUID: vi.fn().mockReturnValue('mock-correlation-id'), }); }); it('should return completed status for successful payment', async () => { const mockReceipt = { jsonrpc: '2.0', id: 1, result: { success: true, receipt: { transactionHash: '0xabc123', logs: [ { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', data: '0x0000000000000000000000000000000000000000000000000000000000989680', // 10 USDC (10 * 10^6) topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // Transfer event topic '0x0000000000000000000000004a7c6899cdcb379e284fbfd045462e751da4c7ce', // from address (padded) '0x000000000000000000000000f1ddf1fc0310cb11f0ca87508207012f4a9cb336', // to address (padded) ], }, ], }, sender: '0xsender', }, }; vi.mocked(fetch).mockResolvedValueOnce({ json: async () => mockReceipt, } as Response); const status = await getPaymentStatus({ id: '0x123456', testnet: false, }); expect(status).toEqual<PaymentStatus>({ status: 'completed', id: '0x123456', message: 'Payment completed successfully', sender: '0xsender', amount: '10', recipient: '0xf1DdF1fc0310Cb11F0Ca87508207012F4a9CB336', }); expect(fetch).toHaveBeenCalledWith( 'https://api.developer.coinbase.com/rpc/v1/base/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O', expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_getUserOperationReceipt', params: ['0x123456'], }), }) ); }); it('should return failed status for failed payment', async () => { const mockReceipt = { jsonrpc: '2.0', id: 1, result: { success: false, receipt: { transactionHash: '0xdef456', }, sender: '0xsender', reason: 'Insufficient USDC balance', }, }; vi.mocked(fetch).mockResolvedValueOnce({ json: async () => mockReceipt, } as Response); const status = await getPaymentStatus({ id: '0x789abc', testnet: false, }); expect(status).toEqual<PaymentStatus>({ status: 'failed', id: '0x789abc', message: 'Payment failed', sender: '0xsender', error: 'Insufficient USDC balance', }); }); it('should return pending status when userOp exists but no receipt', async () => { // First call returns no receipt vi.mocked(fetch).mockResolvedValueOnce({ json: async () => ({ jsonrpc: '2.0', id: 1, result: null }), } as Response); // Second call returns userOp vi.mocked(fetch).mockResolvedValueOnce({ json: async () => ({ jsonrpc: '2.0', id: 2, result: { sender: '0xpendingSender', // other userOp fields... }, }), } as Response); const status = await getPaymentStatus({ id: '0xpending123', testnet: false, }); expect(status).toEqual<PaymentStatus>({ status: 'pending', id: '0xpending123', message: 'Your payment is being processed. This usually takes a few seconds.', sender: '0xpendingSender', }); expect(fetch).toHaveBeenCalledTimes(2); }); it('should return not_found status when payment does not exist', async () => { // Both calls return null vi.mocked(fetch) .mockResolvedValueOnce({ json: async () => ({ jsonrpc: '2.0', id: 1, result: null }), } as Response) .mockResolvedValueOnce({ json: async () => ({ jsonrpc: '2.0', id: 2, result: null }), } as Response); const status = await getPaymentStatus({ id: '0xnotfound', testnet: false, }); expect(status).toEqual<PaymentStatus>({ status: 'not_found', id: '0xnotfound', message: 'Payment not found. Please check your transaction ID.', }); }); it('should handle RPC errors gracefully', async () => { vi.mocked(fetch).mockResolvedValueOnce({ json: async () => ({ jsonrpc: '2.0', id: 1, error: { code: -32602, message: 'Invalid params', }, }), } as Response); const status = await getPaymentStatus({ id: '0xinvalid', testnet: false, }); expect(status).toEqual<PaymentStatus>({ status: 'failed', id: '0xinvalid', message: 'Unable to check payment status. Please try again later.', error: 'Invalid params', }); }); it('should handle network errors gracefully', async () => { vi.mocked(fetch).mockRejectedValueOnce(new Error('Network error')); const status = await getPaymentStatus({ id: '0xnetworkerror', testnet: false, }); expect(status).toEqual<PaymentStatus>({ status: 'failed', id: '0xnetworkerror', message: 'Unable to check payment status', error: 'Network error', }); }); it('should use testnet bundler URL when testnet is true', async () => { vi.mocked(fetch).mockResolvedValueOnce({ json: async () => ({ jsonrpc: '2.0', id: 1, result: null }), } as Response); vi.mocked(fetch).mockResolvedValueOnce({ json: async () => ({ jsonrpc: '2.0', id: 2, result: null }), } as Response); await getPaymentStatus({ id: '0xtestnet', testnet: true, }); expect(fetch).toHaveBeenCalledWith( 'https://api.developer.coinbase.com/rpc/v1/base-sepolia/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O', expect.any(Object) ); }); it('should parse user-friendly failure reasons', async () => { const testCases = [ { reason: 'execution reverted: insufficient balance', expected: 'Insufficient USDC balance' }, { reason: 'transaction reverted', expected: 'Payment was rejected' }, { reason: 'custom error message', expected: 'custom error message' }, ]; for (const { reason, expected } of testCases) { vi.mocked(fetch).mockResolvedValueOnce({ json: async () => ({ jsonrpc: '2.0', id: 1, result: { success: false, receipt: { transactionHash: '0xfailed' }, sender: '0xsender', reason, }, }), } as Response); const status = await getPaymentStatus({ id: '0xfailedreason', testnet: false, }); expect(status.error).toBe(expected); } }); it('should handle logs with different USDC addresses on testnet', async () => { const mockReceipt = { jsonrpc: '2.0', id: 1, result: { success: true, receipt: { transactionHash: '0xabc123', logs: [ { address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // testnet USDC data: '0x0000000000000000000000000000000000000000000000000000000000989680', // 10 USDC topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x0000000000000000000000004a7c6899cdcb379e284fbfd045462e751da4c7ce', // from address (padded) '0x000000000000000000000000f1ddf1fc0310cb11f0ca87508207012f4a9cb336', // to address (padded) ], }, ], }, sender: '0xsender', }, }; vi.mocked(fetch).mockResolvedValueOnce({ json: async () => mockReceipt, } as Response); const status = await getPaymentStatus({ id: '0x123456', testnet: true, }); expect(status.amount).toBe('10'); expect(status.recipient).toBe('0xf1DdF1fc0310Cb11F0Ca87508207012F4a9CB336'); }); });