import { describe, it, expect, vi, beforeEach } from 'vitest'; import { WalletService } from './WalletService'; import { PaymentsApiClient } from '../../api/payments/PaymentsApiClient'; import type { GetWalletOutputDto } from '../../dtos'; import { presentWallet } from '../../presenters/WalletPresenter'; // Mock the presenter vi.mock('../../presenters/WalletPresenter', () => ({ presentWallet: vi.fn(), })); describe('WalletService', () => { let mockApiClient: PaymentsApiClient; let service: WalletService; beforeEach(() => { mockApiClient = { getWallet: vi.fn(), getMembershipFees: vi.fn(), getPayments: vi.fn(), createPayment: vi.fn(), getPrizes: vi.fn(), } as unknown as PaymentsApiClient; service = new WalletService(mockApiClient); }); describe('getWallet', () => { it('should get wallet via API client and present it', async () => { // Arrange const driverId = 'driver-1'; const dto: GetWalletOutputDto = { balance: 1000, currency: 'USD', transactions: [], }; const expectedViewModel = { balance: 1000, currency: 'USD', transactions: [], }; vi.mocked(mockApiClient.getWallet).mockResolvedValue(dto); vi.mocked(presentWallet).mockReturnValue(expectedViewModel); // Act const result = await service.getWallet(driverId); // Assert expect(mockApiClient.getWallet).toHaveBeenCalledWith(driverId); expect(mockApiClient.getWallet).toHaveBeenCalledTimes(1); expect(presentWallet).toHaveBeenCalledWith(dto); expect(result).toBe(expectedViewModel); }); it('should propagate API client errors', async () => { // Arrange const driverId = 'driver-1'; const error = new Error('API Error: Failed to get wallet'); vi.mocked(mockApiClient.getWallet).mockRejectedValue(error); // Act & Assert await expect(service.getWallet(driverId)).rejects.toThrow( 'API Error: Failed to get wallet' ); expect(mockApiClient.getWallet).toHaveBeenCalledWith(driverId); expect(mockApiClient.getWallet).toHaveBeenCalledTimes(1); }); it('should handle different driver IDs', async () => { // Arrange const driverId = 'driver-2'; const dto: GetWalletOutputDto = { balance: 500, currency: 'EUR', transactions: [], }; const expectedViewModel = { balance: 500, currency: 'EUR', transactions: [], }; vi.mocked(mockApiClient.getWallet).mockResolvedValue(dto); vi.mocked(presentWallet).mockReturnValue(expectedViewModel); // Act const result = await service.getWallet(driverId); // Assert expect(mockApiClient.getWallet).toHaveBeenCalledWith(driverId); expect(result).toBe(expectedViewModel); }); }); describe('Constructor Dependency Injection', () => { it('should require apiClient', () => { // This test verifies the constructor signature expect(() => { new WalletService(mockApiClient); }).not.toThrow(); }); it('should use injected apiClient', async () => { // Arrange const customApiClient = { getWallet: vi.fn().mockResolvedValue({ balance: 200, currency: 'USD', transactions: [] }), getMembershipFees: vi.fn(), getPayments: vi.fn(), createPayment: vi.fn(), getPrizes: vi.fn(), } as unknown as PaymentsApiClient; const customService = new WalletService(customApiClient); vi.mocked(presentWallet).mockReturnValue({ balance: 200, currency: 'USD', transactions: [] }); // Act await customService.getWallet('driver-1'); // Assert expect(customApiClient.getWallet).toHaveBeenCalledWith('driver-1'); }); }); });