import { CreateSeasonForLeagueUseCase, type CreateSeasonForLeagueInput, type LeagueConfigFormModel, } from '@core/racing/application/use-cases/CreateSeasonForLeagueUseCase'; import { Season } from '@core/racing/domain/entities/season/Season'; import type { LeagueRepository } from '@core/racing/domain/repositories/LeagueRepository'; import type { SeasonRepository } from '@core/racing/domain/repositories/SeasonRepository'; import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest'; function createLeagueConfigFormModel(overrides?: Partial): LeagueConfigFormModel { return { basics: { name: 'Test League', visibility: 'ranked', gameId: 'iracing', ...overrides?.basics, }, structure: { mode: 'solo', maxDrivers: 30, ...overrides?.structure, }, championships: { enableDriverChampionship: true, enableTeamChampionship: false, enableNationsChampionship: false, enableTrophyChampionship: false, ...overrides?.championships, }, scoring: { patternId: 'sprint-main-driver', customScoringEnabled: false, ...overrides?.scoring, }, dropPolicy: { strategy: 'bestNResults', n: 3, ...overrides?.dropPolicy, }, timings: { qualifyingMinutes: 10, mainRaceMinutes: 30, sessionCount: 8, seasonStartDate: '2025-01-01', raceStartTime: '20:00', timezoneId: 'UTC', recurrenceStrategy: 'weekly', weekdays: ['Mon'], ...overrides?.timings, }, stewarding: { decisionMode: 'steward_vote', requiredVotes: 3, requireDefense: true, defenseTimeLimit: 24, voteTimeLimit: 24, protestDeadlineHours: 48, stewardingClosesHours: 72, notifyAccusedOnProtest: true, notifyOnVoteRequired: true, ...overrides?.stewarding, }, ...overrides, }; } describe('CreateSeasonForLeagueUseCase', () => { const mockLeagueFindById = vi.fn(); const mockLeagueRepo: { findById: Mock; findAll: Mock; findByOwnerId: Mock; create: Mock; update: Mock; delete: Mock; exists: Mock; searchByName: Mock; } = { findById: mockLeagueFindById, findAll: vi.fn(), findByOwnerId: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn(), exists: vi.fn(), searchByName: vi.fn(), }; const mockSeasonFindById = vi.fn(); const mockSeasonAdd = vi.fn(); const mockSeasonRepo: { findById: Mock; findByLeagueId: Mock; create: Mock; add: Mock; update: Mock; listByLeague: Mock; listActiveByLeague: Mock; } = { findById: mockSeasonFindById, findByLeagueId: vi.fn(), create: vi.fn(), add: mockSeasonAdd, update: vi.fn(), listByLeague: vi.fn(), listActiveByLeague: vi.fn(), }; beforeEach(() => { vi.clearAllMocks(); }); it('creates a planned Season for an existing league with config-derived props', async () => { mockLeagueFindById.mockResolvedValue({ id: 'league-1' }); mockSeasonAdd.mockResolvedValue(undefined); const useCase = new CreateSeasonForLeagueUseCase( mockLeagueRepo as unknown as LeagueRepository, mockSeasonRepo as unknown as SeasonRepository ); const config = createLeagueConfigFormModel({ basics: { name: 'League With Config', visibility: 'ranked', gameId: 'iracing', }, scoring: { patternId: 'club-default', customScoringEnabled: true, }, dropPolicy: { strategy: 'dropWorstN', n: 2, }, timings: { qualifyingMinutes: 10, mainRaceMinutes: 30, sessionCount: 8, }, }); const command: CreateSeasonForLeagueInput = { leagueId: 'league-1', name: 'Season from Config', gameId: 'iracing', config, }; const result = await useCase.execute(command); expect(result.isOk()).toBe(true); const presented = result.unwrap(); expect(presented.season).toBeInstanceOf(Season); expect(presented.league.id).toBe('league-1'); }); it('clones configuration from a source season when sourceSeasonId is provided', async () => { const sourceSeason = Season.create({ id: 'source-season', leagueId: 'league-1', gameId: 'iracing', name: 'Source Season', status: 'planned', }).withMaxDrivers(40); mockLeagueFindById.mockResolvedValue({ id: 'league-1' }); mockSeasonFindById.mockResolvedValue(sourceSeason); mockSeasonAdd.mockResolvedValue(undefined); const useCase = new CreateSeasonForLeagueUseCase( mockLeagueRepo as unknown as LeagueRepository, mockSeasonRepo as unknown as SeasonRepository ); const command: CreateSeasonForLeagueInput = { leagueId: 'league-1', name: 'Cloned Season', gameId: 'iracing', sourceSeasonId: 'source-season', }; const result = await useCase.execute(command); expect(result.isOk()).toBe(true); const presented = result.unwrap(); expect(presented.season.maxDrivers).toBe(40); }); it('returns error when league not found', async () => { mockLeagueFindById.mockResolvedValue(null); const useCase = new CreateSeasonForLeagueUseCase( mockLeagueRepo as unknown as LeagueRepository, mockSeasonRepo as unknown as SeasonRepository ); const command: CreateSeasonForLeagueInput = { leagueId: 'missing-league', name: 'Any', gameId: 'iracing', }; const result = await useCase.execute(command); expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('LEAGUE_NOT_FOUND'); expect(error.details?.message).toBe('League not found: missing-league'); }); it('returns validation error when source season is missing', async () => { mockLeagueFindById.mockResolvedValue({ id: 'league-1' }); mockSeasonFindById.mockResolvedValue(undefined); const useCase = new CreateSeasonForLeagueUseCase( mockLeagueRepo as unknown as LeagueRepository, mockSeasonRepo as unknown as SeasonRepository ); const command: CreateSeasonForLeagueInput = { leagueId: 'league-1', name: 'Cloned Season', gameId: 'iracing', sourceSeasonId: 'missing-source', }; const result = await useCase.execute(command); expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('VALIDATION_ERROR'); expect(error.details?.message).toBe('Source Season not found: missing-source'); }); });