import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest'; import { MemberPaymentStatus, type MemberPayment } from '../../domain/entities/MemberPayment'; import type { MemberPaymentRepository, MembershipFeeRepository } from '../../domain/repositories/MembershipFeeRepository'; import { UpdateMemberPaymentUseCase, type UpdateMemberPaymentInput } from './UpdateMemberPaymentUseCase'; describe('UpdateMemberPaymentUseCase', () => { let membershipFeeRepository: { findById: Mock; }; let memberPaymentRepository: { findByFeeIdAndDriverId: Mock; create: Mock; update: Mock; }; let useCase: UpdateMemberPaymentUseCase; beforeEach(() => { membershipFeeRepository = { findById: vi.fn(), }; memberPaymentRepository = { findByFeeIdAndDriverId: vi.fn(), create: vi.fn(), update: vi.fn(), }; useCase = new UpdateMemberPaymentUseCase( membershipFeeRepository as unknown as MembershipFeeRepository, memberPaymentRepository as unknown as MemberPaymentRepository, ); }); it('returns MEMBERSHIP_FEE_NOT_FOUND when fee does not exist', async () => { const input: UpdateMemberPaymentInput = { feeId: 'fee-1', driverId: 'driver-1', }; membershipFeeRepository.findById.mockResolvedValue(null); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('MEMBERSHIP_FEE_NOT_FOUND'); expect(memberPaymentRepository.findByFeeIdAndDriverId).not.toHaveBeenCalled(); expect(memberPaymentRepository.create).not.toHaveBeenCalled(); expect(memberPaymentRepository.update).not.toHaveBeenCalled(); }); it('creates a new payment when missing, applies status and paidAt when PAID', async () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z')); vi.spyOn(Math, 'random').mockReturnValue(0.123456789); try { const input: UpdateMemberPaymentInput = { feeId: 'fee-1', driverId: 'driver-1', status: MemberPaymentStatus.PAID, }; const fee = { id: 'fee-1', leagueId: 'league-1', type: 'season', amount: 100, enabled: true, createdAt: new Date('2024-01-01T00:00:00.000Z'), updatedAt: new Date('2024-01-02T00:00:00.000Z'), }; membershipFeeRepository.findById.mockResolvedValue(fee); memberPaymentRepository.findByFeeIdAndDriverId.mockResolvedValue(null); memberPaymentRepository.create.mockImplementation(async (p: MemberPayment) => ({ ...p })); memberPaymentRepository.update.mockImplementation(async (p: MemberPayment) => p); const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(memberPaymentRepository.create).toHaveBeenCalledWith( expect.objectContaining({ id: expect.stringMatching(/^mp-1735689600000-[a-z0-9]{9}$/), feeId: 'fee-1', driverId: 'driver-1', amount: 100, platformFee: 10, netAmount: 90, status: MemberPaymentStatus.PENDING, dueDate: new Date('2025-01-01T00:00:00.000Z'), }), ); expect(memberPaymentRepository.update).toHaveBeenCalledWith( expect.objectContaining({ status: MemberPaymentStatus.PAID, paidAt: new Date('2025-01-01T00:00:00.000Z'), }), ); const value = result.value; const updated = memberPaymentRepository.update.mock.calls[0]?.[0] as MemberPayment; expect(value.payment).toEqual(updated); } finally { vi.useRealTimers(); } }); it('updates existing payment status and parses paidAt string', async () => { const input: UpdateMemberPaymentInput = { feeId: 'fee-1', driverId: 'driver-1', status: MemberPaymentStatus.PAID, paidAt: '2025-02-01T00:00:00.000Z', }; const fee = { id: 'fee-1', leagueId: 'league-1', type: 'season', amount: 100, enabled: true, createdAt: new Date('2024-01-01T00:00:00.000Z'), updatedAt: new Date('2024-01-02T00:00:00.000Z'), }; const existingPayment: MemberPayment = { id: 'mp-1', feeId: 'fee-1', driverId: 'driver-1', amount: 100, platformFee: 10, netAmount: 90, status: MemberPaymentStatus.PENDING, dueDate: new Date('2025-01-01T00:00:00.000Z'), }; membershipFeeRepository.findById.mockResolvedValue(fee); memberPaymentRepository.findByFeeIdAndDriverId.mockResolvedValue(existingPayment); memberPaymentRepository.update.mockImplementation(async (p: MemberPayment) => p); const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(memberPaymentRepository.update).toHaveBeenCalledWith( expect.objectContaining({ id: 'mp-1', status: MemberPaymentStatus.PAID, paidAt: new Date('2025-02-01T00:00:00.000Z'), }), ); const value = result.value; const updated = memberPaymentRepository.update.mock.calls[0]?.[0] as MemberPayment; expect(value.payment).toEqual(updated); }); });