import { describe, it, expect, vi, beforeEach } from 'vitest'; import { WithdrawFromRaceUseCase, type WithdrawFromRaceInput, type WithdrawFromRaceResult, type WithdrawFromRaceErrorCode, } from './WithdrawFromRaceUseCase'; import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository'; import type { Logger } from '@core/shared/application/Logger'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { Result } from '@core/shared/application/Result'; describe('WithdrawFromRaceUseCase', () => { let raceRepository: IRaceRepository; let registrationRepository: IRaceRegistrationRepository; let logger: Logger; let output: UseCaseOutputPort & { present: ReturnType }; beforeEach(() => { raceRepository = { findById: vi.fn(), } as unknown as IRaceRepository; registrationRepository = { isRegistered: vi.fn(), withdraw: vi.fn(), } as unknown as IRaceRegistrationRepository; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), }; output = { present: vi.fn() } as any; }); const createUseCase = () => new WithdrawFromRaceUseCase(raceRepository, registrationRepository, logger, output); it('withdraws from race successfully', async () => { const race = { id: 'race-1', isUpcoming: vi.fn().mockReturnValue(true), } as any; (raceRepository.findById as any).mockResolvedValue(race); (registrationRepository.isRegistered as any).mockResolvedValue(true); (registrationRepository.withdraw as any).mockResolvedValue(undefined); const useCase = createUseCase(); const input: WithdrawFromRaceInput = { raceId: 'race-1', driverId: 'driver-1', }; const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); expect(output.present).toHaveBeenCalledWith({ raceId: 'race-1', driverId: 'driver-1', status: 'withdrawn', }); expect(registrationRepository.withdraw).toHaveBeenCalledWith('race-1', 'driver-1'); }); it('returns error when race is not found', async () => { (raceRepository.findById as any).mockResolvedValue(null); const useCase = createUseCase(); const input: WithdrawFromRaceInput = { raceId: 'race-unknown', driverId: 'driver-1', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr() as ApplicationErrorCode; expect(error.code).toBe('RACE_NOT_FOUND'); expect(error.details?.message).toContain('Race race-unknown not found'); expect(output.present).not.toHaveBeenCalled(); }); it('returns error when registration is not found', async () => { const race = { id: 'race-1', isUpcoming: vi.fn().mockReturnValue(true), } as any; (raceRepository.findById as any).mockResolvedValue(race); (registrationRepository.isRegistered as any).mockResolvedValue(false); const useCase = createUseCase(); const input: WithdrawFromRaceInput = { raceId: 'race-1', driverId: 'driver-unknown', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr() as ApplicationErrorCode; expect(error.code).toBe('REGISTRATION_NOT_FOUND'); expect(error.details?.message).toContain('Driver driver-unknown is not registered for race race-1'); expect(output.present).not.toHaveBeenCalled(); }); it('returns error when withdrawal is not allowed', async () => { const race = { id: 'race-1', isUpcoming: vi.fn().mockReturnValue(false), } as any; (raceRepository.findById as any).mockResolvedValue(race); (registrationRepository.isRegistered as any).mockResolvedValue(true); const useCase = createUseCase(); const input: WithdrawFromRaceInput = { raceId: 'race-1', driverId: 'driver-1', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr() as ApplicationErrorCode; expect(error.code).toBe('WITHDRAWAL_NOT_ALLOWED'); expect(error.details?.message).toContain('Withdrawal is not allowed for this race'); expect(output.present).not.toHaveBeenCalled(); }); it('wraps repository errors and logs them', async () => { const race = { id: 'race-1', isUpcoming: vi.fn().mockReturnValue(true), } as any; (raceRepository.findById as any).mockResolvedValue(race); (registrationRepository.isRegistered as any).mockResolvedValue(true); (registrationRepository.withdraw as any).mockRejectedValue(new Error('DB failure')); const useCase = createUseCase(); const input: WithdrawFromRaceInput = { raceId: 'race-1', driverId: 'driver-1', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr() as ApplicationErrorCode; expect(error.code).toBe('REPOSITORY_ERROR'); expect(error.details?.message).toBe('DB failure'); expect(output.present).not.toHaveBeenCalled(); expect(logger.error).toHaveBeenCalled(); }); });