import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; import type { IPenaltyRepository } from '../../domain/repositories/IPenaltyRepository'; import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import type { IStandingRepository } from '../../domain/repositories/IStandingRepository'; import type { DriverRatingPort } from '../ports/DriverRatingPort'; import { GetLeagueDriverSeasonStatsUseCase, type GetLeagueDriverSeasonStatsErrorCode, type GetLeagueDriverSeasonStatsInput, type GetLeagueDriverSeasonStatsResult, } from './GetLeagueDriverSeasonStatsUseCase'; describe('GetLeagueDriverSeasonStatsUseCase', () => { const mockStandingFindByLeagueId = vi.fn(); const mockResultFindByDriverIdAndLeagueId = vi.fn(); const mockPenaltyFindByRaceId = vi.fn(); const mockRaceFindByLeagueId = vi.fn(); const mockDriverRatingGetRating = vi.fn(); const mockDriverFindById = vi.fn(); const mockTeamFindById = vi.fn(); let useCase: GetLeagueDriverSeasonStatsUseCase; let standingRepository: IStandingRepository; let resultRepository: IResultRepository; let penaltyRepository: IPenaltyRepository; let raceRepository: IRaceRepository; let driverRepository: IDriverRepository; let driverRatingPort: DriverRatingPort; beforeEach(() => { mockStandingFindByLeagueId.mockReset(); mockResultFindByDriverIdAndLeagueId.mockReset(); mockPenaltyFindByRaceId.mockReset(); mockRaceFindByLeagueId.mockReset(); mockDriverRatingGetRating.mockReset(); mockDriverFindById.mockReset(); mockTeamFindById.mockReset(); standingRepository = { findByLeagueId: mockStandingFindByLeagueId, findByDriverIdAndLeagueId: vi.fn(), findAll: vi.fn(), save: vi.fn(), saveMany: vi.fn(), delete: vi.fn(), deleteByLeagueId: vi.fn(), exists: vi.fn(), recalculate: vi.fn(), }; resultRepository = { findById: vi.fn(), findAll: vi.fn(), findByRaceId: vi.fn(), findByDriverId: vi.fn(), findByDriverIdAndLeagueId: mockResultFindByDriverIdAndLeagueId, create: vi.fn(), createMany: vi.fn(), update: vi.fn(), delete: vi.fn(), deleteByRaceId: vi.fn(), exists: vi.fn(), existsByRaceId: vi.fn(), }; penaltyRepository = { findById: vi.fn(), findByDriverId: vi.fn(), findByProtestId: vi.fn(), findPending: vi.fn(), findByRaceId: mockPenaltyFindByRaceId, findIssuedBy: vi.fn(), create: vi.fn(), update: vi.fn(), exists: vi.fn(), }; raceRepository = { findById: vi.fn(), findAll: vi.fn(), findByLeagueId: mockRaceFindByLeagueId, findUpcomingByLeagueId: vi.fn(), findCompletedByLeagueId: vi.fn(), findByStatus: vi.fn(), findByDateRange: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn(), exists: vi.fn(), }; driverRepository = { findById: mockDriverFindById, findByIRacingId: vi.fn(), findAll: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn(), exists: vi.fn(), existsByIRacingId: vi.fn(), }; driverRatingPort = { getDriverRating: mockDriverRatingGetRating, calculateRatingChange: vi.fn(), updateDriverRating: vi.fn(), }; }; useCase = new GetLeagueDriverSeasonStatsUseCase(standingRepository, resultRepository, penaltyRepository, raceRepository, driverRepository, driverRatingPort); }); it('should return league driver season stats for given league id', async () => { const input: GetLeagueDriverSeasonStatsInput = { leagueId: 'league-1' }; const mockStandings = [ { driverId: { toString: () => 'driver-1' }, position: { toNumber: () => 1 }, points: { toNumber: () => 100 }, }, { driverId: { toString: () => 'driver-2' }, position: { toNumber: () => 2 }, points: { toNumber: () => 80 }, }, ]; const mockRaces = [ { id: 'race-1' }, { id: 'race-2' }, { id: 'race-3' }, { id: 'race-4' }, { id: 'race-5' }, ]; const mockPenalties = [ { driverId: 'driver-1', status: 'applied', type: 'points_deduction', value: 10 }, ]; const mockDriver1Results = [ { position: { toNumber: () => 1 } }, { position: { toNumber: () => 1 } }, { position: { toNumber: () => 1 } }, { position: { toNumber: () => 1 } }, { position: { toNumber: () => 1 } }, ]; const mockDriver2Results = [ { position: { toNumber: () => 2 } }, { position: { toNumber: () => 2 } }, { position: { toNumber: () => 2 } }, { position: { toNumber: () => 2 } }, { position: { toNumber: () => 2 } }, ]; mockStandingFindByLeagueId.mockResolvedValue(mockStandings); mockRaceFindByLeagueId.mockResolvedValue(mockRaces); mockPenaltyFindByRaceId.mockImplementation((raceId: string) => { if (raceId === 'race-1') return Promise.resolve(mockPenalties); return Promise.resolve([]); }); mockDriverRatingGetRating.mockImplementation((driverId: string) => { if (driverId === 'driver-1') return Promise.resolve(1500); if (driverId === 'driver-2') return Promise.resolve(1400); return Promise.resolve(null); }); mockResultFindByDriverIdAndLeagueId.mockImplementation((driverId: string) => { if (driverId === 'driver-1') return Promise.resolve(mockDriver1Results); if (driverId === 'driver-2') return Promise.resolve(mockDriver2Results); return Promise.resolve([]); }); mockDriverFindById.mockImplementation((id: string) => { if (id === 'driver-1') return Promise.resolve({ id: 'driver-1', name: { toString: () => 'Driver One' } }); if (id === 'driver-2') return Promise.resolve({ id: 'driver-2', name: { toString: () => 'Driver Two' } }); return Promise.resolve(null); }); const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeUndefined(); const presented = expect(presented.leagueId).toBe('league-1'); expect(presented.stats).toHaveLength(2); expect(presented.stats[0]).toEqual({ leagueId: 'league-1', driverId: 'driver-1', position: 1, driverName: 'Driver One', teamId: undefined, teamName: undefined, totalPoints: 100, basePoints: 110, penaltyPoints: -10, bonusPoints: 0, pointsPerRace: 20, racesStarted: 5, racesFinished: 5, dnfs: 0, noShows: 0, avgFinish: 1, rating: 1500, ratingChange: null, }); }); it('should handle no penalties', async () => { const input: GetLeagueDriverSeasonStatsInput = { leagueId: 'league-1' }; const mockStandings = [ { driverId: { toString: () => 'driver-1' }, position: { toNumber: () => 1 }, points: { toNumber: () => 100 }, }, ]; const mockRaces = [{ id: 'race-1' }]; const mockResults = [{ position: { toNumber: () => 1 } }]; mockStandingFindByLeagueId.mockResolvedValue(mockStandings); mockRaceFindByLeagueId.mockResolvedValue(mockRaces); mockPenaltyFindByRaceId.mockResolvedValue([]); mockDriverRatingGetRating.mockResolvedValue(null); mockResultFindByDriverIdAndLeagueId.mockResolvedValue(mockResults); mockDriverFindById.mockResolvedValue({ id: 'driver-1', name: { toString: () => 'Driver One' } }); const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeUndefined(); const presented = expect(presented?.stats[0]?.penaltyPoints).toBe(0); }); it('should return LEAGUE_NOT_FOUND when no standings are found', async () => { const input: GetLeagueDriverSeasonStatsInput = { leagueId: 'missing-league' }; mockStandingFindByLeagueId.mockResolvedValue([]); mockRaceFindByLeagueId.mockResolvedValue([]); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< GetLeagueDriverSeasonStatsErrorCode, { message: string } >; expect(err.code).toBe('LEAGUE_NOT_FOUND'); expect(err.details.message).toBe('League not found'); }); it('should return REPOSITORY_ERROR when an unexpected error occurs', async () => { const input: GetLeagueDriverSeasonStatsInput = { leagueId: 'league-1' }; const thrown = new Error('repository failure'); mockStandingFindByLeagueId.mockRejectedValue(thrown); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< GetLeagueDriverSeasonStatsErrorCode, { message: string } >; expect(err.code).toBe('REPOSITORY_ERROR'); expect(err.details.message).toBe('repository failure'); }); });