import { describe, it, expect, beforeEach, vi, Mock } from 'vitest'; import { ApplyPenaltyUseCase, type ApplyPenaltyResult } from './ApplyPenaltyUseCase'; import type { IPenaltyRepository } from '../../domain/repositories/IPenaltyRepository'; import type { IProtestRepository } from '../../domain/repositories/IProtestRepository'; import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; describe('ApplyPenaltyUseCase', () => { let mockPenaltyRepo: { create: Mock; }; let mockProtestRepo: { findById: Mock; }; let mockRaceRepo: { findById: Mock; }; let mockLeagueMembershipRepo: { getLeagueMembers: Mock; }; let mockLogger: { debug: Mock; warn: Mock; info: Mock; error: Mock; }; beforeEach(() => { mockPenaltyRepo = { create: vi.fn(), }; mockProtestRepo = { findById: vi.fn(), }; mockRaceRepo = { findById: vi.fn(), }; mockLeagueMembershipRepo = { getLeagueMembers: vi.fn(), }; mockLogger = { debug: vi.fn(), warn: vi.fn(), info: vi.fn(), error: vi.fn(), }; }); it('should return error when race does not exist', async () => { const output: { present: Mock } = { present: vi.fn(), }; const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository, mockProtestRepo as unknown as IProtestRepository, mockRaceRepo as unknown as IRaceRepository, mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository, mockLogger as unknown as Logger); mockRaceRepo.findById.mockResolvedValue(null); const result = await useCase.execute({ raceId: 'nonexistent', driverId: 'driver1', stewardId: 'steward1', type: 'time_penalty', value: 5, reason: 'Test penalty', }); expect(result.isOk()).toBe(false); expect(result.error!.code).toBe('RACE_NOT_FOUND'); }); it('should return error when steward does not have authority', async () => { const output: { present: Mock } = { present: vi.fn(), }; const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository, mockProtestRepo as unknown as IProtestRepository, mockRaceRepo as unknown as IRaceRepository, mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository, mockLogger as unknown as Logger); mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' }); const membership = { driverId: { toString: () => 'steward1' }, role: { toString: () => 'member' }, status: { toString: () => 'active' }, }; mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([membership]); const result = await useCase.execute({ raceId: 'race1', driverId: 'driver1', stewardId: 'steward1', type: 'time_penalty', value: 5, reason: 'Test penalty', }); expect(result.isOk()).toBe(false); expect(result.error!.code).toBe('INSUFFICIENT_AUTHORITY'); }); it('should return error when protest does not exist', async () => { const output: { present: Mock } = { present: vi.fn(), }; const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository, mockProtestRepo as unknown as IProtestRepository, mockRaceRepo as unknown as IRaceRepository, mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository, mockLogger as unknown as Logger); mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' }); const membership = { driverId: { toString: () => 'steward1' }, role: { toString: () => 'owner' }, status: { toString: () => 'active' }, }; mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([membership]); mockProtestRepo.findById.mockResolvedValue(null); const result = await useCase.execute({ raceId: 'race1', driverId: 'driver1', stewardId: 'steward1', type: 'time_penalty', value: 5, reason: 'Test penalty', protestId: 'protest1', }); expect(result.isOk()).toBe(false); expect(result.error!.code).toBe('PROTEST_NOT_FOUND'); }); it('should return error when protest is not upheld', async () => { const output: { present: Mock } = { present: vi.fn(), }; const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository, mockProtestRepo as unknown as IProtestRepository, mockRaceRepo as unknown as IRaceRepository, mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository, mockLogger as unknown as Logger); mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' }); const membership = { driverId: { toString: () => 'steward1' }, role: { toString: () => 'owner' }, status: { toString: () => 'active' }, }; mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([membership]); mockProtestRepo.findById.mockResolvedValue({ id: 'protest1', status: 'pending', raceId: 'race1' }); const result = await useCase.execute({ raceId: 'race1', driverId: 'driver1', stewardId: 'steward1', type: 'time_penalty', value: 5, reason: 'Test penalty', protestId: 'protest1', }); expect(result.isOk()).toBe(false); expect(result.error!.code).toBe('PROTEST_NOT_UPHELD'); }); it('should return error when protest is not for this race', async () => { const output: { present: Mock } = { present: vi.fn(), }; const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository, mockProtestRepo as unknown as IProtestRepository, mockRaceRepo as unknown as IRaceRepository, mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository, mockLogger as unknown as Logger); mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' }); const membership = { driverId: { toString: () => 'steward1' }, role: { toString: () => 'owner' }, status: { toString: () => 'active' }, }; mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([membership]); mockProtestRepo.findById.mockResolvedValue({ id: 'protest1', status: 'upheld', raceId: 'race2' }); const result = await useCase.execute({ raceId: 'race1', driverId: 'driver1', stewardId: 'steward1', type: 'time_penalty', value: 5, reason: 'Test penalty', protestId: 'protest1', }); expect(result.isOk()).toBe(false); expect(result.error!.code).toBe('PROTEST_NOT_FOR_RACE'); }); it('should create penalty and return result on success', async () => { const output: { present: Mock } = { present: vi.fn(), }; const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository, mockProtestRepo as unknown as IProtestRepository, mockRaceRepo as unknown as IRaceRepository, mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository, mockLogger as unknown as Logger); mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' }); const membership = { driverId: { toString: () => 'steward1' }, role: { toString: () => 'admin' }, status: { toString: () => 'active' }, }; mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([membership]); mockPenaltyRepo.create.mockResolvedValue(undefined); const result = await useCase.execute({ raceId: 'race1', driverId: 'driver1', stewardId: 'steward1', type: 'time_penalty', value: 5, reason: 'Test penalty', notes: 'Test notes', }); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeUndefined(); const presented = (expect(presented).toEqual({ penaltyId: expect.any(String) }); expect(mockPenaltyRepo.create).toHaveBeenCalledTimes(1); const createdPenalty = (mockPenaltyRepo.create as Mock).mock.calls[0]?.[0] as unknown as { leagueId: unknown; raceId: unknown; driverId: unknown; type: string; value?: number; reason: string; issuedBy: unknown; status: unknown; notes?: string; }; type ToStringable = { toString(): string }; const asString = (value: unknown): string => { if (typeof value === 'string') return value; if ( value && typeof value === 'object' && 'toString' in value && typeof (value as ToStringable).toString === 'function' ) { return (value as ToStringable).toString(); } return String(value); }; expect(asString(createdPenalty.leagueId)).toBe('league1'); expect(asString(createdPenalty.raceId)).toBe('race1'); expect(asString(createdPenalty.driverId)).toBe('driver1'); expect(createdPenalty.type).toBe('time_penalty'); expect(createdPenalty.value).toBe(5); expect(createdPenalty.reason).toBe('Test penalty'); expect(asString(createdPenalty.issuedBy)).toBe('steward1'); expect(asString(createdPenalty.status)).toBe('pending'); expect(createdPenalty.notes).toBe('Test notes'); }); });