Files
gridpilot.gg/core/racing/application/use-cases/RecalculateChampionshipStandingsUseCase.test.ts
2026-01-16 19:46:49 +01:00

196 lines
7.6 KiB
TypeScript

import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import type { Penalty } from '../../domain/entities/Penalty';
import type { ChampionshipStandingRepository } from '../../domain/repositories/ChampionshipStandingRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { LeagueScoringConfigRepository } from '../../domain/repositories/LeagueScoringConfigRepository';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import type { ChampionshipAggregator } from '../../domain/services/ChampionshipAggregator';
import type { EventScoringService } from '../../domain/services/EventScoringService';
import {
RecalculateChampionshipStandingsUseCase,
type RecalculateChampionshipStandingsErrorCode,
type RecalculateChampionshipStandingsInput
} from './RecalculateChampionshipStandingsUseCase';
describe('RecalculateChampionshipStandingsUseCase', () => {
let useCase: RecalculateChampionshipStandingsUseCase;
let leagueRepository: { findById: Mock };
let seasonRepository: { findById: Mock };
let leagueScoringConfigRepository: { findBySeasonId: Mock };
let raceRepository: { findByLeagueId: Mock };
let resultRepository: { findByRaceId: Mock };
let penaltyRepository: { findByRaceId: Mock };
let championshipStandingRepository: { saveAll: Mock };
let eventScoringService: { scoreSession: Mock };
let championshipAggregator: { aggregate: Mock };
let logger: Logger;
beforeEach(() => {
leagueRepository = { findById: vi.fn() };
seasonRepository = { findById: vi.fn() };
leagueScoringConfigRepository = { findBySeasonId: vi.fn() };
raceRepository = { findByLeagueId: vi.fn() };
resultRepository = { findByRaceId: vi.fn() };
penaltyRepository = { findByRaceId: vi.fn() };
championshipStandingRepository = { saveAll: vi.fn() };
eventScoringService = { scoreSession: vi.fn() };
championshipAggregator = { aggregate: vi.fn() };
logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
} as unknown as Logger;
useCase = new RecalculateChampionshipStandingsUseCase(leagueRepository as unknown as LeagueRepository,
seasonRepository as unknown as SeasonRepository,
leagueScoringConfigRepository as unknown as LeagueScoringConfigRepository,
raceRepository as unknown as RaceRepository,
resultRepository as unknown as ResultRepository,
penaltyRepository as unknown as PenaltyRepository,
championshipStandingRepository as unknown as ChampionshipStandingRepository,
eventScoringService as unknown as EventScoringService,
championshipAggregator as unknown as ChampionshipAggregator,
logger);
});
it('returns league not found error', async () => {
leagueRepository.findById.mockResolvedValue(null);
const input: RecalculateChampionshipStandingsInput = {
leagueId: 'league-1',
seasonId: 'season-1',
};
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr() as ApplicationErrorCode<
RecalculateChampionshipStandingsErrorCode,
{ message: string }
>;
expect(error.code).toBe('LEAGUE_NOT_FOUND');
expect(error.details.message).toContain('league-1');
});
it('returns season not found error when season does not exist', async () => {
leagueRepository.findById.mockResolvedValue({ id: 'league-1' });
seasonRepository.findById.mockResolvedValue(null);
const input: RecalculateChampionshipStandingsInput = {
leagueId: 'league-1',
seasonId: 'season-1',
};
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr() as ApplicationErrorCode<
RecalculateChampionshipStandingsErrorCode,
{ message: string }
>;
expect(error.code).toBe('SEASON_NOT_FOUND');
});
it('returns season not found error when season belongs to different league', async () => {
leagueRepository.findById.mockResolvedValue({ id: 'league-1' });
seasonRepository.findById.mockResolvedValue({ id: 'season-1', leagueId: 'other-league' });
const input: RecalculateChampionshipStandingsInput = {
leagueId: 'league-1',
seasonId: 'season-1',
};
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr() as ApplicationErrorCode<
RecalculateChampionshipStandingsErrorCode,
{ message: string }
>;
expect(error.code).toBe('SEASON_NOT_FOUND');
});
it('recalculates standings successfully and returns result', async () => {
const league = { id: 'league-1' };
const season = { id: 'season-1', leagueId: 'league-1' };
const championship = {
id: 'champ-1',
name: 'Champ 1',
sessionTypes: ['main'],
pointsTableBySessionType: {},
dropScorePolicy: {},
};
const leagueScoringConfig = { championships: [championship] };
const races = [{ id: 'race-1', sessionType: 'race' as const }];
const results: unknown[] = [];
const penalties: Penalty[] = [];
const standings = [
{
participant: { id: 'driver-1' },
position: { toNumber: () => 1 },
totalPoints: { toNumber: () => 25 },
resultsCounted: { toNumber: () => 1 },
resultsDropped: { toNumber: () => 0 },
},
];
leagueRepository.findById.mockResolvedValue(league);
seasonRepository.findById.mockResolvedValue(season);
leagueScoringConfigRepository.findBySeasonId.mockResolvedValue(leagueScoringConfig);
raceRepository.findByLeagueId.mockResolvedValue(races);
resultRepository.findByRaceId.mockResolvedValue(results);
penaltyRepository.findByRaceId.mockResolvedValue(penalties);
eventScoringService.scoreSession.mockReturnValue({});
championshipAggregator.aggregate.mockReturnValue(standings);
championshipStandingRepository.saveAll.mockResolvedValue(undefined);
const input: RecalculateChampionshipStandingsInput = {
leagueId: 'league-1',
seasonId: 'season-1',
};
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
const presented = result.unwrap();
expect(presented.leagueId).toBe('league-1');
expect(presented.seasonId).toBe('season-1');
expect(presented.entries).toHaveLength(1);
expect(presented.entries[0]).toEqual({
driverId: 'driver-1',
teamId: null,
position: 1,
points: 25,
});
});
it('wraps repository failures in REPOSITORY_ERROR', async () => {
leagueRepository.findById.mockResolvedValue({ id: 'league-1' });
seasonRepository.findById.mockResolvedValue({ id: 'season-1', leagueId: 'league-1' });
leagueScoringConfigRepository.findBySeasonId.mockImplementation(() => {
throw new Error('boom');
});
const input: RecalculateChampionshipStandingsInput = {
leagueId: 'league-1',
seasonId: 'season-1',
};
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr() as ApplicationErrorCode<
RecalculateChampionshipStandingsErrorCode,
{ message: string }
>;
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details.message).toContain('boom');
});
});