import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; import { ApplyPenaltyUseCase } from './ApplyPenaltyUseCase'; 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 useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any, mockProtestRepo as any, mockRaceRepo as any, mockLeagueMembershipRepo as any, mockLogger as any); 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.unwrapErr().code).toBe('RACE_NOT_FOUND'); }); it('should return error when steward does not have authority', async () => { const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any, mockProtestRepo as any, mockRaceRepo as any, mockLeagueMembershipRepo as any, mockLogger as any); 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.unwrapErr().code).toBe('INSUFFICIENT_AUTHORITY'); }); it('should return error when protest does not exist', async () => { const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any, mockProtestRepo as any, mockRaceRepo as any, mockLeagueMembershipRepo as any, mockLogger as any); 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.unwrapErr().code).toBe('PROTEST_NOT_FOUND'); }); it('should return error when protest is not upheld', async () => { const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any, mockProtestRepo as any, mockRaceRepo as any, mockLeagueMembershipRepo as any, mockLogger as any); 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.unwrapErr().code).toBe('PROTEST_NOT_UPHELD'); }); it('should return error when protest is not for this race', async () => { const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any, mockProtestRepo as any, mockRaceRepo as any, mockLeagueMembershipRepo as any, mockLogger as any); 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.unwrapErr().code).toBe('PROTEST_NOT_FOR_RACE'); }); it('should create penalty and return result on success', async () => { const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any, mockProtestRepo as any, mockRaceRepo as any, mockLeagueMembershipRepo as any, mockLogger as any); 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); const presented = result.unwrap(); expect(presented.penaltyId).toBeDefined(); expect(mockPenaltyRepo.create).toHaveBeenCalledTimes(1); const createdPenalty = (mockPenaltyRepo.create as Mock).mock.calls[0]?.[0] as any; 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'); }); });