diff --git a/apps/api/src/domain/dashboard/DashboardProviders.ts b/apps/api/src/domain/dashboard/DashboardProviders.ts index e65cc90ff..569d6faca 100644 --- a/apps/api/src/domain/dashboard/DashboardProviders.ts +++ b/apps/api/src/domain/dashboard/DashboardProviders.ts @@ -139,6 +139,7 @@ export const DashboardProviders: Provider[] = [ feedRepo: IFeedRepository, socialRepo: ISocialGraphRepository, imageService: ImageServicePort, + output: DashboardOverviewPresenter, ) => new DashboardOverviewUseCase( driverRepo, @@ -152,6 +153,7 @@ export const DashboardProviders: Provider[] = [ socialRepo, async (driverId: string) => imageService.getDriverAvatar(driverId), () => null, + output, ), inject: [ DRIVER_REPOSITORY_TOKEN, @@ -164,6 +166,7 @@ export const DashboardProviders: Provider[] = [ FEED_REPOSITORY_TOKEN, SOCIAL_GRAPH_REPOSITORY_TOKEN, IMAGE_SERVICE_TOKEN, + DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN, ], }, ]; \ No newline at end of file diff --git a/apps/api/src/domain/dashboard/DashboardService.ts b/apps/api/src/domain/dashboard/DashboardService.ts index a8f07175e..b20b412dc 100644 --- a/apps/api/src/domain/dashboard/DashboardService.ts +++ b/apps/api/src/domain/dashboard/DashboardService.ts @@ -20,7 +20,13 @@ export class DashboardService { async getDashboardOverview(driverId: string): Promise { this.logger.debug('[DashboardService] Getting dashboard overview:', { driverId }); - await this.dashboardOverviewUseCase.execute({ driverId }); + const result = await this.dashboardOverviewUseCase.execute({ driverId }); + + if (result.isErr()) { + const error = result.error; + const message = error?.details?.message || 'Unknown error'; + throw new Error(`Failed to get dashboard overview: ${message}`); + } return this.presenter.getResponseModel(); } diff --git a/apps/api/src/domain/dashboard/presenters/DashboardOverviewPresenter.test.ts b/apps/api/src/domain/dashboard/presenters/DashboardOverviewPresenter.test.ts index 1c95cc4e3..0e910eb77 100644 --- a/apps/api/src/domain/dashboard/presenters/DashboardOverviewPresenter.test.ts +++ b/apps/api/src/domain/dashboard/presenters/DashboardOverviewPresenter.test.ts @@ -144,7 +144,7 @@ describe('DashboardOverviewPresenter', () => { it('maps DashboardOverviewResult to DashboardOverviewDTO correctly', () => { const output = createOutput(); - presenter.present(Result.ok(output)); + presenter.present(output); const dto = presenter.getResponseModel(); expect(dto.activeLeaguesCount).toBe(2); diff --git a/apps/api/src/domain/dashboard/presenters/DashboardOverviewPresenter.ts b/apps/api/src/domain/dashboard/presenters/DashboardOverviewPresenter.ts index f5f481d9e..7ce8e568c 100644 --- a/apps/api/src/domain/dashboard/presenters/DashboardOverviewPresenter.ts +++ b/apps/api/src/domain/dashboard/presenters/DashboardOverviewPresenter.ts @@ -1,4 +1,4 @@ -import { Result } from '@core/shared/application/Result'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import type { DashboardOverviewResult, } from '@core/racing/application/use-cases/DashboardOverviewUseCase'; @@ -13,12 +13,10 @@ import { DashboardFriendSummaryDTO, } from '../dtos/DashboardOverviewDTO'; -export class DashboardOverviewPresenter { +export class DashboardOverviewPresenter implements UseCaseOutputPort { private responseModel: DashboardOverviewDTO | null = null; - present(result: Result): void { - const data = result.unwrap(); - + present(data: DashboardOverviewResult): void { const currentDriver: DashboardDriverSummaryDTO | null = data.currentDriver ? { id: data.currentDriver.driver.id, diff --git a/core/racing/application/use-cases/DashboardOverviewUseCase.test.ts b/core/racing/application/use-cases/DashboardOverviewUseCase.test.ts index 4c7b6df72..d65f2b9b2 100644 --- a/core/racing/application/use-cases/DashboardOverviewUseCase.test.ts +++ b/core/racing/application/use-cases/DashboardOverviewUseCase.test.ts @@ -15,6 +15,7 @@ import { Result as RaceResult } from '@core/racing/domain/entities/result/Result import type { FeedItem } from '@core/social/domain/types/FeedItem'; import { Result as UseCaseResult } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; describe('DashboardOverviewUseCase', () => { it('partitions upcoming races into myUpcomingRaces and otherUpcomingRaces and selects nextRace from myUpcomingRaces', async () => { @@ -226,6 +227,7 @@ describe('DashboardOverviewUseCase', () => { clearRaceRegistrations: async (): Promise => { throw new Error('Not implemented'); }, + findByRaceId: async (): Promise => [], }; const feedRepository = { @@ -253,6 +255,14 @@ describe('DashboardOverviewUseCase', () => { } : null; + // Mock output port to capture presented data + let _presentedData: DashboardOverviewResult | null = null; + const outputPort: UseCaseOutputPort = { + present: (data: DashboardOverviewResult) => { + _presentedData = data; + }, + }; + const useCase = new DashboardOverviewUseCase( driverRepository, raceRepository, @@ -265,21 +275,23 @@ describe('DashboardOverviewUseCase', () => { socialRepository, getDriverAvatar, getDriverStats, + outputPort, ); const input: DashboardOverviewInput = { driverId }; const result: UseCaseResult< - DashboardOverviewResult, + void, ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }> > = await useCase.execute(input); expect(result.isOk()).toBe(true); - const vm = result.unwrap(); + expect(_presentedData).not.toBeNull(); + const vm = _presentedData!; - expect(vm.myUpcomingRaces.map(r => r.race.id)).toEqual(['race-1', 'race-3']); + expect(vm.myUpcomingRaces.map((r: any) => r.race.id)).toEqual(['race-1', 'race-3']); - expect(vm.otherUpcomingRaces.map(r => r.race.id)).toEqual(['race-2', 'race-4']); + expect(vm.otherUpcomingRaces.map((r: any) => r.race.id)).toEqual(['race-2', 'race-4']); expect(vm.nextRace).not.toBeNull(); expect(vm.nextRace!.race.id).toBe('race-1'); @@ -465,7 +477,7 @@ describe('DashboardOverviewUseCase', () => { getMembership: async (leagueId: string, driverIdParam: string): Promise => { return ( memberships.find( - m => m.leagueId === leagueId && m.driverId === driverIdParam, + m => m.leagueId.toString() === leagueId && m.driverId.toString() === driverIdParam, ) ?? null ); }, @@ -499,6 +511,7 @@ describe('DashboardOverviewUseCase', () => { clearRaceRegistrations: async (): Promise => { throw new Error('Not implemented'); }, + findByRaceId: async (): Promise => [], }; const feedRepository = { @@ -526,6 +539,14 @@ describe('DashboardOverviewUseCase', () => { } : null; + // Mock output port to capture presented data + let _presentedData: DashboardOverviewResult | null = null; + const outputPort: UseCaseOutputPort = { + present: (data: DashboardOverviewResult) => { + _presentedData = data; + }, + }; + const useCase = new DashboardOverviewUseCase( driverRepository, raceRepository, @@ -538,37 +559,39 @@ describe('DashboardOverviewUseCase', () => { socialRepository, getDriverAvatar, getDriverStats, + outputPort, ); const input: DashboardOverviewInput = { driverId }; const result: UseCaseResult< - DashboardOverviewResult, + void, ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }> > = await useCase.execute(input); expect(result.isOk()).toBe(true); - const vm = result.unwrap(); + expect(_presentedData).not.toBeNull(); + const vm = _presentedData!; expect(vm.recentResults.length).toBe(2); expect(vm.recentResults[0]!.race.id).toBe('race-new'); expect(vm.recentResults[1]!.race.id).toBe('race-old'); const summariesByLeague = new Map( - vm.leagueStandingsSummaries.map(s => [s.league.id, s]), + vm.leagueStandingsSummaries.map((s: any) => [s.league.id.toString(), s]), ); const summaryA = summariesByLeague.get('league-A'); const summaryB = summariesByLeague.get('league-B'); expect(summaryA).toBeDefined(); - expect(summaryA!.standing?.position).toBe(3); - expect(summaryA!.standing?.points).toBe(50); + expect(summaryA!.standing?.position.toNumber()).toBe(3); + expect(summaryA!.standing?.points.toNumber()).toBe(50); expect(summaryA!.totalDrivers).toBe(2); expect(summaryB).toBeDefined(); - expect(summaryB!.standing?.position).toBe(1); - expect(summaryB!.standing?.points).toBe(100); + expect(summaryB!.standing?.position.toNumber()).toBe(1); + expect(summaryB!.standing?.points.toNumber()).toBe(100); expect(summaryB!.totalDrivers).toBe(2); }); @@ -708,6 +731,7 @@ describe('DashboardOverviewUseCase', () => { clearRaceRegistrations: async (): Promise => { throw new Error('Not implemented'); }, + findByRaceId: async (): Promise => [], }; const feedRepository = { @@ -725,6 +749,14 @@ describe('DashboardOverviewUseCase', () => { const getDriverStats = () => null; + // Mock output port to capture presented data + let _presentedData: DashboardOverviewResult | null = null; + const outputPort: UseCaseOutputPort = { + present: (data: DashboardOverviewResult) => { + _presentedData = data; + }, + }; + const useCase = new DashboardOverviewUseCase( driverRepository, raceRepository, @@ -737,17 +769,19 @@ describe('DashboardOverviewUseCase', () => { socialRepository, getDriverAvatar, getDriverStats, + outputPort, ); const input: DashboardOverviewInput = { driverId }; const result: UseCaseResult< - DashboardOverviewResult, + void, ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }> > = await useCase.execute(input); expect(result.isOk()).toBe(true); - const vm = result.unwrap(); + expect(_presentedData).not.toBeNull(); + const vm = _presentedData!; expect(vm.myUpcomingRaces).toEqual([]); expect(vm.otherUpcomingRaces).toEqual([]); @@ -893,6 +927,7 @@ describe('DashboardOverviewUseCase', () => { clearRaceRegistrations: async (): Promise => { throw new Error('Not implemented'); }, + findByRaceId: async (): Promise => [], }; const feedRepository = { @@ -910,6 +945,13 @@ describe('DashboardOverviewUseCase', () => { const getDriverStats = () => null; + // Mock output port to capture presented data + const outputPort: UseCaseOutputPort = { + present: (_data: DashboardOverviewResult) => { + // No-op + }, + }; + const useCase = new DashboardOverviewUseCase( driverRepository, raceRepository, @@ -922,12 +964,13 @@ describe('DashboardOverviewUseCase', () => { socialRepository, getDriverAvatar, getDriverStats, + outputPort, ); const input: DashboardOverviewInput = { driverId }; const result: UseCaseResult< - DashboardOverviewResult, + void, ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }> > = await useCase.execute(input); @@ -1075,6 +1118,7 @@ describe('DashboardOverviewUseCase', () => { clearRaceRegistrations: async (): Promise => { throw new Error('Not implemented'); }, + findByRaceId: async (): Promise => [], }; const feedRepository = { @@ -1092,6 +1136,13 @@ describe('DashboardOverviewUseCase', () => { const getDriverStats = () => null; + // Mock output port to capture presented data + const outputPort: UseCaseOutputPort = { + present: (_data: DashboardOverviewResult) => { + // No-op + }, + }; + const useCase = new DashboardOverviewUseCase( driverRepository, raceRepository, @@ -1104,12 +1155,13 @@ describe('DashboardOverviewUseCase', () => { socialRepository, getDriverAvatar, getDriverStats, + outputPort, ); const input: DashboardOverviewInput = { driverId }; const result: UseCaseResult< - DashboardOverviewResult, + void, ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }> > = await useCase.execute(input); diff --git a/core/racing/application/use-cases/DashboardOverviewUseCase.ts b/core/racing/application/use-cases/DashboardOverviewUseCase.ts index 15d403851..1f33d4aab 100644 --- a/core/racing/application/use-cases/DashboardOverviewUseCase.ts +++ b/core/racing/application/use-cases/DashboardOverviewUseCase.ts @@ -1,5 +1,6 @@ import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository'; import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository'; import type { FeedItem } from '@core/social/domain/types/FeedItem'; @@ -14,6 +15,7 @@ import type { IRaceRegistrationRepository } from '../../domain/repositories/IRac import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import type { IStandingRepository } from '../../domain/repositories/IStandingRepository'; +import type { Result as RaceResult } from '../../domain/entities/result/Result'; export interface DashboardOverviewInput { driverId: string; @@ -48,7 +50,7 @@ export interface DashboardRaceSummary { export interface DashboardRecentRaceResultSummary { race: Race; league: League | null; - result: Result; + result: RaceResult; } export interface DashboardLeagueStandingSummary { @@ -95,13 +97,14 @@ export class DashboardOverviewUseCase { private readonly getDriverStats: ( driverId: string, ) => DashboardDriverStatsAdapter | null, + private readonly output: UseCaseOutputPort, ) {} async execute( input: DashboardOverviewInput, ): Promise< Result< - DashboardOverviewResult, + void, ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }> > > { @@ -206,7 +209,9 @@ export class DashboardOverviewUseCase { friends: friendsSummary, }; - return Result.ok(result); + this.output.present(result); + + return Result.ok(undefined); } catch (error) { return Result.err({ code: 'REPOSITORY_ERROR', diff --git a/core/racing/application/use-cases/FileProtestUseCase.test.ts b/core/racing/application/use-cases/FileProtestUseCase.test.ts index 4c1816e1b..6e2cf3656 100644 --- a/core/racing/application/use-cases/FileProtestUseCase.test.ts +++ b/core/racing/application/use-cases/FileProtestUseCase.test.ts @@ -146,7 +146,7 @@ describe('FileProtestUseCase', () => { ); expect(output.present).toHaveBeenCalledTimes(1); - const presented = (output.present as unknown as Mock).mock.calls[0][0] as FileProtestResult; + const presented = (output.present as unknown as Mock).mock.calls[0]?.[0] as FileProtestResult; expect(presented.protest.raceId).toBe('race1'); expect(presented.protest.protestingDriverId).toBe('driver1'); expect(presented.protest.accusedDriverId).toBe('driver2'); diff --git a/core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase.test.ts b/core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase.test.ts index 39d76b9a4..e0cf4656d 100644 --- a/core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase.test.ts +++ b/core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase.test.ts @@ -1,16 +1,15 @@ -import { describe, it, expect, beforeEach, vi, Mock } from 'vitest'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; +import type { IGameRepository } from '../../domain/repositories/IGameRepository'; +import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; +import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; +import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository'; +import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; import { GetAllLeaguesWithCapacityAndScoringUseCase, type GetAllLeaguesWithCapacityAndScoringInput, type GetAllLeaguesWithCapacityAndScoringResult, - type LeagueCapacityAndScoringSummary, } from './GetAllLeaguesWithCapacityAndScoringUseCase'; -import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; -import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; -import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; -import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository'; -import type { IGameRepository } from '../../domain/repositories/IGameRepository'; -import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; describe('GetAllLeaguesWithCapacityAndScoringUseCase', () => { let mockLeagueRepo: { findAll: Mock }; @@ -63,18 +62,18 @@ describe('GetAllLeaguesWithCapacityAndScoringUseCase', () => { expect(output.present).toHaveBeenCalledTimes(1); const presented = - output.present.mock.calls[0][0] as GetAllLeaguesWithCapacityAndScoringResult; + output.present.mock.calls[0]?.[0] as GetAllLeaguesWithCapacityAndScoringResult; - expect(presented.leagues).toHaveLength(1); + expect(presented?.leagues).toHaveLength(1); - const [summary] = presented.leagues as LeagueCapacityAndScoringSummary[]; + const [summary] = presented?.leagues ?? []; - expect(summary.league).toEqual(league); - expect(summary.currentDrivers).toBe(2); - expect(summary.maxDrivers).toBe(30); - expect(summary.season).toEqual(season); - expect(summary.scoringConfig).toEqual(scoringConfig); - expect(summary.game).toEqual(game); - expect(summary.preset).toEqual({ id: 'preset1', name: 'Default' }); + expect(summary?.league).toEqual(league); + expect(summary?.currentDrivers).toBe(2); + expect(summary?.maxDrivers).toBe(30); + expect(summary?.season).toEqual(season); + expect(summary?.scoringConfig).toEqual(scoringConfig); + expect(summary?.game).toEqual(game); + expect(summary?.preset).toEqual({ id: 'preset1', name: 'Default' }); }); }); diff --git a/core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase.ts b/core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase.ts index adb11b965..5216e1e55 100644 --- a/core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase.ts +++ b/core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase.ts @@ -7,7 +7,7 @@ import type { League } from '../../domain/entities/League'; import type { Season } from '../../domain/entities/season/Season'; import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig'; import type { Game } from '../../domain/entities/Game'; -import type { LeagueScoringPreset } from '../../../bootstrap/LeagueScoringPresets'; +import type { LeagueScoringPreset } from '../../domain/types/LeagueScoringPreset'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; @@ -62,18 +62,18 @@ export class GetAllLeaguesWithCapacityAndScoringUseCase { const enrichedLeagues: LeagueCapacityAndScoringSummary[] = []; for (const league of leagues) { - const members = await this.leagueMembershipRepository.getLeagueMembers(league.id); + const members = await this.leagueMembershipRepository.getLeagueMembers(league.id.toString()); const currentDrivers = members.filter( (m) => - m.status === 'active' && - (m.role === 'owner' || - m.role === 'admin' || - m.role === 'steward' || - m.role === 'member'), + m.status.toString() === 'active' && + (m.role.toString() === 'owner' || + m.role.toString() === 'admin' || + m.role.toString() === 'steward' || + m.role.toString() === 'member'), ).length; - const seasons = await this.seasonRepository.findByLeagueId(league.id); + const seasons = await this.seasonRepository.findByLeagueId(league.id.toString()); const activeSeason = seasons && seasons.length > 0 ? seasons.find((s) => s.status === 'active') ?? seasons[0] @@ -85,14 +85,14 @@ export class GetAllLeaguesWithCapacityAndScoringUseCase { if (activeSeason) { const scoringConfigResult = - await this.leagueScoringConfigRepository.findBySeasonId(activeSeason.id); + await this.leagueScoringConfigRepository.findBySeasonId(activeSeason.id.toString()); scoringConfig = scoringConfigResult ?? undefined; if (scoringConfig) { - const gameResult = await this.gameRepository.findById(activeSeason.gameId); + const gameResult = await this.gameRepository.findById(activeSeason.gameId.toString()); game = gameResult ?? undefined; const presetId = scoringConfig.scoringPresetId; if (presetId) { - preset = this.presetProvider.getPresetById(presetId); + preset = this.presetProvider.getPresetById(presetId.toString()); } } } diff --git a/core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.test.ts b/core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.test.ts index 07fabe377..5a9277990 100644 --- a/core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.test.ts +++ b/core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.test.ts @@ -1,13 +1,12 @@ -import { describe, it, expect, beforeEach, vi, Mock } from 'vitest'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; +import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; +import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import { GetAllLeaguesWithCapacityUseCase, type GetAllLeaguesWithCapacityInput, type GetAllLeaguesWithCapacityResult, - type LeagueCapacitySummary, } from './GetAllLeaguesWithCapacityUseCase'; -import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; -import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; -import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; describe('GetAllLeaguesWithCapacityUseCase', () => { let mockLeagueRepo: { findAll: Mock }; @@ -24,7 +23,6 @@ describe('GetAllLeaguesWithCapacityUseCase', () => { const useCase = new GetAllLeaguesWithCapacityUseCase( mockLeagueRepo as unknown as ILeagueRepository, mockMembershipRepo as unknown as ILeagueMembershipRepository, - output, ); const league1 = { id: 'league1', name: 'Test League 1', settings: { maxDrivers: 10 } }; @@ -48,27 +46,26 @@ describe('GetAllLeaguesWithCapacityUseCase', () => { expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeUndefined(); - expect(output.present).toHaveBeenCalledTimes(1); + const resultValue = result.unwrap(); + expect(resultValue).toBeDefined(); - const presented = output.present.mock.calls[0][0] as GetAllLeaguesWithCapacityResult; - expect(presented.leagues).toHaveLength(2); + expect(resultValue?.leagues).toHaveLength(2); - const [first, second] = presented.leagues as LeagueCapacitySummary[]; + const [first, second] = resultValue?.leagues ?? []; - expect(first.league).toEqual(league1); - expect(first.currentDrivers).toBe(2); - expect(first.maxDrivers).toBe(10); + expect(first?.league).toEqual(league1); + expect(first?.currentDrivers).toBe(2); + expect(first?.maxDrivers).toBe(10); - expect(second.league).toEqual(league2); - expect(second.currentDrivers).toBe(1); - expect(second.maxDrivers).toBe(20); + expect(second?.league).toEqual(league2); + expect(second?.currentDrivers).toBe(1); + expect(second?.maxDrivers).toBe(20); }); it('should return empty result when no leagues', async () => { const useCase = new GetAllLeaguesWithCapacityUseCase( mockLeagueRepo as unknown as ILeagueRepository, mockMembershipRepo as unknown as ILeagueMembershipRepository, - output, ); mockLeagueRepo.findAll.mockResolvedValue([]); @@ -78,8 +75,8 @@ describe('GetAllLeaguesWithCapacityUseCase', () => { expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeUndefined(); - expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetAllLeaguesWithCapacityResult; - expect(presented.leagues).toEqual([]); + const resultValue = result.unwrap(); + expect(resultValue).toBeDefined(); + expect(resultValue?.leagues).toEqual([]); }); }); \ No newline at end of file diff --git a/core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.ts b/core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.ts index 9bbddf689..229219e8a 100644 --- a/core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.ts +++ b/core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.ts @@ -3,7 +3,6 @@ import type { ILeagueMembershipRepository } from '../../domain/repositories/ILea import type { League } from '../../domain/entities/League'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; -import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; export type GetAllLeaguesWithCapacityInput = {}; @@ -27,7 +26,6 @@ export class GetAllLeaguesWithCapacityUseCase { constructor( private readonly leagueRepository: ILeagueRepository, private readonly leagueMembershipRepository: ILeagueMembershipRepository, - private readonly outputPort: UseCaseOutputPort, ) {} async execute( diff --git a/core/racing/application/use-cases/GetAllRacesPageDataUseCase.test.ts b/core/racing/application/use-cases/GetAllRacesPageDataUseCase.test.ts index b63b0a2e1..fde91a325 100644 --- a/core/racing/application/use-cases/GetAllRacesPageDataUseCase.test.ts +++ b/core/racing/application/use-cases/GetAllRacesPageDataUseCase.test.ts @@ -93,7 +93,7 @@ describe('GetAllRacesPageDataUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetAllRacesPageDataResult; + const presented = output.present.mock.calls[0]?.[0] as GetAllRacesPageDataResult; expect(presented.races).toEqual([ { @@ -150,7 +150,7 @@ describe('GetAllRacesPageDataUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetAllRacesPageDataResult; + const presented = output.present.mock.calls[0]?.[0] as GetAllRacesPageDataResult; expect(presented.races).toEqual([]); expect(presented.filters).toEqual({ diff --git a/core/racing/application/use-cases/GetAllRacesUseCase.test.ts b/core/racing/application/use-cases/GetAllRacesUseCase.test.ts index 1db794420..93357abac 100644 --- a/core/racing/application/use-cases/GetAllRacesUseCase.test.ts +++ b/core/racing/application/use-cases/GetAllRacesUseCase.test.ts @@ -89,7 +89,7 @@ describe('GetAllRacesUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetAllRacesResult; + const presented = output.present.mock.calls[0]?.[0] as GetAllRacesResult; expect(presented.totalCount).toBe(2); expect(presented.races).toEqual([race1, race2]); expect(presented.leagues).toEqual([league1, league2]); @@ -112,7 +112,7 @@ describe('GetAllRacesUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetAllRacesResult; + const presented = output.present.mock.calls[0]?.[0] as GetAllRacesResult; expect(presented.totalCount).toBe(0); expect(presented.races).toEqual([]); expect(presented.leagues).toEqual([]); @@ -123,7 +123,6 @@ describe('GetAllRacesUseCase', () => { mockRaceRepo, mockLeagueRepo, mockLogger, - output, ); const error = new Error('Repository error'); diff --git a/core/racing/application/use-cases/GetAllTeamsUseCase.test.ts b/core/racing/application/use-cases/GetAllTeamsUseCase.test.ts index 3578cb5bb..af46a101b 100644 --- a/core/racing/application/use-cases/GetAllTeamsUseCase.test.ts +++ b/core/racing/application/use-cases/GetAllTeamsUseCase.test.ts @@ -82,7 +82,7 @@ describe('GetAllTeamsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetAllTeamsResult; + const presented = output.present.mock.calls[0]?.[0] as GetAllTeamsResult; expect(presented).toEqual({ teams: [ @@ -127,7 +127,7 @@ describe('GetAllTeamsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetAllTeamsResult; + const presented = output.present.mock.calls[0]?.[0] as GetAllTeamsResult; expect(presented).toEqual({ teams: [], diff --git a/core/racing/application/use-cases/GetDriversLeaderboardUseCase.test.ts b/core/racing/application/use-cases/GetDriversLeaderboardUseCase.test.ts index 19d877f90..6bf185100 100644 --- a/core/racing/application/use-cases/GetDriversLeaderboardUseCase.test.ts +++ b/core/racing/application/use-cases/GetDriversLeaderboardUseCase.test.ts @@ -3,19 +3,19 @@ import { GetDriversLeaderboardUseCase, type GetDriversLeaderboardResult, type GetDriversLeaderboardInput, - type GetDriversLeaderboardErrorCode, } from './GetDriversLeaderboardUseCase'; import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; import type { IRankingService } from '../../domain/services/IRankingService'; import type { IDriverStatsService } from '../../domain/services/IDriverStatsService'; import type { Logger } from '@core/shared/application'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; -import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; describe('GetDriversLeaderboardUseCase', () => { const mockDriverFindAll = vi.fn(); const mockDriverRepo: IDriverRepository = { findById: vi.fn(), + findByIRacingId: vi.fn(), + existsByIRacingId: vi.fn(), findAll: mockDriverFindAll, create: vi.fn(), update: vi.fn(), @@ -57,7 +57,6 @@ describe('GetDriversLeaderboardUseCase', () => { mockDriverStatsService, mockGetDriverAvatar, mockLogger, - output, ); const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } }; @@ -99,7 +98,7 @@ describe('GetDriversLeaderboardUseCase', () => { rating: 2500, skillLevel: 'advanced', racesCompleted: 10, - wins: 5, + wins:5, podiums: 7, isActive: true, rank: 1, @@ -130,7 +129,6 @@ describe('GetDriversLeaderboardUseCase', () => { mockDriverStatsService, mockGetDriverAvatar, mockLogger, - output, ); mockDriverFindAll.mockResolvedValue([]); @@ -161,7 +159,6 @@ describe('GetDriversLeaderboardUseCase', () => { mockDriverStatsService, mockGetDriverAvatar, mockLogger, - output, ); const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } }; @@ -209,7 +206,6 @@ describe('GetDriversLeaderboardUseCase', () => { mockDriverStatsService, mockGetDriverAvatar, mockLogger, - output, ); const error = new Error('Repository error'); diff --git a/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.test.ts b/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.test.ts index cadbd108c..377c201e4 100644 --- a/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.test.ts +++ b/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.test.ts @@ -5,16 +5,12 @@ import { type GetEntitySponsorshipPricingResult, } from './GetEntitySponsorshipPricingUseCase'; import type { ISponsorshipPricingRepository } from '../../domain/repositories/ISponsorshipPricingRepository'; -import type { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository'; -import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository'; import type { Logger } from '@core/shared/application'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; describe('GetEntitySponsorshipPricingUseCase', () => { let mockSponsorshipPricingRepo: ISponsorshipPricingRepository; - let mockSponsorshipRequestRepo: ISponsorshipRequestRepository; - let mockSeasonSponsorshipRepo: ISeasonSponsorshipRepository; let mockLogger: Logger; let mockFindByEntity: Mock; let mockFindPendingByEntity: Mock; @@ -37,37 +33,6 @@ describe('GetEntitySponsorshipPricingUseCase', () => { exists: vi.fn(), findAcceptingApplications: vi.fn(), } as ISponsorshipPricingRepository; - mockSponsorshipRequestRepo = { - findPendingByEntity: mockFindPendingByEntity, - findByEntity: vi.fn(), - findById: vi.fn(), - save: vi.fn(), - update: vi.fn(), - delete: vi.fn(), - findBySponsorId: vi.fn(), - findByStatus: vi.fn(), - findBySponsorIdAndStatus: vi.fn(), - hasPendingRequest: vi.fn(), - findPendingBySponsor: vi.fn(), - findApprovedByEntity: vi.fn(), - findRejectedByEntity: vi.fn(), - countPendingByEntity: vi.fn(), - create: vi.fn(), - exists: vi.fn(), - } as ISponsorshipRequestRepository; - mockSeasonSponsorshipRepo = { - findBySeasonId: mockFindBySeasonId, - findById: vi.fn(), - save: vi.fn(), - update: vi.fn(), - delete: vi.fn(), - findAll: vi.fn(), - findByLeagueId: vi.fn(), - findBySponsorId: vi.fn(), - findBySeasonAndTier: vi.fn(), - create: vi.fn(), - exists: vi.fn(), - } as ISeasonSponsorshipRepository; mockLogger = { debug: vi.fn(), info: vi.fn(), @@ -80,8 +45,6 @@ describe('GetEntitySponsorshipPricingUseCase', () => { it('should return PRICING_NOT_CONFIGURED when no pricing found', async () => { const useCase = new GetEntitySponsorshipPricingUseCase( mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository, - mockSponsorshipRequestRepo as unknown as ISponsorshipRequestRepository, - mockSeasonSponsorshipRepo as unknown as ISeasonSponsorshipRepository, mockLogger, output, ); @@ -108,8 +71,6 @@ describe('GetEntitySponsorshipPricingUseCase', () => { it('should return pricing data when found', async () => { const useCase = new GetEntitySponsorshipPricingUseCase( mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository, - mockSponsorshipRequestRepo as unknown as ISponsorshipRequestRepository, - mockSeasonSponsorshipRepo as unknown as ISeasonSponsorshipRepository, mockLogger, output, ); @@ -118,6 +79,7 @@ describe('GetEntitySponsorshipPricingUseCase', () => { entityType: 'season', entityId: 'season1', }; + const pricing = { acceptingApplications: true, customRequirements: 'Some requirements', @@ -145,7 +107,7 @@ describe('GetEntitySponsorshipPricingUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = (output.present as Mock).mock.calls[0][0] as GetEntitySponsorshipPricingResult; + const presented = (output.present as Mock).mock.calls[0]?.[0] as GetEntitySponsorshipPricingResult; expect(presented.entityType).toBe('season'); expect(presented.entityId).toBe('season1'); @@ -170,8 +132,6 @@ describe('GetEntitySponsorshipPricingUseCase', () => { it('should return error when repository throws', async () => { const useCase = new GetEntitySponsorshipPricingUseCase( mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository, - mockSponsorshipRequestRepo as unknown as ISponsorshipRequestRepository, - mockSeasonSponsorshipRepo as unknown as ISeasonSponsorshipRepository, mockLogger, output, ); @@ -180,6 +140,7 @@ describe('GetEntitySponsorshipPricingUseCase', () => { entityType: 'season', entityId: 'season1', }; + const error = new Error('Repository error'); mockFindByEntity.mockRejectedValue(error); @@ -195,4 +156,4 @@ describe('GetEntitySponsorshipPricingUseCase', () => { expect(err.details.message).toBe('Repository error'); expect(output.present).not.toHaveBeenCalled(); }); -}); \ No newline at end of file +}); diff --git a/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.ts b/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.ts index 0562682f4..bd3dce7b2 100644 --- a/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.ts +++ b/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.ts @@ -6,9 +6,6 @@ */ import type { ISponsorshipPricingRepository } from '../../domain/repositories/ISponsorshipPricingRepository'; -import type { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository'; -import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository'; -import type { SponsorshipPricing, SponsorshipSlotConfig } from '../../domain/value-objects/SponsorshipPricing'; import type { Logger } from '@core/shared/application'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import { Result } from '@core/shared/application/Result'; @@ -47,8 +44,6 @@ export type GetEntitySponsorshipPricingErrorCode = export class GetEntitySponsorshipPricingUseCase { constructor( private readonly sponsorshipPricingRepo: ISponsorshipPricingRepository, - private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository, - private readonly seasonSponsorshipRepo: ISeasonSponsorshipRepository, private readonly logger: Logger, private readonly output: UseCaseOutputPort, ) {} diff --git a/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.test.ts b/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.test.ts index 3f8d5ac20..019f835a4 100644 --- a/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.test.ts @@ -133,7 +133,7 @@ describe('GetLeagueAdminPermissionsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueAdminPermissionsResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueAdminPermissionsResult; expect(presented.league).toBe(league); expect(presented.permissions).toEqual({ canManageSchedule: true, @@ -155,7 +155,7 @@ describe('GetLeagueAdminPermissionsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueAdminPermissionsResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueAdminPermissionsResult; expect(presented.league).toBe(league); expect(presented.permissions).toEqual({ canManageSchedule: true, diff --git a/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts b/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts index 31006f237..7fe471b96 100644 --- a/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts @@ -52,7 +52,7 @@ export class GetLeagueAdminPermissionsUseCase { } const membership = await this.leagueMembershipRepository.getMembership(leagueId, performerDriverId); - if (!membership || membership.status !== 'active' || (membership.role !== 'owner' && membership.role !== 'admin')) { + if (!membership || membership.status.toString() !== 'active' || (membership.role.toString() !== 'owner' && membership.role.toString() !== 'admin')) { this.logger.warn('User is not a member or not authorized for league admin permissions', { leagueId, performerDriverId, diff --git a/core/racing/application/use-cases/GetLeagueAdminUseCase.test.ts b/core/racing/application/use-cases/GetLeagueAdminUseCase.test.ts index 68ea2fe9b..e380023dd 100644 --- a/core/racing/application/use-cases/GetLeagueAdminUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueAdminUseCase.test.ts @@ -64,7 +64,7 @@ describe('GetLeagueAdminUseCase', () => { expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueAdminResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueAdminResult; expect(presented.league.id).toBe('league1'); expect(presented.league.ownerId).toBe('owner1'); }); diff --git a/core/racing/application/use-cases/GetLeagueDriverSeasonStatsUseCase.test.ts b/core/racing/application/use-cases/GetLeagueDriverSeasonStatsUseCase.test.ts index 93832d20a..ff390632f 100644 --- a/core/racing/application/use-cases/GetLeagueDriverSeasonStatsUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueDriverSeasonStatsUseCase.test.ts @@ -55,10 +55,29 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => { 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(), @@ -75,12 +94,27 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => { }; driverRepository = { findById: mockDriverFindById, + findByIRacingId: vi.fn(), + findAll: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + exists: vi.fn(), + existsByIRacingId: vi.fn(), }; teamRepository = { findById: mockTeamFindById, + findAll: vi.fn(), + findByLeagueId: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + exists: vi.fn(), }; driverRatingPort = { - getRating: mockDriverRatingGetRating, + getDriverRating: mockDriverRatingGetRating, + calculateRatingChange: vi.fn(), + updateDriverRating: vi.fn(), }; output = { @@ -138,7 +172,7 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueDriverSeasonStatsResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueDriverSeasonStatsResult; expect(presented.leagueId).toBe('league-1'); expect(presented.stats).toHaveLength(2); expect(presented.stats[0]).toEqual({ @@ -188,8 +222,8 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueDriverSeasonStatsResult; - expect(presented.stats[0].penaltyPoints).toBe(0); + const presented = output.present.mock.calls[0]?.[0] as GetLeagueDriverSeasonStatsResult; + expect(presented?.stats[0]?.penaltyPoints).toBe(0); }); it('should return LEAGUE_NOT_FOUND when no standings are found', async () => { diff --git a/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.test.ts b/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.test.ts index 439d2132c..6625306d9 100644 --- a/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.test.ts @@ -72,7 +72,7 @@ describe('GetLeagueJoinRequestsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueJoinRequestsResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueJoinRequestsResult; expect(presented.joinRequests).toHaveLength(1); expect(presented.joinRequests[0]).toMatchObject({ @@ -81,7 +81,7 @@ describe('GetLeagueJoinRequestsUseCase', () => { driverId: 'driver-1', message: 'msg', }); - expect(presented.joinRequests[0].driver).toBe(driver); + expect(presented?.joinRequests[0]?.driver).toBe(driver); }); it('should return LEAGUE_NOT_FOUND error when league does not exist', async () => { diff --git a/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.ts b/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.ts index 7e68592d6..262db3e40 100644 --- a/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.ts @@ -1,10 +1,10 @@ -import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; -import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; -import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; -import type { Driver } from '../../domain/entities/Driver'; import type { UseCaseOutputPort } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; +import type { Driver } from '../../domain/entities/Driver'; +import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; +import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; +import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; export interface GetLeagueJoinRequestsInput { leagueId: string; @@ -47,7 +47,7 @@ export class GetLeagueJoinRequestsUseCase { } const joinRequests = await this.leagueMembershipRepository.getJoinRequests(input.leagueId); - const driverIds = [...new Set(joinRequests.map(request => request.driverId))]; + const driverIds = [...new Set(joinRequests.map(request => request.driverId.toString()))]; const drivers = await Promise.all(driverIds.map(id => this.driverRepository.findById(id))); const driverMap = new Map( @@ -55,10 +55,14 @@ export class GetLeagueJoinRequestsUseCase { ); const enrichedJoinRequests: LeagueJoinRequestWithDriver[] = joinRequests - .filter(request => driverMap.has(request.driverId)) + .filter(request => driverMap.has(request.driverId.toString())) .map(request => ({ - ...request, - driver: driverMap.get(request.driverId)!, + id: request.id, + leagueId: request.leagueId.toString(), + driverId: request.driverId.toString(), + requestedAt: request.requestedAt.toDate(), + ...(request.message !== undefined && { message: request.message }), + driver: driverMap.get(request.driverId.toString())!, })); const result: GetLeagueJoinRequestsResult = { @@ -71,7 +75,7 @@ export class GetLeagueJoinRequestsUseCase { } catch (error: unknown) { const message = error && typeof error === 'object' && 'message' in error && typeof (error as any).message === 'string' - ? (error as any).message + ? (error as Error).message : 'Failed to load league join requests'; return Result.err({ diff --git a/core/racing/application/use-cases/GetLeagueMembershipsUseCase.test.ts b/core/racing/application/use-cases/GetLeagueMembershipsUseCase.test.ts index f4f49d4f4..5e30c1e3a 100644 --- a/core/racing/application/use-cases/GetLeagueMembershipsUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueMembershipsUseCase.test.ts @@ -99,14 +99,14 @@ describe('GetLeagueMembershipsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueMembershipsResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueMembershipsResult; - expect(presented.league).toEqual(league); - expect(presented.memberships).toHaveLength(2); - expect(presented.memberships[0].membership).toEqual(memberships[0]); - expect(presented.memberships[0].driver).toEqual(driver1); - expect(presented.memberships[1].membership).toEqual(memberships[1]); - expect(presented.memberships[1].driver).toEqual(driver2); + expect(presented?.league).toEqual(league); + expect(presented?.memberships).toHaveLength(2); + expect(presented?.memberships[0]?.membership).toEqual(memberships[0]); + expect(presented?.memberships[0]?.driver).toEqual(driver1); + expect(presented?.memberships[1]?.membership).toEqual(memberships[1]); + expect(presented?.memberships[1]?.driver).toEqual(driver2); }); it('should handle drivers not found', async () => { @@ -137,12 +137,12 @@ describe('GetLeagueMembershipsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueMembershipsResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueMembershipsResult; - expect(presented.league).toEqual(league); - expect(presented.memberships).toHaveLength(1); - expect(presented.memberships[0].membership).toEqual(memberships[0]); - expect(presented.memberships[0].driver).toBeNull(); + expect(presented?.league).toEqual(league); + expect(presented?.memberships).toHaveLength(1); + expect(presented?.memberships[0]?.membership).toEqual(memberships[0]); + expect(presented?.memberships[0]?.driver).toBeNull(); }); it('should return error when league not found', async () => { diff --git a/core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase.test.ts b/core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase.test.ts index 0f5649550..a7f2d87ba 100644 --- a/core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase.test.ts @@ -66,12 +66,12 @@ describe('GetLeagueOwnerSummaryUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueOwnerSummaryResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueOwnerSummaryResult; - expect(presented.league).toBe(league); - expect(presented.owner).toBe(driver); - expect(presented.rating).toBe(0); - expect(presented.rank).toBe(0); + expect(presented?.league).toBe(league); + expect(presented?.owner).toBe(driver); + expect(presented?.rating).toBe(0); + expect(presented?.rank).toBe(0); }); it('should return error when league does not exist', async () => { diff --git a/core/racing/application/use-cases/GetLeagueProtestsUseCase.test.ts b/core/racing/application/use-cases/GetLeagueProtestsUseCase.test.ts index ea6339ca8..4bf841464 100644 --- a/core/racing/application/use-cases/GetLeagueProtestsUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueProtestsUseCase.test.ts @@ -111,15 +111,15 @@ describe('GetLeagueProtestsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueProtestsResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueProtestsResult; - expect(presented.league).toEqual(league); - expect(presented.protests).toHaveLength(1); - const presentedProtest = presented.protests[0]; - expect(presentedProtest.protest).toEqual(protest); - expect(presentedProtest.race).toEqual(race); - expect(presentedProtest.protestingDriver).toEqual(driver1); - expect(presentedProtest.accusedDriver).toEqual(driver2); + expect(presented?.league).toEqual(league); + expect(presented?.protests).toHaveLength(1); + const presentedProtest = presented?.protests[0]; + expect(presentedProtest?.protest).toEqual(protest); + expect(presentedProtest?.race).toEqual(race); + expect(presentedProtest?.protestingDriver).toEqual(driver1); + expect(presentedProtest?.accusedDriver).toEqual(driver2); }); it('should return empty protests when no races', async () => { @@ -141,10 +141,10 @@ describe('GetLeagueProtestsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueProtestsResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueProtestsResult; - expect(presented.league).toEqual(league); - expect(presented.protests).toEqual([]); + expect(presented?.league).toEqual(league); + expect(presented?.protests).toEqual([]); }); it('should return LEAGUE_NOT_FOUND when league does not exist', async () => { diff --git a/core/racing/application/use-cases/GetLeagueScheduleUseCase.test.ts b/core/racing/application/use-cases/GetLeagueScheduleUseCase.test.ts index 6fa815f7b..2e2a1b343 100644 --- a/core/racing/application/use-cases/GetLeagueScheduleUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueScheduleUseCase.test.ts @@ -70,11 +70,11 @@ describe('GetLeagueScheduleUseCase', () => { expect(output.present).toHaveBeenCalledTimes(1); const presented = - output.present.mock.calls[0]![0] as GetLeagueScheduleResult; + output.present.mock.calls[0]?.[0] as GetLeagueScheduleResult; expect(presented.league).toBe(league); expect(presented.races).toHaveLength(1); - expect(presented.races[0].race).toBe(race); + expect(presented.races[0]?.race).toBe(race); }); it('should present empty schedule when no races exist', async () => { @@ -92,7 +92,7 @@ describe('GetLeagueScheduleUseCase', () => { expect(output.present).toHaveBeenCalledTimes(1); const presented = - output.present.mock.calls[0]![0] as GetLeagueScheduleResult; + output.present.mock.calls[0]?.[0] as GetLeagueScheduleResult; expect(presented.league).toBe(league); expect(presented.races).toHaveLength(0); @@ -119,7 +119,7 @@ describe('GetLeagueScheduleUseCase', () => { it('should return REPOSITORY_ERROR when repository throws', async () => { const leagueId = 'league-1'; - const league = { id: leagueId } as League; + const league = { id: leagueId } as unknown as League; const repositoryError = new Error('DB down'); leagueRepository.findById.mockResolvedValue(league); diff --git a/core/racing/application/use-cases/GetLeagueScoringConfigUseCase.test.ts b/core/racing/application/use-cases/GetLeagueScoringConfigUseCase.test.ts index 5d273c776..f1a703137 100644 --- a/core/racing/application/use-cases/GetLeagueScoringConfigUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueScoringConfigUseCase.test.ts @@ -60,12 +60,12 @@ describe('GetLeagueScoringConfigUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); const presented = - output.present.mock.calls[0][0] as GetLeagueScoringConfigResult; - expect(presented.league).toEqual(league); - expect(presented.season).toEqual(season); - expect(presented.scoringConfig).toEqual(scoringConfig); - expect(presented.game).toEqual(game); - expect(presented.preset).toEqual(preset); + output.present.mock.calls[0]?.[0] as GetLeagueScoringConfigResult; + expect(presented?.league).toEqual(league); + expect(presented?.season).toEqual(season); + expect(presented?.scoringConfig).toEqual(scoringConfig); + expect(presented?.game).toEqual(game); + expect(presented?.preset).toEqual(preset); }); it('should return scoring config for first season if no active', async () => { @@ -86,12 +86,12 @@ describe('GetLeagueScoringConfigUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); const presented = - output.present.mock.calls[0][0] as GetLeagueScoringConfigResult; - expect(presented.league).toEqual(league); - expect(presented.season).toEqual(season); - expect(presented.scoringConfig).toEqual(scoringConfig); - expect(presented.game).toEqual(game); - expect(presented.preset).toBeUndefined(); + output.present.mock.calls[0]?.[0] as GetLeagueScoringConfigResult; + expect(presented?.league).toEqual(league); + expect(presented?.season).toEqual(season); + expect(presented?.scoringConfig).toEqual(scoringConfig); + expect(presented?.game).toEqual(game); + expect(presented?.preset).toBeUndefined(); }); it('should return error if league not found', async () => { diff --git a/core/racing/application/use-cases/GetLeagueScoringConfigUseCase.ts b/core/racing/application/use-cases/GetLeagueScoringConfigUseCase.ts index 8e79286e0..aa97eafff 100644 --- a/core/racing/application/use-cases/GetLeagueScoringConfigUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueScoringConfigUseCase.ts @@ -1,15 +1,15 @@ -import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; -import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; -import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository'; -import type { IGameRepository } from '../../domain/repositories/IGameRepository'; -import type { LeagueScoringPreset } from '../../../bootstrap/LeagueScoringPresets'; import { Result } from '@core/shared/application/Result'; -import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; -import type { League } from '../../domain/entities/League'; -import type { Season } from '../../domain/entities/season/Season'; -import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig'; +import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { Game } from '../../domain/entities/Game'; +import type { League } from '../../domain/entities/League'; +import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig'; +import type { Season } from '../../domain/entities/season/Season'; +import type { IGameRepository } from '../../domain/repositories/IGameRepository'; +import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; +import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository'; +import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; +import type { LeagueScoringPreset } from '../../domain/types/LeagueScoringPreset'; export type GetLeagueScoringConfigInput = { leagueId: string; @@ -101,7 +101,7 @@ export class GetLeagueScoringConfigUseCase { const presetId = scoringConfig.scoringPresetId; const preset = presetId - ? this.presetProvider.getPresetById(presetId) + ? this.presetProvider.getPresetById(presetId.toString()) : undefined; const result: GetLeagueScoringConfigResult = { diff --git a/core/racing/application/use-cases/GetLeagueSeasonsUseCase.test.ts b/core/racing/application/use-cases/GetLeagueSeasonsUseCase.test.ts index f91c07f40..547e98c43 100644 --- a/core/racing/application/use-cases/GetLeagueSeasonsUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueSeasonsUseCase.test.ts @@ -9,7 +9,7 @@ import type { UseCaseOutputPort } from '@core/shared/application'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; -import { Season } from '../../domain/entities/Season'; +import { Season } from '../../domain/entities/season'; import { League } from '../../domain/entities/League'; describe('GetLeagueSeasonsUseCase', () => { @@ -83,18 +83,18 @@ describe('GetLeagueSeasonsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueSeasonsResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueSeasonsResult; - expect(presented.league).toBe(league); - expect(presented.seasons).toHaveLength(2); + expect(presented?.league).toBe(league); + expect(presented?.seasons).toHaveLength(2); - expect(presented.seasons[0]!.season).toBe(seasons[0]); - expect(presented.seasons[0]!.isPrimary).toBe(false); - expect(presented.seasons[0]!.isParallelActive).toBe(false); + expect(presented?.seasons[0]?.season).toBe(seasons[0]); + expect(presented?.seasons[0]?.isPrimary).toBe(false); + expect(presented?.seasons[0]?.isParallelActive).toBe(false); - expect(presented.seasons[1]!.season).toBe(seasons[1]); - expect(presented.seasons[1]!.isPrimary).toBe(false); - expect(presented.seasons[1]!.isParallelActive).toBe(false); + expect(presented?.seasons[1]?.season).toBe(seasons[1]); + expect(presented?.seasons[1]?.isPrimary).toBe(false); + expect(presented?.seasons[1]?.isParallelActive).toBe(false); }); it('should set isParallelActive true for active seasons when multiple active', async () => { @@ -132,11 +132,11 @@ describe('GetLeagueSeasonsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = output.present.mock.calls[0][0] as GetLeagueSeasonsResult; + const presented = output.present.mock.calls[0]?.[0] as GetLeagueSeasonsResult; - expect(presented.seasons).toHaveLength(2); - expect(presented.seasons[0]!.isParallelActive).toBe(true); - expect(presented.seasons[1]!.isParallelActive).toBe(true); + expect(presented?.seasons).toHaveLength(2); + expect(presented?.seasons[0]?.isParallelActive).toBe(true); + expect(presented?.seasons[1]?.isParallelActive).toBe(true); }); it('should return LEAGUE_NOT_FOUND error when league does not exist', async () => { diff --git a/core/racing/application/use-cases/GetLeagueWalletUseCase.test.ts b/core/racing/application/use-cases/GetLeagueWalletUseCase.test.ts index 881c7a6ea..abbb562c2 100644 --- a/core/racing/application/use-cases/GetLeagueWalletUseCase.test.ts +++ b/core/racing/application/use-cases/GetLeagueWalletUseCase.test.ts @@ -74,6 +74,7 @@ describe('GetLeagueWalletUseCase', () => { amount: Money.create(1200, 'USD'), description: 'Main Sponsor - TechCorp', metadata: {}, + completedAt: undefined, }).complete(); const membershipTx = Transaction.create({ @@ -83,6 +84,7 @@ describe('GetLeagueWalletUseCase', () => { amount: Money.create(1600, 'USD'), description: 'Season Fee - 32 drivers', metadata: {}, + completedAt: undefined, }).complete(); const withdrawalTx = Transaction.create({ @@ -92,6 +94,7 @@ describe('GetLeagueWalletUseCase', () => { amount: Money.create(430, 'USD'), description: 'Bank Transfer - Season 1 Payout', metadata: {}, + completedAt: undefined, }).complete(); const pendingPrizeTx = Transaction.create({ @@ -101,6 +104,7 @@ describe('GetLeagueWalletUseCase', () => { amount: Money.create(150, 'USD'), description: 'Championship Prize Pool (reserved)', metadata: {}, + completedAt: undefined, }); const refundTx = Transaction.create({ @@ -110,6 +114,7 @@ describe('GetLeagueWalletUseCase', () => { amount: Money.create(100, 'USD'), description: 'Refund for cancelled sponsorship', metadata: {}, + completedAt: undefined, }); const transactions = [ diff --git a/core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase.test.ts b/core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase.test.ts index 42ee6d1e1..cf64f1b95 100644 --- a/core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase.test.ts +++ b/core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase.test.ts @@ -8,7 +8,7 @@ import { import { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository'; import { ISponsorRepository } from '../../domain/repositories/ISponsorRepository'; import { SponsorshipRequest } from '../../domain/entities/SponsorshipRequest'; -import { Sponsor } from '../../domain/entities/Sponsor'; +import { Sponsor } from '../../domain/entities/sponsor/Sponsor'; import { Money } from '../../domain/value-objects/Money'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; @@ -72,16 +72,18 @@ describe('GetPendingSponsorshipRequestsUseCase', () => { expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); - const presented = (output.present as Mock).mock.calls[0][0] as GetPendingSponsorshipRequestsResult; + const presented = (output.present as Mock).mock.calls[0]?.[0] as GetPendingSponsorshipRequestsResult; + expect(presented).toBeDefined(); expect(presented.entityType).toBe('season'); expect(presented.entityId).toBe('entity-1'); expect(presented.totalCount).toBe(1); expect(presented.requests).toHaveLength(1); const summary = presented.requests[0]; - expect(summary.sponsor?.name).toBe('Test Sponsor'); - expect(summary.financials.offeredAmount.amount).toBe(10000); - expect(summary.financials.offeredAmount.currency).toBe('USD'); + expect(summary).toBeDefined(); + expect(summary!.sponsor?.name).toBe('Test Sponsor'); + expect(summary!.financials.offeredAmount.amount).toBe(10000); + expect(summary!.financials.offeredAmount.currency).toBe('USD'); }); it('should return error when repository fails', async () => { diff --git a/core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase.ts b/core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase.ts index 0b10effa9..ae0846755 100644 --- a/core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase.ts +++ b/core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase.ts @@ -11,7 +11,7 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC import { Result } from '@core/shared/application/Result'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import type { SponsorshipRequest } from '../../domain/entities/SponsorshipRequest'; -import type { Sponsor } from '../../domain/entities/Sponsor'; +import type { Sponsor } from '../../domain/entities/sponsor/Sponsor'; import { Money } from '../../domain/value-objects/Money'; export interface GetPendingSponsorshipRequestsInput { diff --git a/core/racing/application/use-cases/GetRaceDetailUseCase.ts b/core/racing/application/use-cases/GetRaceDetailUseCase.ts index 9245e9ce7..3992fd75b 100644 --- a/core/racing/application/use-cases/GetRaceDetailUseCase.ts +++ b/core/racing/application/use-cases/GetRaceDetailUseCase.ts @@ -1,16 +1,15 @@ -import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; -import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; -import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; -import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository'; -import type { IResultRepository } from '../../domain/repositories/IResultRepository'; -import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; -import { Result } from '@core/shared/application/Result'; -import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; +import { Result as DomainResult, Result } from '@core/shared/application/Result'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { League } from '../../domain/entities/League'; import type { Race } from '../../domain/entities/Race'; import type { RaceRegistration } from '../../domain/entities/RaceRegistration'; -import type { Result as DomainResult } from '../../domain/entities/Result'; +import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; +import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; +import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; +import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository'; +import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; +import type { IResultRepository } from '../../domain/repositories/IResultRepository'; export type GetRaceDetailInput = { raceId: string; @@ -33,7 +32,7 @@ export type GetRaceDetailResult = { }; export class GetRaceDetailUseCase { - private output: UseCaseOutputPort | null = null; + private output: UseCaseOutputPort | null = null; // TODO wtf this must be injected via constructor constructor( private readonly raceRepository: IRaceRepository, @@ -44,7 +43,7 @@ export class GetRaceDetailUseCase { private readonly leagueMembershipRepository: ILeagueMembershipRepository, ) {} - setOutput(output: UseCaseOutputPort) { + setOutput(output: UseCaseOutputPort) { // TODO must be removed this.output = output; } diff --git a/core/racing/application/use-cases/GetRacesPageDataUseCase.ts b/core/racing/application/use-cases/GetRacesPageDataUseCase.ts index 5e5a1c2d8..2aa450b2b 100644 --- a/core/racing/application/use-cases/GetRacesPageDataUseCase.ts +++ b/core/racing/application/use-cases/GetRacesPageDataUseCase.ts @@ -1,10 +1,10 @@ -import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; -import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import type { Logger } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; -import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; +import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { Race } from '../../domain/entities/Race'; +import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; +import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; export type GetRacesPageDataInput = { leagueId: string; diff --git a/core/racing/application/use-cases/GetSeasonDetailsUseCase.ts b/core/racing/application/use-cases/GetSeasonDetailsUseCase.ts index a541a294a..871c33b24 100644 --- a/core/racing/application/use-cases/GetSeasonDetailsUseCase.ts +++ b/core/racing/application/use-cases/GetSeasonDetailsUseCase.ts @@ -45,7 +45,7 @@ export class GetSeasonDetailsUseCase { } const season = await this.seasonRepository.findById(input.seasonId); - if (!season || season.leagueId !== league.id) { + if (!season || season.leagueId.toString() !== league.id.toString()) { return Result.err({ code: 'SEASON_NOT_FOUND', details: { diff --git a/core/racing/application/use-cases/GetSponsorDashboardUseCase.test.ts b/core/racing/application/use-cases/GetSponsorDashboardUseCase.test.ts index c22aa6db9..e1338ec01 100644 --- a/core/racing/application/use-cases/GetSponsorDashboardUseCase.test.ts +++ b/core/racing/application/use-cases/GetSponsorDashboardUseCase.test.ts @@ -11,7 +11,7 @@ import { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; import { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; import { IRaceRepository } from '../../domain/repositories/IRaceRepository'; -import { Sponsor } from '../../domain/entities/Sponsor'; +import { Sponsor } from '../../domain/entities/sponsor/Sponsor'; import { SeasonSponsorship } from '../../domain/entities/season/SeasonSponsorship'; import { Season } from '../../domain/entities/season/Season'; import { League } from '../../domain/entities/League'; @@ -121,6 +121,7 @@ describe('GetSponsorDashboardUseCase', () => { expect(output.present).toHaveBeenCalledTimes(1); const dashboard = (output.present as Mock).mock.calls[0][0] as GetSponsorDashboardResult; + expect(dashboard).toBeDefined(); expect(dashboard.sponsorId).toBe(sponsorId); expect(dashboard.metrics.impressions).toBe(100); // 1 completed race * 1 driver * 100 expect(dashboard.investment.totalInvestment.amount).toBe(10000); diff --git a/core/racing/application/use-cases/GetSponsorDashboardUseCase.ts b/core/racing/application/use-cases/GetSponsorDashboardUseCase.ts index a1b4ebfa2..d35031af3 100644 --- a/core/racing/application/use-cases/GetSponsorDashboardUseCase.ts +++ b/core/racing/application/use-cases/GetSponsorDashboardUseCase.ts @@ -170,7 +170,7 @@ export class GetSponsorDashboardUseCase { const result: GetSponsorDashboardResult = { sponsorId, - sponsorName: sponsor.name, + sponsorName: sponsor.name.toString(), metrics: { impressions: totalImpressions, impressionsChange: 0, diff --git a/core/racing/application/use-cases/ImportRaceResultsUseCase.ts b/core/racing/application/use-cases/ImportRaceResultsUseCase.ts index be38d1679..f0c0a3ebe 100644 --- a/core/racing/application/use-cases/ImportRaceResultsUseCase.ts +++ b/core/racing/application/use-cases/ImportRaceResultsUseCase.ts @@ -3,7 +3,7 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueReposit import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; import type { IStandingRepository } from '../../domain/repositories/IStandingRepository'; -import { Result as RaceResult } from '../../domain/entities/Result'; +import { Result as RaceResult } from '../../domain/entities/result/Result'; import type { Logger, UseCaseOutputPort } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; @@ -154,15 +154,15 @@ export class ImportRaceResultsUseCase { this.logger.info('ImportRaceResultsUseCase:race results created', { raceId }); - await this.standingRepository.recalculate(league.id); + await this.standingRepository.recalculate(league.id.toString()); this.logger.info('ImportRaceResultsUseCase:standings recalculated', { - leagueId: league.id, + leagueId: league.id.toString(), }); const result: ImportRaceResultsResult = { raceId, - leagueId: league.id, + leagueId: league.id.toString(), driversProcessed: rows.length, resultsRecorded: validEntities.length, errors: [], diff --git a/core/racing/application/use-cases/ListLeagueScoringPresetsUseCase.ts b/core/racing/application/use-cases/ListLeagueScoringPresetsUseCase.ts index 70d231981..658e90bd1 100644 --- a/core/racing/application/use-cases/ListLeagueScoringPresetsUseCase.ts +++ b/core/racing/application/use-cases/ListLeagueScoringPresetsUseCase.ts @@ -1,19 +1,11 @@ -import type { LeagueScoringPreset as BootstrapLeagueScoringPreset } from '../../../bootstrap/LeagueScoringPresets'; +import type { LeagueScoringPreset } from '../../domain/types/LeagueScoringPreset'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { UseCaseOutputPort } from '@core/shared/application'; export type ListLeagueScoringPresetsInput = {}; -export type LeagueScoringPreset = { - id: string; - name: string; - description: string; - primaryChampionshipType: string; - sessionSummary: string; - bonusSummary: string; - dropPolicySummary: string; -}; +export type { LeagueScoringPreset } from '../../domain/types/LeagueScoringPreset'; export interface ListLeagueScoringPresetsResult { presets: LeagueScoringPreset[]; @@ -27,7 +19,7 @@ export type ListLeagueScoringPresetsErrorCode = 'REPOSITORY_ERROR'; */ export class ListLeagueScoringPresetsUseCase { constructor( - private readonly presets: BootstrapLeagueScoringPreset[], + private readonly presets: LeagueScoringPreset[], private readonly output: UseCaseOutputPort, ) {} diff --git a/core/racing/application/use-cases/RegisterForRaceUseCase.ts b/core/racing/application/use-cases/RegisterForRaceUseCase.ts index 07fd00ff8..688ee284e 100644 --- a/core/racing/application/use-cases/RegisterForRaceUseCase.ts +++ b/core/racing/application/use-cases/RegisterForRaceUseCase.ts @@ -63,7 +63,7 @@ export class RegisterForRaceUseCase { } const membership = await this.membershipRepository.getMembership(leagueId, driverId); - if (!membership || membership.status !== 'active') { + if (!membership || membership.status.toString() !== 'active') { this.logger.error(`RegisterForRaceUseCase: driver ${driverId} not an active member of league ${leagueId}`); return Result.err>({ code: 'NOT_ACTIVE_MEMBER', diff --git a/core/racing/application/use-cases/ReviewProtestUseCase.ts b/core/racing/application/use-cases/ReviewProtestUseCase.ts index 33e5fc796..201c6bbcc 100644 --- a/core/racing/application/use-cases/ReviewProtestUseCase.ts +++ b/core/racing/application/use-cases/ReviewProtestUseCase.ts @@ -58,10 +58,10 @@ export class ReviewProtestUseCase { // Validate steward has authority (owner or admin of the league) const memberships = await this.leagueMembershipRepository.getLeagueMembers(race.leagueId); const stewardMembership = memberships.find( - m => m.driverId === input.stewardId && m.status === 'active' + m => m.driverId.toString() === input.stewardId && m.status.toString() === 'active' ); - if (!stewardMembership || (stewardMembership.role !== 'owner' && stewardMembership.role !== 'admin')) { + if (!stewardMembership || (stewardMembership.role.toString() !== 'owner' && stewardMembership.role.toString() !== 'admin')) { this.logger.warn('Unauthorized steward attempting to review protest', { stewardId: input.stewardId, leagueId: race.leagueId }); return Result.err({ code: 'NOT_LEAGUE_ADMIN', details: { message: 'Only league owners and admins can review protests' } }); } @@ -90,7 +90,7 @@ export class ReviewProtestUseCase { return Result.ok(undefined); } catch (error) { const message = error instanceof Error ? error.message : 'Failed to review protest'; - this.logger.error('Failed to review protest', { error: message }); + this.logger.error('Failed to review protest', new Error(message)); return Result.err({ code: 'REPOSITORY_ERROR', details: { message } }); } } diff --git a/core/racing/application/use-cases/SendFinalResultsUseCase.ts b/core/racing/application/use-cases/SendFinalResultsUseCase.ts index f0c72691e..a50776f27 100644 --- a/core/racing/application/use-cases/SendFinalResultsUseCase.ts +++ b/core/racing/application/use-cases/SendFinalResultsUseCase.ts @@ -4,7 +4,7 @@ import type { Logger, UseCaseOutputPort } from '@core/shared/application'; import type { NotificationService } from '../../../notifications/application/ports/NotificationService'; import type { NotificationType } from '../../../notifications/domain/types/NotificationTypes'; import type { RaceEvent } from '../../domain/entities/RaceEvent'; -import type { Result as RaceResult } from '../../domain/entities/Result'; +import type { Result as RaceResult } from '../../domain/entities/result/Result'; import type { IRaceEventRepository } from '../../domain/repositories/IRaceEventRepository'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; @@ -61,7 +61,7 @@ export class SendFinalResultsUseCase { return Result.err({ code: 'RACE_NOT_FOUND', details: { message: 'Race event not found' } }); } - const membership = await this.membershipRepository.getMembership(league.id, input.triggeredById); + const membership = await this.membershipRepository.getMembership(league.id.toString(), input.triggeredById); if (!membership || !isLeagueStewardOrHigherRole(membership.role)) { return Result.err({ code: 'INSUFFICIENT_PERMISSIONS', @@ -90,17 +90,17 @@ export class SendFinalResultsUseCase { for (const driverResult of results) { await this.sendFinalResultsNotification( - driverResult.driverId, + driverResult.driverId.toString(), raceEvent, driverResult, - league.id, + league.id.toString(), false, ); notificationsSent += 1; } const result: SendFinalResultsResult = { - leagueId: league.id, + leagueId: league.id.toString(), raceId: raceEvent.id, notificationsSent, }; @@ -127,8 +127,8 @@ export class SendFinalResultsUseCase { const incidents = driverResult?.incidents ?? 0; const finalRatingChange = this.calculateFinalRatingChange( - driverResult?.position, - driverResult?.incidents, + driverResult?.position?.toNumber(), + driverResult?.incidents?.toNumber(), hadPenaltiesApplied, ); diff --git a/core/racing/application/use-cases/SendPerformanceSummaryUseCase.ts b/core/racing/application/use-cases/SendPerformanceSummaryUseCase.ts index df94c5d8e..e2426136f 100644 --- a/core/racing/application/use-cases/SendPerformanceSummaryUseCase.ts +++ b/core/racing/application/use-cases/SendPerformanceSummaryUseCase.ts @@ -4,7 +4,9 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC import type { NotificationService } from '../../../notifications/application/ports/NotificationService'; import type { NotificationType } from '../../../notifications/domain/types/NotificationTypes'; import type { RaceEvent } from '../../domain/entities/RaceEvent'; -import type { Result as RaceResult } from '../../domain/entities/Result'; +import type { Result as RaceResult } from '../../domain/entities/result/Result'; +import { Position } from '../../domain/entities/result/Position'; +import { IncidentCount } from '../../domain/entities/result/IncidentCount'; import type { IRaceEventRepository } from '../../domain/repositories/IRaceEventRepository'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; @@ -72,7 +74,7 @@ export class SendPerformanceSummaryUseCase { } if (input.triggeredById !== input.driverId) { - const membership = await this.membershipRepository.getMembership(league.id, input.triggeredById); + const membership = await this.membershipRepository.getMembership(league.id.toString(), input.triggeredById); if (!membership || !isLeagueStewardOrHigherRole(membership.role)) { return Result.err({ code: 'INSUFFICIENT_PERMISSIONS', @@ -90,7 +92,7 @@ export class SendPerformanceSummaryUseCase { } const results = await this.resultRepository.findByRaceId(mainRaceSession.id); - const driverResult = results.find(r => r.driverId === input.driverId); + const driverResult = results.find(r => r.driverId.toString() === input.driverId); if (!driverResult) { return Result.err({ @@ -101,11 +103,11 @@ export class SendPerformanceSummaryUseCase { let notificationsSent = 0; - await this.sendPerformanceSummaryNotification(input.driverId, raceEvent, driverResult, league.id); + await this.sendPerformanceSummaryNotification(input.driverId, raceEvent, driverResult, league.id.toString()); notificationsSent += 1; const result: SendPerformanceSummaryResult = { - leagueId: league.id, + leagueId: league.id.toString(), raceId: raceEvent.id, driverId: input.driverId, notificationsSent, @@ -131,10 +133,12 @@ export class SendPerformanceSummaryUseCase { const positionChange = driverResult?.getPositionChange() ?? 0; const incidents = driverResult?.incidents ?? 0; - const provisionalRatingChange = this.calculateProvisionalRatingChange(driverResult?.position, driverResult?.incidents); + const provisionalRatingChange = this.calculateProvisionalRatingChange(driverResult?.position?.toNumber(), driverResult?.incidents?.toNumber()); const title = `Race Complete: ${raceEvent.name}`; - const body = this.buildPerformanceSummaryBody(position, positionChange, incidents, provisionalRatingChange); + const positionValue = position instanceof Position ? position.toNumber() : position; + const incidentValue = incidents instanceof IncidentCount ? incidents.toNumber() : incidents; + const body = this.buildPerformanceSummaryBody(positionValue, positionChange, incidentValue, provisionalRatingChange); await this.notificationService.sendNotification({ recipientId: driverId, @@ -147,9 +151,9 @@ export class SendPerformanceSummaryUseCase { raceEventId: raceEvent.id, sessionId: raceEvent.getMainRaceSession()?.id ?? '', leagueId, - position, + position: positionValue, positionChange, - incidents, + incidents: incidentValue, provisionalRatingChange, }, actions: [ diff --git a/core/racing/application/utils/RaceResultGenerator.ts b/core/racing/application/utils/RaceResultGenerator.ts index 298e772be..b42c56bd0 100644 --- a/core/racing/application/utils/RaceResultGenerator.ts +++ b/core/racing/application/utils/RaceResultGenerator.ts @@ -1,4 +1,4 @@ -import { Result } from '../../domain/entities/Result'; +import { Result } from '../../domain/entities/result/Result'; /** * Enhanced race result generator with detailed incident types diff --git a/core/racing/domain/entities/ResultWithIncidents.ts b/core/racing/domain/entities/ResultWithIncidents.ts index bc9c6fb76..f71a8f1a8 100644 --- a/core/racing/domain/entities/ResultWithIncidents.ts +++ b/core/racing/domain/entities/ResultWithIncidents.ts @@ -2,13 +2,13 @@ * Enhanced Result entity with detailed incident tracking */ -import { RacingDomainValidationError } from '../errors/RacingDomainError'; import type { IEntity } from '@core/shared/domain'; +import { RacingDomainValidationError } from '../errors/RacingDomainError'; import { RaceIncidents, type IncidentRecord } from '../value-objects/RaceIncidents'; -import { RaceId } from './RaceId'; import { DriverId } from './DriverId'; -import { Position } from './result/Position'; +import { RaceId } from './RaceId'; import { LapTime } from './result/LapTime'; +import { Position } from './result/Position'; export class ResultWithIncidents implements IEntity { readonly id: string; @@ -66,6 +66,7 @@ export class ResultWithIncidents implements IEntity { }); } + // TODO WE DONT NEED ANY LEGACY CODE WE ARE NOT EVEN LIVE /** * Create from legacy Result data (with incidents as number) */ diff --git a/core/racing/domain/entities/SponsorshipRequest.test.ts b/core/racing/domain/entities/SponsorshipRequest.test.ts index 63167391b..cc1d7b711 100644 --- a/core/racing/domain/entities/SponsorshipRequest.test.ts +++ b/core/racing/domain/entities/SponsorshipRequest.test.ts @@ -8,9 +8,9 @@ describe('SponsorshipRequest', () => { const validProps = { id: 'request-123', sponsorId: 'sponsor-456', - entityType: 'driver', + entityType: 'driver' as SponsorableEntityType, entityId: 'driver-789', - tier: 'main', + tier: 'main' as SponsorshipTier, offeredAmount: validMoney, }; diff --git a/core/racing/domain/entities/league-wallet/LeagueWallet.test.ts b/core/racing/domain/entities/league-wallet/LeagueWallet.test.ts index 416caf3c0..a3d9fac78 100644 --- a/core/racing/domain/entities/league-wallet/LeagueWallet.test.ts +++ b/core/racing/domain/entities/league-wallet/LeagueWallet.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { LeagueWallet } from './LeagueWallet'; -import { Money } from '../value-objects/Money'; +import { Money } from '../../value-objects/Money'; describe('LeagueWallet', () => { it('should create a league wallet', () => { diff --git a/core/racing/domain/entities/prize/Prize.ts b/core/racing/domain/entities/prize/Prize.ts index 4427d4654..6cc54a049 100644 --- a/core/racing/domain/entities/prize/Prize.ts +++ b/core/racing/domain/entities/prize/Prize.ts @@ -11,7 +11,7 @@ import type { Money } from '../../value-objects/Money'; import { Position } from '../championship/Position'; import { PrizeId } from './PrizeId'; import { PrizeStatus } from './PrizeStatus'; -import { SeasonId } from '../SeasonId'; +import { SeasonId } from '../season/SeasonId'; import { DriverId } from '../DriverId'; export interface PrizeProps { diff --git a/core/racing/domain/entities/sponsor/Sponsor.ts b/core/racing/domain/entities/sponsor/Sponsor.ts index f927f157f..6f48167fd 100644 --- a/core/racing/domain/entities/sponsor/Sponsor.ts +++ b/core/racing/domain/entities/sponsor/Sponsor.ts @@ -78,9 +78,9 @@ export class Sponsor implements IEntity { id: this.id, name, contactEmail, - logoUrl, - websiteUrl, createdAt: this.createdAt, + ...(logoUrl !== undefined ? { logoUrl } : {}), + ...(websiteUrl !== undefined ? { websiteUrl } : {}), }); } } \ No newline at end of file diff --git a/core/racing/domain/repositories/IPrizeRepository.ts b/core/racing/domain/repositories/IPrizeRepository.ts index 0142932e3..3b1b9c2bf 100644 --- a/core/racing/domain/repositories/IPrizeRepository.ts +++ b/core/racing/domain/repositories/IPrizeRepository.ts @@ -4,13 +4,14 @@ * Defines operations for Prize entity persistence */ -import type { Prize, PrizeStatus } from '../entities/Prize'; +import type { Prize } from '../entities/prize/Prize'; +import type { PrizeStatusValue } from '../entities/prize/PrizeStatus'; export interface IPrizeRepository { findById(id: string): Promise; findBySeasonId(seasonId: string): Promise; findByDriverId(driverId: string): Promise; - findByStatus(status: PrizeStatus): Promise; + findByStatus(status: PrizeStatusValue): Promise; findBySeasonAndPosition(seasonId: string, position: number): Promise; create(prize: Prize): Promise; update(prize: Prize): Promise; diff --git a/core/racing/domain/repositories/ISponsorRepository.ts b/core/racing/domain/repositories/ISponsorRepository.ts index 97a0bf4d3..5c0138975 100644 --- a/core/racing/domain/repositories/ISponsorRepository.ts +++ b/core/racing/domain/repositories/ISponsorRepository.ts @@ -4,7 +4,7 @@ * Defines operations for Sponsor aggregate persistence */ -import type { Sponsor } from '../entities/Sponsor'; +import type { Sponsor } from '../entities/sponsor/Sponsor'; export interface ISponsorRepository { findById(id: string): Promise; diff --git a/core/racing/domain/services/EventScoringService.test.ts b/core/racing/domain/services/EventScoringService.test.ts index 199a0df7b..56489cffe 100644 --- a/core/racing/domain/services/EventScoringService.test.ts +++ b/core/racing/domain/services/EventScoringService.test.ts @@ -6,12 +6,12 @@ import type { SessionType } from '@core/racing/domain/types/SessionType'; import { PointsTable } from '@core/racing/domain/value-objects/PointsTable'; import type { BonusRule } from '@core/racing/domain/types/BonusRule'; import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig'; -import { Result } from '@core/racing/domain/entities/Result'; +import { Result } from '@core/racing/domain/entities/result/Result'; import type { Penalty } from '@core/racing/domain/entities/Penalty'; import type { ChampionshipType } from '@core/racing/domain/types/ChampionshipType'; -import { makeDriverRef } from '../../testing/factories/racing/DriverRefFactory'; -import { makePointsTable } from '../../testing/factories/racing/PointsTableFactory'; -import { makeChampionshipConfig } from '../../testing/factories/racing/ChampionshipConfigFactory'; +import { makeDriverRef } from '../../../testing/factories/racing/DriverRefFactory'; +import { makePointsTable } from '../../../testing/factories/racing/PointsTableFactory'; +import { makeChampionshipConfig } from '../../../testing/factories/racing/ChampionshipConfigFactory'; describe('EventScoringService', () => { diff --git a/core/racing/domain/services/SeasonScheduleGenerator.ts b/core/racing/domain/services/SeasonScheduleGenerator.ts index ef1b1d1d2..7280f832a 100644 --- a/core/racing/domain/services/SeasonScheduleGenerator.ts +++ b/core/racing/domain/services/SeasonScheduleGenerator.ts @@ -6,6 +6,7 @@ import { RaceTimeOfDay } from '../value-objects/RaceTimeOfDay'; import type { Weekday } from '../types/Weekday'; import { weekdayToIndex } from '../types/Weekday'; import type { IDomainCalculationService } from '@core/shared/domain'; +import { RacingDomainValidationError } from '../errors/RacingDomainError'; function cloneDate(date: Date): Date { return new Date(date.getTime()); @@ -63,15 +64,15 @@ function generateWeeklyOrEveryNWeeksSlots( const result: ScheduledRaceSlot[] = []; const recurrence = schedule.recurrence; const weekdays = - recurrence.kind === 'weekly' || recurrence.kind === 'everyNWeeks' - ? recurrence.weekdays.getAll() + recurrence.props.kind === 'weekly' || recurrence.props.kind === 'everyNWeeks' + ? recurrence.props.weekdays.getAll() : []; if (weekdays.length === 0) { throw new RacingDomainValidationError('RecurrenceStrategy has no weekdays'); } - const intervalWeeks = recurrence.kind === 'everyNWeeks' ? recurrence.intervalWeeks : 1; + const intervalWeeks = recurrence.props.kind === 'everyNWeeks' ? recurrence.props.intervalWeeks : 1; let anchorWeekStart = cloneDate(schedule.startDate); let roundNumber = 1; @@ -123,11 +124,11 @@ function findNthWeekdayOfMonth(base: Date, ordinal: 1 | 2 | 3 | 4, weekday: Week function generateMonthlySlots(schedule: SeasonSchedule, maxRounds: number): ScheduledRaceSlot[] { const result: ScheduledRaceSlot[] = []; const recurrence = schedule.recurrence; - if (recurrence.kind !== 'monthlyNthWeekday') { + if (recurrence.props.kind !== 'monthlyNthWeekday') { return result; } - const { ordinal, weekday } = recurrence.monthlyPattern; + const { ordinal, weekday } = recurrence.props.monthlyPattern; let currentMonthDate = new Date( schedule.startDate.getFullYear(), schedule.startDate.getMonth(), @@ -168,7 +169,7 @@ export class SeasonScheduleGenerator { const recurrence: RecurrenceStrategy = schedule.recurrence; - if (recurrence.kind === 'monthlyNthWeekday') { + if (recurrence.props.kind === 'monthlyNthWeekday') { return generateMonthlySlots(schedule, maxRounds); } diff --git a/core/racing/domain/types/LeagueRoles.ts b/core/racing/domain/types/LeagueRoles.ts index e35877735..ac9003bba 100644 --- a/core/racing/domain/types/LeagueRoles.ts +++ b/core/racing/domain/types/LeagueRoles.ts @@ -4,12 +4,12 @@ * Utility functions for working with league membership roles. */ -import type { MembershipRole } from '../entities/LeagueMembership'; +import type { MembershipRoleValue } from '../entities/MembershipRole'; /** * Role hierarchy (higher number = more authority) */ -const ROLE_HIERARCHY: Record = { +const ROLE_HIERARCHY: Record = { member: 0, steward: 1, admin: 2, @@ -19,21 +19,21 @@ const ROLE_HIERARCHY: Record = { /** * Check if a role is at least steward level */ -export function isLeagueStewardOrHigherRole(role: MembershipRole): boolean { +export function isLeagueStewardOrHigherRole(role: MembershipRoleValue): boolean { return ROLE_HIERARCHY[role] >= ROLE_HIERARCHY.steward; } /** * Check if a role is at least admin level */ -export function isLeagueAdminOrHigherRole(role: MembershipRole): boolean { +export function isLeagueAdminOrHigherRole(role: MembershipRoleValue): boolean { return ROLE_HIERARCHY[role] >= ROLE_HIERARCHY.admin; } /** * Check if a role is owner */ -export function isLeagueOwnerRole(role: MembershipRole): boolean { +export function isLeagueOwnerRole(role: MembershipRoleValue): boolean { return role === 'owner'; } @@ -41,33 +41,33 @@ export function isLeagueOwnerRole(role: MembershipRole): boolean { * Compare two roles * Returns positive if role1 > role2, negative if role1 < role2, 0 if equal */ -export function compareRoles(role1: MembershipRole, role2: MembershipRole): number { +export function compareRoles(role1: MembershipRoleValue, role2: MembershipRoleValue): number { return ROLE_HIERARCHY[role1] - ROLE_HIERARCHY[role2]; } /** * Get role display name */ -export function getRoleDisplayName(role: MembershipRole): string { - const names: Record = { +export function getRoleDisplayName(role: MembershipRoleValue): string { + const names: Record = { member: 'Member', steward: 'Steward', admin: 'Admin', owner: 'Owner', }; - return names[role]; + return names[role] || role; } /** * Get all roles in order of hierarchy */ -export function getAllRolesOrdered(): MembershipRole[] { +export function getAllRolesOrdered(): MembershipRoleValue[] { return ['member', 'steward', 'admin', 'owner']; } /** * Get roles that can be assigned (excludes owner as it's transferred, not assigned) */ -export function getAssignableRoles(): MembershipRole[] { +export function getAssignableRoles(): MembershipRoleValue[] { return ['member', 'steward', 'admin']; } \ No newline at end of file diff --git a/core/racing/domain/types/LeagueScoringPreset.ts b/core/racing/domain/types/LeagueScoringPreset.ts new file mode 100644 index 000000000..cd2119de1 --- /dev/null +++ b/core/racing/domain/types/LeagueScoringPreset.ts @@ -0,0 +1,21 @@ +/** + * Domain Types: LeagueScoringPreset + * + * Local type definition for league scoring presets to avoid cross-module dependencies + */ + +export type LeagueScoringPresetPrimaryChampionshipType = + | 'driver' + | 'team' + | 'nations' + | 'trophy'; + +export interface LeagueScoringPreset { + id: string; + name: string; + description: string; + primaryChampionshipType: LeagueScoringPresetPrimaryChampionshipType; + dropPolicySummary: string; + sessionSummary: string; + bonusSummary: string; +} \ No newline at end of file diff --git a/core/racing/domain/value-objects/RaceIncidents.ts b/core/racing/domain/value-objects/RaceIncidents.ts index e81cc8299..f27cbf25b 100644 --- a/core/racing/domain/value-objects/RaceIncidents.ts +++ b/core/racing/domain/value-objects/RaceIncidents.ts @@ -104,10 +104,10 @@ export class RaceIncidents implements IValueObject { return sortedThis.every((incident, index) => { const otherIncident = sortedOther[index]; - return incident.type === otherIncident.type && - incident.lap === otherIncident.lap && - incident.description === otherIncident.description && - incident.penaltyPoints === otherIncident.penaltyPoints; + return incident.type === otherIncident?.type && + incident.lap === otherIncident?.lap && + incident.description === otherIncident?.description && + incident.penaltyPoints === otherIncident?.penaltyPoints; }); } diff --git a/core/social/application/use-cases/GetCurrentUserSocialUseCase.ts b/core/social/application/use-cases/GetCurrentUserSocialUseCase.ts index 3c80a5c00..622e4ed04 100644 --- a/core/social/application/use-cases/GetCurrentUserSocialUseCase.ts +++ b/core/social/application/use-cases/GetCurrentUserSocialUseCase.ts @@ -1,4 +1,4 @@ -import type { Logger , UseCaseOutputPort } from '@core/shared/application'; +import type { Logger, UseCaseOutputPort } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ISocialGraphRepository } from '../../domain/repositories/ISocialGraphRepository'; @@ -59,9 +59,11 @@ export class GetCurrentUserSocialUseCase { ); } + // TODO looks like this must still be implemented? + const friends: FriendDTO[] = friendsDomain.map(friend => ({ driverId: friend.id, - displayName: friend.name, + displayName: friend.name.toString(), avatarUrl: '', isOnline: false, lastSeen: new Date(), diff --git a/core/social/infrastructure/inmemory/InMemorySocialAndFeed.ts b/core/social/infrastructure/inmemory/InMemorySocialAndFeed.ts index a8fa14db5..398e2e67c 100644 --- a/core/social/infrastructure/inmemory/InMemorySocialAndFeed.ts +++ b/core/social/infrastructure/inmemory/InMemorySocialAndFeed.ts @@ -1,8 +1,8 @@ import type { Driver } from '@core/racing/domain/entities/Driver'; -import type { FeedItem } from '@core/social/domain/types/FeedItem'; +import type { Logger } from '@core/shared/application'; import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository'; import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository'; -import type { Logger } from '@core/shared/application'; +import type { FeedItem } from '@core/social/domain/types/FeedItem'; export type Friendship = { driverId: string; @@ -18,7 +18,6 @@ export type RacingSeedData = { export class InMemoryFeedRepository implements IFeedRepository { private readonly feedEvents: FeedItem[]; private readonly friendships: Friendship[]; - private readonly driversById: Map; private readonly logger: Logger; constructor(logger: Logger, seed: RacingSeedData) { @@ -26,7 +25,6 @@ export class InMemoryFeedRepository implements IFeedRepository { this.logger.info('InMemoryFeedRepository initialized.'); this.feedEvents = seed.feedEvents; this.friendships = seed.friendships; - this.driversById = new Map(seed.drivers.map((d) => [d.id, d])); } async getFeedForDriver(driverId: string, limit?: number): Promise { @@ -52,7 +50,7 @@ export class InMemoryFeedRepository implements IFeedRepository { this.logger.info(`Found ${sorted.length} feed items for driver: ${driverId}.`); return typeof limit === 'number' ? sorted.slice(0, limit) : sorted; } catch (error) { - this.logger.error(`Error getting feed for driver ${driverId}:`, error); + this.logger.error(`Error getting feed for driver ${driverId}:`, error as Error); throw error; } } @@ -67,7 +65,7 @@ export class InMemoryFeedRepository implements IFeedRepository { this.logger.info(`Found ${sorted.length} global feed items.`); return typeof limit === 'number' ? sorted.slice(0, limit) : sorted; } catch (error) { - this.logger.error(`Error getting global feed:`, error); + this.logger.error(`Error getting global feed:`, error as Error); throw error; } } @@ -94,7 +92,7 @@ export class InMemorySocialGraphRepository implements ISocialGraphRepository { this.logger.info(`Found ${friendIds.length} friend IDs for driver: ${driverId}.`); return friendIds; } catch (error) { - this.logger.error(`Error getting friend IDs for driver ${driverId}:`, error); + this.logger.error(`Error getting friend IDs for driver ${driverId}:`, error as Error); throw error; } } @@ -109,7 +107,7 @@ export class InMemorySocialGraphRepository implements ISocialGraphRepository { this.logger.info(`Found ${friends.length} friends for driver: ${driverId}.`); return friends; } catch (error) { - this.logger.error(`Error getting friends for driver ${driverId}:`, error); + this.logger.error(`Error getting friends for driver ${driverId}:`, error as Error); throw error; } } @@ -141,7 +139,7 @@ export class InMemorySocialGraphRepository implements ISocialGraphRepository { this.logger.info(`Found ${result.length} suggested friends for driver: ${driverId}.`); return result; } catch (error) { - this.logger.error(`Error getting suggested friends for driver ${driverId}:`, error); + this.logger.error(`Error getting suggested friends for driver ${driverId}:`, error as Error); throw error; } } diff --git a/core/testing/factories/racing/ChampionshipConfigFactory.ts b/core/testing/factories/racing/ChampionshipConfigFactory.ts new file mode 100644 index 000000000..29adbec29 --- /dev/null +++ b/core/testing/factories/racing/ChampionshipConfigFactory.ts @@ -0,0 +1,58 @@ +import type { ChampionshipConfig } from '../../../racing/domain/types/ChampionshipConfig'; +import type { ChampionshipType } from '../../../racing/domain/types/ChampionshipType'; +import type { SessionType } from '../../../racing/domain/types/SessionType'; +import type { BonusRule } from '../../../racing/domain/types/BonusRule'; +import type { DropScorePolicy, DropScoreStrategy } from '../../../racing/domain/types/DropScorePolicy'; +import { PointsTable } from '../../../racing/domain/value-objects/PointsTable'; + +interface ChampionshipConfigInput { + id: string; + name: string; + sessionTypes: SessionType[]; + mainPoints?: number[]; + mainBonusRules?: BonusRule[]; + type?: ChampionshipType; + dropScorePolicy?: DropScorePolicy; + strategy?: DropScoreStrategy; +} + +export function makeChampionshipConfig(input: ChampionshipConfigInput): ChampionshipConfig { + const { + id, + name, + sessionTypes, + mainPoints = [25, 18, 15, 12, 10, 8, 6, 4, 2, 1], + mainBonusRules = [], + type = 'driver', + dropScorePolicy = { strategy: 'none' }, + } = input; + + const pointsTableBySessionType: Record = {} as Record; + + // Convert array format to PointsTable for each session type + sessionTypes.forEach(sessionType => { + const pointsArray = mainPoints; + const pointsMap: Record = {}; + pointsArray.forEach((points, index) => { + pointsMap[index + 1] = points; + }); + pointsTableBySessionType[sessionType] = new PointsTable(pointsMap); + }); + + const bonusRulesBySessionType: Record = {} as Record; + + // Add bonus rules for each session type + sessionTypes.forEach(sessionType => { + bonusRulesBySessionType[sessionType] = mainBonusRules; + }); + + return { + id, + name, + type, + sessionTypes, + pointsTableBySessionType, + bonusRulesBySessionType, + dropScorePolicy, + }; +} \ No newline at end of file diff --git a/core/testing/factories/racing/DriverRefFactory.ts b/core/testing/factories/racing/DriverRefFactory.ts new file mode 100644 index 000000000..cef3fbe14 --- /dev/null +++ b/core/testing/factories/racing/DriverRefFactory.ts @@ -0,0 +1,9 @@ +import type { DriverId } from '../../../racing/domain/entities/DriverId'; +import type { ParticipantRef } from '../../../racing/domain/types/ParticipantRef'; + +export function makeDriverRef(driverId: string): ParticipantRef { + return { + id: driverId, + type: 'driver', + }; +} \ No newline at end of file diff --git a/core/testing/factories/racing/PointsTableFactory.ts b/core/testing/factories/racing/PointsTableFactory.ts new file mode 100644 index 000000000..3abc9fa80 --- /dev/null +++ b/core/testing/factories/racing/PointsTableFactory.ts @@ -0,0 +1,5 @@ +import { PointsTable } from '../../../racing/domain/value-objects/PointsTable'; + +export function makePointsTable(points: number[]): PointsTable { + return new PointsTable(points); +} \ No newline at end of file