import { describe, it, expect, vi, type Mock } from 'vitest'; import { GetMembershipFeesUseCase, type GetMembershipFeesInput } from './GetMembershipFeesUseCase'; import type { IMembershipFeeRepository, IMemberPaymentRepository } from '../../domain/repositories/IMembershipFeeRepository'; import type { IGetMembershipFeesPresenter, GetMembershipFeesResultDTO, GetMembershipFeesViewModel } from '../presenters/IGetMembershipFeesPresenter'; interface TestPresenter extends IGetMembershipFeesPresenter { reset: Mock; present: Mock; lastDto?: GetMembershipFeesResultDTO; viewModel?: GetMembershipFeesViewModel; } describe('GetMembershipFeesUseCase', () => { let membershipFeeRepository: { findByLeagueId: Mock; }; let memberPaymentRepository: { findByLeagueIdAndDriverId: Mock; }; let presenter: TestPresenter; let useCase: GetMembershipFeesUseCase; beforeEach(() => { membershipFeeRepository = { findByLeagueId: vi.fn(), } as unknown as IMembershipFeeRepository as any; memberPaymentRepository = { findByLeagueIdAndDriverId: vi.fn(), } as unknown as IMemberPaymentRepository as any; presenter = { reset: vi.fn(), present: vi.fn((dto: GetMembershipFeesResultDTO) => { presenter.lastDto = dto; }), toViewModel: vi.fn((dto: GetMembershipFeesResultDTO) => ({ fee: dto.fee, payments: dto.payments, })), } as unknown as TestPresenter; useCase = new GetMembershipFeesUseCase( membershipFeeRepository as unknown as IMembershipFeeRepository, memberPaymentRepository as unknown as IMemberPaymentRepository, ); }); it('throws when leagueId is missing', async () => { const input = { leagueId: '' } as GetMembershipFeesInput; await expect(useCase.execute(input, presenter)).rejects.toThrow('leagueId is required'); expect(presenter.reset).toHaveBeenCalled(); }); it('returns null fee and empty payments when no fee exists', async () => { const input: GetMembershipFeesInput = { leagueId: 'league-1' }; membershipFeeRepository.findByLeagueId.mockResolvedValue(null); await useCase.execute(input, presenter); expect(membershipFeeRepository.findByLeagueId).toHaveBeenCalledWith('league-1'); expect(memberPaymentRepository.findByLeagueIdAndDriverId).not.toHaveBeenCalled(); expect(presenter.present).toHaveBeenCalledWith({ fee: null, payments: [], }); }); it('maps fee and payments when fee and driverId are provided', async () => { const input: GetMembershipFeesInput = { leagueId: 'league-1', driverId: 'driver-1' }; const fee = { id: 'fee-1', leagueId: 'league-1', seasonId: 'season-1', type: 'season', amount: 100, enabled: true, createdAt: new Date('2024-01-01'), updatedAt: new Date('2024-01-02'), }; const payments = [ { id: 'pay-1', feeId: 'fee-1', driverId: 'driver-1', amount: 100, platformFee: 5, netAmount: 95, status: 'paid', dueDate: new Date('2024-02-01'), paidAt: new Date('2024-01-15'), }, ]; membershipFeeRepository.findByLeagueId.mockResolvedValue(fee); memberPaymentRepository.findByLeagueIdAndDriverId.mockResolvedValue(payments); await useCase.execute(input, presenter); expect(membershipFeeRepository.findByLeagueId).toHaveBeenCalledWith('league-1'); expect(memberPaymentRepository.findByLeagueIdAndDriverId).toHaveBeenCalledWith('league-1', 'driver-1', membershipFeeRepository as unknown as IMembershipFeeRepository); expect(presenter.present).toHaveBeenCalledWith({ fee: { id: fee.id, leagueId: fee.leagueId, seasonId: fee.seasonId, type: fee.type, amount: fee.amount, enabled: fee.enabled, createdAt: fee.createdAt, updatedAt: fee.updatedAt, }, payments: [ { id: 'pay-1', feeId: 'fee-1', driverId: 'driver-1', amount: 100, platformFee: 5, netAmount: 95, status: 'paid', dueDate: payments[0].dueDate, paidAt: payments[0].paidAt, }, ], }); }); });