import type { Logger } from '@core/shared/domain/Logger'; import { Result } from '@core/shared/domain/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { LeagueRepository } from '../../domain/repositories/LeagueRepository'; import type { ProtestRepository } from '../../domain/repositories/ProtestRepository'; import { SubmitProtestDefenseUseCase, type SubmitProtestDefenseErrorCode, type SubmitProtestDefenseInput, type SubmitProtestDefenseResult, } from './SubmitProtestDefenseUseCase'; interface MockProtest { id: string; accusedDriverId: string; canSubmitDefense: ReturnType; submitDefense: ReturnType; } describe('SubmitProtestDefenseUseCase', () => { let leagueRepository: LeagueRepository & { findById: ReturnType }; let protestRepository: ProtestRepository & { findById: ReturnType; update: ReturnType }; let logger: Logger & { error: ReturnType }; let useCase: SubmitProtestDefenseUseCase; const createInput = (overrides: Partial = {}): SubmitProtestDefenseInput => ({ leagueId: 'league-1', protestId: 'protest-1', driverId: 'driver-1', defenseText: 'My defense', videoUrl: 'http://video.com', ...overrides, }); const unwrapError = ( result: Result>, ): ApplicationErrorCode => { expect(result.isErr()).toBe(true); return result.unwrapErr(); }; beforeEach(() => { leagueRepository = { findById: vi.fn(), findAll: vi.fn(), findByOwnerId: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn(), exists: vi.fn(), searchByName: vi.fn(), } as unknown as LeagueRepository & { findById: ReturnType }; protestRepository = { findById: vi.fn(), findByRaceId: vi.fn(), findByProtestingDriverId: vi.fn(), findByAccusedDriverId: vi.fn(), findPending: vi.fn(), findUnderReviewBy: vi.fn(), create: vi.fn(), update: vi.fn(), exists: vi.fn(), } as unknown as ProtestRepository & { findById: ReturnType; update: ReturnType; }; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger & { error: ReturnType }; useCase = new SubmitProtestDefenseUseCase(leagueRepository as unknown as LeagueRepository, protestRepository as unknown as ProtestRepository, logger as unknown as Logger); }); it('submits defense successfully', async () => { const mockProtest: MockProtest = { id: 'protest-1', accusedDriverId: 'driver-1', canSubmitDefense: vi.fn().mockReturnValue(true), submitDefense: vi.fn().mockReturnValue({ id: 'protest-1' }), } as unknown as MockProtest; leagueRepository.findById.mockResolvedValue({ id: 'league-1' }); protestRepository.findById.mockResolvedValue(mockProtest); protestRepository.update.mockResolvedValue(undefined); const input = createInput(); const result = await useCase.execute(input); expect(result.isOk()).toBe(true); const value = result.unwrap(); expect(value.leagueId).toBe('league-1'); expect(value.protestId).toBe('protest-1'); expect(value.driverId).toBe('driver-1'); expect(value.status).toBe('defense_submitted'); expect(leagueRepository.findById).toHaveBeenCalledWith('league-1'); expect(protestRepository.findById).toHaveBeenCalledWith('protest-1'); expect(mockProtest.canSubmitDefense).toHaveBeenCalled(); expect(mockProtest.submitDefense).toHaveBeenCalledWith('My defense', 'http://video.com'); expect(protestRepository.update).toHaveBeenCalled(); }); it('returns error when league not found', async () => { leagueRepository.findById.mockResolvedValue(null); const input = createInput(); const result = await useCase.execute(input); const error = unwrapError(result); expect(error.code).toBe('LEAGUE_NOT_FOUND'); expect(error.details?.message).toBe('League not found'); }); it('returns error when protest not found', async () => { leagueRepository.findById.mockResolvedValue({ id: 'league-1' }); protestRepository.findById.mockResolvedValue(null); const input = createInput(); const result = await useCase.execute(input); const error = unwrapError(result); expect(error.code).toBe('PROTEST_NOT_FOUND'); expect(error.details?.message).toBe('Protest not found'); }); it('returns error when driver is not allowed', async () => { const mockProtest = { id: 'protest-1', accusedDriverId: 'driver-2', } as unknown as MockProtest; leagueRepository.findById.mockResolvedValue({ id: 'league-1' }); protestRepository.findById.mockResolvedValue(mockProtest); const input = createInput({ driverId: 'driver-1' }); const result = await useCase.execute(input); const error = unwrapError(result); expect(error.code).toBe('DRIVER_NOT_ALLOWED'); expect(error.details?.message).toBe('Driver is not allowed to submit a defense for this protest'); }); it('returns error when defense cannot be submitted due to invalid state', async () => { const mockProtest: MockProtest = { id: 'protest-1', accusedDriverId: 'driver-1', canSubmitDefense: vi.fn().mockReturnValue(false), submitDefense: vi.fn(), } as unknown as MockProtest; leagueRepository.findById.mockResolvedValue({ id: 'league-1' }); protestRepository.findById.mockResolvedValue(mockProtest); const input = createInput(); const result = await useCase.execute(input); const error = unwrapError(result); expect(error.code).toBe('INVALID_PROTEST_STATE'); expect(error.details?.message).toBe('Defense cannot be submitted for this protest'); expect(mockProtest.submitDefense).not.toHaveBeenCalled(); }); it('returns repository error when update throws', async () => { const mockProtest: MockProtest = { id: 'protest-1', accusedDriverId: 'driver-1', canSubmitDefense: vi.fn().mockReturnValue(true), submitDefense: vi.fn().mockReturnValue({ id: 'protest-1' }), } as unknown as MockProtest; leagueRepository.findById.mockResolvedValue({ id: 'league-1' }); protestRepository.findById.mockResolvedValue(mockProtest); protestRepository.update.mockRejectedValue(new Error('DB failure')); const input = createInput(); const result = await useCase.execute(input); const error = unwrapError(result); expect(error.code).toBe('REPOSITORY_ERROR'); expect(error.details?.message).toBe('DB failure'); expect(logger.error).toHaveBeenCalled(); }); });