import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest'; import { UpdatePaymentStatusUseCase, type UpdatePaymentStatusInput } from './UpdatePaymentStatusUseCase'; import type { IPaymentRepository } from '../../domain/repositories/IPaymentRepository'; import { PaymentStatus, PaymentType, PayerType, type Payment } from '../../domain/entities/Payment'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; describe('UpdatePaymentStatusUseCase', () => { let paymentRepository: { findById: Mock; update: Mock; }; let output: { present: Mock; }; let useCase: UpdatePaymentStatusUseCase; beforeEach(() => { paymentRepository = { findById: vi.fn(), update: vi.fn(), }; output = { present: vi.fn(), }; useCase = new UpdatePaymentStatusUseCase( paymentRepository as unknown as IPaymentRepository, output as unknown as UseCaseOutputPort, ); }); it('returns PAYMENT_NOT_FOUND when payment does not exist', async () => { const input: UpdatePaymentStatusInput = { paymentId: 'payment-1', status: PaymentStatus.COMPLETED, }; paymentRepository.findById.mockResolvedValue(null); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('PAYMENT_NOT_FOUND'); expect(paymentRepository.update).not.toHaveBeenCalled(); expect(output.present).not.toHaveBeenCalled(); }); it('sets completedAt when status becomes COMPLETED', async () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z')); try { const input: UpdatePaymentStatusInput = { paymentId: 'payment-1', status: PaymentStatus.COMPLETED, }; const existingPayment: Payment = { id: 'payment-1', type: PaymentType.SPONSORSHIP, amount: 100, platformFee: 5, netAmount: 95, payerId: 'payer-1', payerType: PayerType.SPONSOR, leagueId: 'league-1', status: PaymentStatus.PENDING, createdAt: new Date('2024-12-31T00:00:00.000Z'), }; paymentRepository.findById.mockResolvedValue(existingPayment); paymentRepository.update.mockImplementation(async (p: Payment) => ({ ...p })); const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(paymentRepository.update).toHaveBeenCalledWith( expect.objectContaining({ id: 'payment-1', status: PaymentStatus.COMPLETED, completedAt: new Date('2025-01-01T00:00:00.000Z'), }), ); const savedPayment = paymentRepository.update.mock.results[0]?.value; await expect(savedPayment).resolves.toEqual( expect.objectContaining({ id: 'payment-1', status: PaymentStatus.COMPLETED, completedAt: new Date('2025-01-01T00:00:00.000Z'), }), ); const presentedPayment = (output.present.mock.calls[0]?.[0] as { payment: Payment }).payment; expect(presentedPayment.status).toBe(PaymentStatus.COMPLETED); expect(presentedPayment.completedAt).toEqual(new Date('2025-01-01T00:00:00.000Z')); } finally { vi.useRealTimers(); } }); it('preserves completedAt when status is not COMPLETED', async () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z')); try { const input: UpdatePaymentStatusInput = { paymentId: 'payment-1', status: PaymentStatus.FAILED, }; const existingCompletedAt = new Date('2025-01-01T00:00:00.000Z'); const existingPayment: Payment = { id: 'payment-1', type: PaymentType.SPONSORSHIP, amount: 100, platformFee: 5, netAmount: 95, payerId: 'payer-1', payerType: PayerType.SPONSOR, leagueId: 'league-1', status: PaymentStatus.COMPLETED, createdAt: new Date('2024-12-31T00:00:00.000Z'), completedAt: existingCompletedAt, }; paymentRepository.findById.mockResolvedValue(existingPayment); paymentRepository.update.mockImplementation(async (p: Payment) => ({ ...p })); const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(paymentRepository.update).toHaveBeenCalledWith( expect.objectContaining({ id: 'payment-1', status: PaymentStatus.FAILED, completedAt: existingCompletedAt, }), ); const presentedPayment = (output.present.mock.calls[0]?.[0] as { payment: Payment }).payment; expect(presentedPayment.status).toBe(PaymentStatus.FAILED); expect(presentedPayment.completedAt).toEqual(existingCompletedAt); } finally { vi.useRealTimers(); } }); });