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