fix issues in core
This commit is contained in:
@@ -139,6 +139,7 @@ export const DashboardProviders: Provider[] = [
|
|||||||
feedRepo: IFeedRepository,
|
feedRepo: IFeedRepository,
|
||||||
socialRepo: ISocialGraphRepository,
|
socialRepo: ISocialGraphRepository,
|
||||||
imageService: ImageServicePort,
|
imageService: ImageServicePort,
|
||||||
|
output: DashboardOverviewPresenter,
|
||||||
) =>
|
) =>
|
||||||
new DashboardOverviewUseCase(
|
new DashboardOverviewUseCase(
|
||||||
driverRepo,
|
driverRepo,
|
||||||
@@ -152,6 +153,7 @@ export const DashboardProviders: Provider[] = [
|
|||||||
socialRepo,
|
socialRepo,
|
||||||
async (driverId: string) => imageService.getDriverAvatar(driverId),
|
async (driverId: string) => imageService.getDriverAvatar(driverId),
|
||||||
() => null,
|
() => null,
|
||||||
|
output,
|
||||||
),
|
),
|
||||||
inject: [
|
inject: [
|
||||||
DRIVER_REPOSITORY_TOKEN,
|
DRIVER_REPOSITORY_TOKEN,
|
||||||
@@ -164,6 +166,7 @@ export const DashboardProviders: Provider[] = [
|
|||||||
FEED_REPOSITORY_TOKEN,
|
FEED_REPOSITORY_TOKEN,
|
||||||
SOCIAL_GRAPH_REPOSITORY_TOKEN,
|
SOCIAL_GRAPH_REPOSITORY_TOKEN,
|
||||||
IMAGE_SERVICE_TOKEN,
|
IMAGE_SERVICE_TOKEN,
|
||||||
|
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -20,7 +20,13 @@ export class DashboardService {
|
|||||||
async getDashboardOverview(driverId: string): Promise<DashboardOverviewDTO> {
|
async getDashboardOverview(driverId: string): Promise<DashboardOverviewDTO> {
|
||||||
this.logger.debug('[DashboardService] Getting dashboard overview:', { driverId });
|
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();
|
return this.presenter.getResponseModel();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ describe('DashboardOverviewPresenter', () => {
|
|||||||
it('maps DashboardOverviewResult to DashboardOverviewDTO correctly', () => {
|
it('maps DashboardOverviewResult to DashboardOverviewDTO correctly', () => {
|
||||||
const output = createOutput();
|
const output = createOutput();
|
||||||
|
|
||||||
presenter.present(Result.ok(output));
|
presenter.present(output);
|
||||||
const dto = presenter.getResponseModel();
|
const dto = presenter.getResponseModel();
|
||||||
|
|
||||||
expect(dto.activeLeaguesCount).toBe(2);
|
expect(dto.activeLeaguesCount).toBe(2);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Result } from '@core/shared/application/Result';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import type {
|
import type {
|
||||||
DashboardOverviewResult,
|
DashboardOverviewResult,
|
||||||
} from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
} from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
||||||
@@ -13,12 +13,10 @@ import {
|
|||||||
DashboardFriendSummaryDTO,
|
DashboardFriendSummaryDTO,
|
||||||
} from '../dtos/DashboardOverviewDTO';
|
} from '../dtos/DashboardOverviewDTO';
|
||||||
|
|
||||||
export class DashboardOverviewPresenter {
|
export class DashboardOverviewPresenter implements UseCaseOutputPort<DashboardOverviewResult> {
|
||||||
private responseModel: DashboardOverviewDTO | null = null;
|
private responseModel: DashboardOverviewDTO | null = null;
|
||||||
|
|
||||||
present(result: Result<DashboardOverviewResult, unknown>): void {
|
present(data: DashboardOverviewResult): void {
|
||||||
const data = result.unwrap();
|
|
||||||
|
|
||||||
const currentDriver: DashboardDriverSummaryDTO | null = data.currentDriver
|
const currentDriver: DashboardDriverSummaryDTO | null = data.currentDriver
|
||||||
? {
|
? {
|
||||||
id: data.currentDriver.driver.id,
|
id: data.currentDriver.driver.id,
|
||||||
|
|||||||
@@ -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 type { FeedItem } from '@core/social/domain/types/FeedItem';
|
||||||
import { Result as UseCaseResult } from '@core/shared/application/Result';
|
import { Result as UseCaseResult } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
|
|
||||||
describe('DashboardOverviewUseCase', () => {
|
describe('DashboardOverviewUseCase', () => {
|
||||||
it('partitions upcoming races into myUpcomingRaces and otherUpcomingRaces and selects nextRace from myUpcomingRaces', async () => {
|
it('partitions upcoming races into myUpcomingRaces and otherUpcomingRaces and selects nextRace from myUpcomingRaces', async () => {
|
||||||
@@ -226,6 +227,7 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
clearRaceRegistrations: async (): Promise<void> => {
|
clearRaceRegistrations: async (): Promise<void> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
},
|
},
|
||||||
|
findByRaceId: async (): Promise<any[]> => [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const feedRepository = {
|
const feedRepository = {
|
||||||
@@ -253,6 +255,14 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// Mock output port to capture presented data
|
||||||
|
let _presentedData: DashboardOverviewResult | null = null;
|
||||||
|
const outputPort: UseCaseOutputPort<DashboardOverviewResult> = {
|
||||||
|
present: (data: DashboardOverviewResult) => {
|
||||||
|
_presentedData = data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const useCase = new DashboardOverviewUseCase(
|
const useCase = new DashboardOverviewUseCase(
|
||||||
driverRepository,
|
driverRepository,
|
||||||
raceRepository,
|
raceRepository,
|
||||||
@@ -265,21 +275,23 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
socialRepository,
|
socialRepository,
|
||||||
getDriverAvatar,
|
getDriverAvatar,
|
||||||
getDriverStats,
|
getDriverStats,
|
||||||
|
outputPort,
|
||||||
);
|
);
|
||||||
|
|
||||||
const input: DashboardOverviewInput = { driverId };
|
const input: DashboardOverviewInput = { driverId };
|
||||||
|
|
||||||
const result: UseCaseResult<
|
const result: UseCaseResult<
|
||||||
DashboardOverviewResult,
|
void,
|
||||||
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
||||||
> = await useCase.execute(input);
|
> = await useCase.execute(input);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
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).not.toBeNull();
|
||||||
expect(vm.nextRace!.race.id).toBe('race-1');
|
expect(vm.nextRace!.race.id).toBe('race-1');
|
||||||
@@ -465,7 +477,7 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
getMembership: async (leagueId: string, driverIdParam: string): Promise<LeagueMembership | null> => {
|
getMembership: async (leagueId: string, driverIdParam: string): Promise<LeagueMembership | null> => {
|
||||||
return (
|
return (
|
||||||
memberships.find(
|
memberships.find(
|
||||||
m => m.leagueId === leagueId && m.driverId === driverIdParam,
|
m => m.leagueId.toString() === leagueId && m.driverId.toString() === driverIdParam,
|
||||||
) ?? null
|
) ?? null
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -499,6 +511,7 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
clearRaceRegistrations: async (): Promise<void> => {
|
clearRaceRegistrations: async (): Promise<void> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
},
|
},
|
||||||
|
findByRaceId: async (): Promise<any[]> => [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const feedRepository = {
|
const feedRepository = {
|
||||||
@@ -526,6 +539,14 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// Mock output port to capture presented data
|
||||||
|
let _presentedData: DashboardOverviewResult | null = null;
|
||||||
|
const outputPort: UseCaseOutputPort<DashboardOverviewResult> = {
|
||||||
|
present: (data: DashboardOverviewResult) => {
|
||||||
|
_presentedData = data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const useCase = new DashboardOverviewUseCase(
|
const useCase = new DashboardOverviewUseCase(
|
||||||
driverRepository,
|
driverRepository,
|
||||||
raceRepository,
|
raceRepository,
|
||||||
@@ -538,37 +559,39 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
socialRepository,
|
socialRepository,
|
||||||
getDriverAvatar,
|
getDriverAvatar,
|
||||||
getDriverStats,
|
getDriverStats,
|
||||||
|
outputPort,
|
||||||
);
|
);
|
||||||
|
|
||||||
const input: DashboardOverviewInput = { driverId };
|
const input: DashboardOverviewInput = { driverId };
|
||||||
|
|
||||||
const result: UseCaseResult<
|
const result: UseCaseResult<
|
||||||
DashboardOverviewResult,
|
void,
|
||||||
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
||||||
> = await useCase.execute(input);
|
> = await useCase.execute(input);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
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.length).toBe(2);
|
||||||
expect(vm.recentResults[0]!.race.id).toBe('race-new');
|
expect(vm.recentResults[0]!.race.id).toBe('race-new');
|
||||||
expect(vm.recentResults[1]!.race.id).toBe('race-old');
|
expect(vm.recentResults[1]!.race.id).toBe('race-old');
|
||||||
|
|
||||||
const summariesByLeague = new Map(
|
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 summaryA = summariesByLeague.get('league-A');
|
||||||
const summaryB = summariesByLeague.get('league-B');
|
const summaryB = summariesByLeague.get('league-B');
|
||||||
|
|
||||||
expect(summaryA).toBeDefined();
|
expect(summaryA).toBeDefined();
|
||||||
expect(summaryA!.standing?.position).toBe(3);
|
expect(summaryA!.standing?.position.toNumber()).toBe(3);
|
||||||
expect(summaryA!.standing?.points).toBe(50);
|
expect(summaryA!.standing?.points.toNumber()).toBe(50);
|
||||||
expect(summaryA!.totalDrivers).toBe(2);
|
expect(summaryA!.totalDrivers).toBe(2);
|
||||||
|
|
||||||
expect(summaryB).toBeDefined();
|
expect(summaryB).toBeDefined();
|
||||||
expect(summaryB!.standing?.position).toBe(1);
|
expect(summaryB!.standing?.position.toNumber()).toBe(1);
|
||||||
expect(summaryB!.standing?.points).toBe(100);
|
expect(summaryB!.standing?.points.toNumber()).toBe(100);
|
||||||
expect(summaryB!.totalDrivers).toBe(2);
|
expect(summaryB!.totalDrivers).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -708,6 +731,7 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
clearRaceRegistrations: async (): Promise<void> => {
|
clearRaceRegistrations: async (): Promise<void> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
},
|
},
|
||||||
|
findByRaceId: async (): Promise<any[]> => [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const feedRepository = {
|
const feedRepository = {
|
||||||
@@ -725,6 +749,14 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
|
|
||||||
const getDriverStats = () => null;
|
const getDriverStats = () => null;
|
||||||
|
|
||||||
|
// Mock output port to capture presented data
|
||||||
|
let _presentedData: DashboardOverviewResult | null = null;
|
||||||
|
const outputPort: UseCaseOutputPort<DashboardOverviewResult> = {
|
||||||
|
present: (data: DashboardOverviewResult) => {
|
||||||
|
_presentedData = data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const useCase = new DashboardOverviewUseCase(
|
const useCase = new DashboardOverviewUseCase(
|
||||||
driverRepository,
|
driverRepository,
|
||||||
raceRepository,
|
raceRepository,
|
||||||
@@ -737,17 +769,19 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
socialRepository,
|
socialRepository,
|
||||||
getDriverAvatar,
|
getDriverAvatar,
|
||||||
getDriverStats,
|
getDriverStats,
|
||||||
|
outputPort,
|
||||||
);
|
);
|
||||||
|
|
||||||
const input: DashboardOverviewInput = { driverId };
|
const input: DashboardOverviewInput = { driverId };
|
||||||
|
|
||||||
const result: UseCaseResult<
|
const result: UseCaseResult<
|
||||||
DashboardOverviewResult,
|
void,
|
||||||
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
||||||
> = await useCase.execute(input);
|
> = await useCase.execute(input);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
const vm = result.unwrap();
|
expect(_presentedData).not.toBeNull();
|
||||||
|
const vm = _presentedData!;
|
||||||
|
|
||||||
expect(vm.myUpcomingRaces).toEqual([]);
|
expect(vm.myUpcomingRaces).toEqual([]);
|
||||||
expect(vm.otherUpcomingRaces).toEqual([]);
|
expect(vm.otherUpcomingRaces).toEqual([]);
|
||||||
@@ -893,6 +927,7 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
clearRaceRegistrations: async (): Promise<void> => {
|
clearRaceRegistrations: async (): Promise<void> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
},
|
},
|
||||||
|
findByRaceId: async (): Promise<any[]> => [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const feedRepository = {
|
const feedRepository = {
|
||||||
@@ -910,6 +945,13 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
|
|
||||||
const getDriverStats = () => null;
|
const getDriverStats = () => null;
|
||||||
|
|
||||||
|
// Mock output port to capture presented data
|
||||||
|
const outputPort: UseCaseOutputPort<DashboardOverviewResult> = {
|
||||||
|
present: (_data: DashboardOverviewResult) => {
|
||||||
|
// No-op
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const useCase = new DashboardOverviewUseCase(
|
const useCase = new DashboardOverviewUseCase(
|
||||||
driverRepository,
|
driverRepository,
|
||||||
raceRepository,
|
raceRepository,
|
||||||
@@ -922,12 +964,13 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
socialRepository,
|
socialRepository,
|
||||||
getDriverAvatar,
|
getDriverAvatar,
|
||||||
getDriverStats,
|
getDriverStats,
|
||||||
|
outputPort,
|
||||||
);
|
);
|
||||||
|
|
||||||
const input: DashboardOverviewInput = { driverId };
|
const input: DashboardOverviewInput = { driverId };
|
||||||
|
|
||||||
const result: UseCaseResult<
|
const result: UseCaseResult<
|
||||||
DashboardOverviewResult,
|
void,
|
||||||
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
||||||
> = await useCase.execute(input);
|
> = await useCase.execute(input);
|
||||||
|
|
||||||
@@ -1075,6 +1118,7 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
clearRaceRegistrations: async (): Promise<void> => {
|
clearRaceRegistrations: async (): Promise<void> => {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
},
|
},
|
||||||
|
findByRaceId: async (): Promise<any[]> => [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const feedRepository = {
|
const feedRepository = {
|
||||||
@@ -1092,6 +1136,13 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
|
|
||||||
const getDriverStats = () => null;
|
const getDriverStats = () => null;
|
||||||
|
|
||||||
|
// Mock output port to capture presented data
|
||||||
|
const outputPort: UseCaseOutputPort<DashboardOverviewResult> = {
|
||||||
|
present: (_data: DashboardOverviewResult) => {
|
||||||
|
// No-op
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const useCase = new DashboardOverviewUseCase(
|
const useCase = new DashboardOverviewUseCase(
|
||||||
driverRepository,
|
driverRepository,
|
||||||
raceRepository,
|
raceRepository,
|
||||||
@@ -1104,12 +1155,13 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
socialRepository,
|
socialRepository,
|
||||||
getDriverAvatar,
|
getDriverAvatar,
|
||||||
getDriverStats,
|
getDriverStats,
|
||||||
|
outputPort,
|
||||||
);
|
);
|
||||||
|
|
||||||
const input: DashboardOverviewInput = { driverId };
|
const input: DashboardOverviewInput = { driverId };
|
||||||
|
|
||||||
const result: UseCaseResult<
|
const result: UseCaseResult<
|
||||||
DashboardOverviewResult,
|
void,
|
||||||
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
||||||
> = await useCase.execute(input);
|
> = await useCase.execute(input);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
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 { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository';
|
||||||
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
||||||
import type { FeedItem } from '@core/social/domain/types/FeedItem';
|
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 { IRaceRepository } from '../../domain/repositories/IRaceRepository';
|
||||||
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
||||||
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
|
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
|
||||||
|
import type { Result as RaceResult } from '../../domain/entities/result/Result';
|
||||||
|
|
||||||
export interface DashboardOverviewInput {
|
export interface DashboardOverviewInput {
|
||||||
driverId: string;
|
driverId: string;
|
||||||
@@ -48,7 +50,7 @@ export interface DashboardRaceSummary {
|
|||||||
export interface DashboardRecentRaceResultSummary {
|
export interface DashboardRecentRaceResultSummary {
|
||||||
race: Race;
|
race: Race;
|
||||||
league: League | null;
|
league: League | null;
|
||||||
result: Result;
|
result: RaceResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardLeagueStandingSummary {
|
export interface DashboardLeagueStandingSummary {
|
||||||
@@ -95,13 +97,14 @@ export class DashboardOverviewUseCase {
|
|||||||
private readonly getDriverStats: (
|
private readonly getDriverStats: (
|
||||||
driverId: string,
|
driverId: string,
|
||||||
) => DashboardDriverStatsAdapter | null,
|
) => DashboardDriverStatsAdapter | null,
|
||||||
|
private readonly output: UseCaseOutputPort<DashboardOverviewResult>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(
|
async execute(
|
||||||
input: DashboardOverviewInput,
|
input: DashboardOverviewInput,
|
||||||
): Promise<
|
): Promise<
|
||||||
Result<
|
Result<
|
||||||
DashboardOverviewResult,
|
void,
|
||||||
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
||||||
>
|
>
|
||||||
> {
|
> {
|
||||||
@@ -206,7 +209,9 @@ export class DashboardOverviewUseCase {
|
|||||||
friends: friendsSummary,
|
friends: friendsSummary,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Result.ok(result);
|
this.output.present(result);
|
||||||
|
|
||||||
|
return Result.ok(undefined);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Result.err({
|
return Result.err({
|
||||||
code: 'REPOSITORY_ERROR',
|
code: 'REPOSITORY_ERROR',
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ describe('FileProtestUseCase', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.raceId).toBe('race1');
|
||||||
expect(presented.protest.protestingDriverId).toBe('driver1');
|
expect(presented.protest.protestingDriverId).toBe('driver1');
|
||||||
expect(presented.protest.accusedDriverId).toBe('driver2');
|
expect(presented.protest.accusedDriverId).toBe('driver2');
|
||||||
|
|||||||
@@ -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 {
|
import {
|
||||||
GetAllLeaguesWithCapacityAndScoringUseCase,
|
GetAllLeaguesWithCapacityAndScoringUseCase,
|
||||||
type GetAllLeaguesWithCapacityAndScoringInput,
|
type GetAllLeaguesWithCapacityAndScoringInput,
|
||||||
type GetAllLeaguesWithCapacityAndScoringResult,
|
type GetAllLeaguesWithCapacityAndScoringResult,
|
||||||
type LeagueCapacityAndScoringSummary,
|
|
||||||
} from './GetAllLeaguesWithCapacityAndScoringUseCase';
|
} 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', () => {
|
describe('GetAllLeaguesWithCapacityAndScoringUseCase', () => {
|
||||||
let mockLeagueRepo: { findAll: Mock };
|
let mockLeagueRepo: { findAll: Mock };
|
||||||
@@ -63,18 +62,18 @@ describe('GetAllLeaguesWithCapacityAndScoringUseCase', () => {
|
|||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
expect(output.present).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
const presented =
|
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?.league).toEqual(league);
|
||||||
expect(summary.currentDrivers).toBe(2);
|
expect(summary?.currentDrivers).toBe(2);
|
||||||
expect(summary.maxDrivers).toBe(30);
|
expect(summary?.maxDrivers).toBe(30);
|
||||||
expect(summary.season).toEqual(season);
|
expect(summary?.season).toEqual(season);
|
||||||
expect(summary.scoringConfig).toEqual(scoringConfig);
|
expect(summary?.scoringConfig).toEqual(scoringConfig);
|
||||||
expect(summary.game).toEqual(game);
|
expect(summary?.game).toEqual(game);
|
||||||
expect(summary.preset).toEqual({ id: 'preset1', name: 'Default' });
|
expect(summary?.preset).toEqual({ id: 'preset1', name: 'Default' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { League } from '../../domain/entities/League';
|
|||||||
import type { Season } from '../../domain/entities/season/Season';
|
import type { Season } from '../../domain/entities/season/Season';
|
||||||
import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig';
|
import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig';
|
||||||
import type { Game } from '../../domain/entities/Game';
|
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 { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
@@ -62,18 +62,18 @@ export class GetAllLeaguesWithCapacityAndScoringUseCase {
|
|||||||
const enrichedLeagues: LeagueCapacityAndScoringSummary[] = [];
|
const enrichedLeagues: LeagueCapacityAndScoringSummary[] = [];
|
||||||
|
|
||||||
for (const league of leagues) {
|
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(
|
const currentDrivers = members.filter(
|
||||||
(m) =>
|
(m) =>
|
||||||
m.status === 'active' &&
|
m.status.toString() === 'active' &&
|
||||||
(m.role === 'owner' ||
|
(m.role.toString() === 'owner' ||
|
||||||
m.role === 'admin' ||
|
m.role.toString() === 'admin' ||
|
||||||
m.role === 'steward' ||
|
m.role.toString() === 'steward' ||
|
||||||
m.role === 'member'),
|
m.role.toString() === 'member'),
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const seasons = await this.seasonRepository.findByLeagueId(league.id);
|
const seasons = await this.seasonRepository.findByLeagueId(league.id.toString());
|
||||||
const activeSeason =
|
const activeSeason =
|
||||||
seasons && seasons.length > 0
|
seasons && seasons.length > 0
|
||||||
? seasons.find((s) => s.status === 'active') ?? seasons[0]
|
? seasons.find((s) => s.status === 'active') ?? seasons[0]
|
||||||
@@ -85,14 +85,14 @@ export class GetAllLeaguesWithCapacityAndScoringUseCase {
|
|||||||
|
|
||||||
if (activeSeason) {
|
if (activeSeason) {
|
||||||
const scoringConfigResult =
|
const scoringConfigResult =
|
||||||
await this.leagueScoringConfigRepository.findBySeasonId(activeSeason.id);
|
await this.leagueScoringConfigRepository.findBySeasonId(activeSeason.id.toString());
|
||||||
scoringConfig = scoringConfigResult ?? undefined;
|
scoringConfig = scoringConfigResult ?? undefined;
|
||||||
if (scoringConfig) {
|
if (scoringConfig) {
|
||||||
const gameResult = await this.gameRepository.findById(activeSeason.gameId);
|
const gameResult = await this.gameRepository.findById(activeSeason.gameId.toString());
|
||||||
game = gameResult ?? undefined;
|
game = gameResult ?? undefined;
|
||||||
const presetId = scoringConfig.scoringPresetId;
|
const presetId = scoringConfig.scoringPresetId;
|
||||||
if (presetId) {
|
if (presetId) {
|
||||||
preset = this.presetProvider.getPresetById(presetId);
|
preset = this.presetProvider.getPresetById(presetId.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
import {
|
||||||
GetAllLeaguesWithCapacityUseCase,
|
GetAllLeaguesWithCapacityUseCase,
|
||||||
type GetAllLeaguesWithCapacityInput,
|
type GetAllLeaguesWithCapacityInput,
|
||||||
type GetAllLeaguesWithCapacityResult,
|
type GetAllLeaguesWithCapacityResult,
|
||||||
type LeagueCapacitySummary,
|
|
||||||
} from './GetAllLeaguesWithCapacityUseCase';
|
} 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', () => {
|
describe('GetAllLeaguesWithCapacityUseCase', () => {
|
||||||
let mockLeagueRepo: { findAll: Mock };
|
let mockLeagueRepo: { findAll: Mock };
|
||||||
@@ -24,7 +23,6 @@ describe('GetAllLeaguesWithCapacityUseCase', () => {
|
|||||||
const useCase = new GetAllLeaguesWithCapacityUseCase(
|
const useCase = new GetAllLeaguesWithCapacityUseCase(
|
||||||
mockLeagueRepo as unknown as ILeagueRepository,
|
mockLeagueRepo as unknown as ILeagueRepository,
|
||||||
mockMembershipRepo as unknown as ILeagueMembershipRepository,
|
mockMembershipRepo as unknown as ILeagueMembershipRepository,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const league1 = { id: 'league1', name: 'Test League 1', settings: { maxDrivers: 10 } };
|
const league1 = { id: 'league1', name: 'Test League 1', settings: { maxDrivers: 10 } };
|
||||||
@@ -48,27 +46,26 @@ describe('GetAllLeaguesWithCapacityUseCase', () => {
|
|||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(result.unwrap()).toBeUndefined();
|
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(resultValue?.leagues).toHaveLength(2);
|
||||||
expect(presented.leagues).toHaveLength(2);
|
|
||||||
|
|
||||||
const [first, second] = presented.leagues as LeagueCapacitySummary[];
|
const [first, second] = resultValue?.leagues ?? [];
|
||||||
|
|
||||||
expect(first.league).toEqual(league1);
|
expect(first?.league).toEqual(league1);
|
||||||
expect(first.currentDrivers).toBe(2);
|
expect(first?.currentDrivers).toBe(2);
|
||||||
expect(first.maxDrivers).toBe(10);
|
expect(first?.maxDrivers).toBe(10);
|
||||||
|
|
||||||
expect(second.league).toEqual(league2);
|
expect(second?.league).toEqual(league2);
|
||||||
expect(second.currentDrivers).toBe(1);
|
expect(second?.currentDrivers).toBe(1);
|
||||||
expect(second.maxDrivers).toBe(20);
|
expect(second?.maxDrivers).toBe(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return empty result when no leagues', async () => {
|
it('should return empty result when no leagues', async () => {
|
||||||
const useCase = new GetAllLeaguesWithCapacityUseCase(
|
const useCase = new GetAllLeaguesWithCapacityUseCase(
|
||||||
mockLeagueRepo as unknown as ILeagueRepository,
|
mockLeagueRepo as unknown as ILeagueRepository,
|
||||||
mockMembershipRepo as unknown as ILeagueMembershipRepository,
|
mockMembershipRepo as unknown as ILeagueMembershipRepository,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
mockLeagueRepo.findAll.mockResolvedValue([]);
|
mockLeagueRepo.findAll.mockResolvedValue([]);
|
||||||
@@ -78,8 +75,8 @@ describe('GetAllLeaguesWithCapacityUseCase', () => {
|
|||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
const resultValue = result.unwrap();
|
||||||
const presented = output.present.mock.calls[0][0] as GetAllLeaguesWithCapacityResult;
|
expect(resultValue).toBeDefined();
|
||||||
expect(presented.leagues).toEqual([]);
|
expect(resultValue?.leagues).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -3,7 +3,6 @@ import type { ILeagueMembershipRepository } from '../../domain/repositories/ILea
|
|||||||
import type { League } from '../../domain/entities/League';
|
import type { League } from '../../domain/entities/League';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
||||||
|
|
||||||
export type GetAllLeaguesWithCapacityInput = {};
|
export type GetAllLeaguesWithCapacityInput = {};
|
||||||
|
|
||||||
@@ -27,7 +26,6 @@ export class GetAllLeaguesWithCapacityUseCase {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly leagueRepository: ILeagueRepository,
|
private readonly leagueRepository: ILeagueRepository,
|
||||||
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
|
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
|
||||||
private readonly outputPort: UseCaseOutputPort<GetAllLeaguesWithCapacityResult, GetAllLeaguesWithCapacityErrorCode>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(
|
async execute(
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ describe('GetAllRacesPageDataUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.races).toEqual([
|
||||||
{
|
{
|
||||||
@@ -150,7 +150,7 @@ describe('GetAllRacesPageDataUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.races).toEqual([]);
|
||||||
expect(presented.filters).toEqual({
|
expect(presented.filters).toEqual({
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ describe('GetAllRacesUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.totalCount).toBe(2);
|
||||||
expect(presented.races).toEqual([race1, race2]);
|
expect(presented.races).toEqual([race1, race2]);
|
||||||
expect(presented.leagues).toEqual([league1, league2]);
|
expect(presented.leagues).toEqual([league1, league2]);
|
||||||
@@ -112,7 +112,7 @@ describe('GetAllRacesUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.totalCount).toBe(0);
|
||||||
expect(presented.races).toEqual([]);
|
expect(presented.races).toEqual([]);
|
||||||
expect(presented.leagues).toEqual([]);
|
expect(presented.leagues).toEqual([]);
|
||||||
@@ -123,7 +123,6 @@ describe('GetAllRacesUseCase', () => {
|
|||||||
mockRaceRepo,
|
mockRaceRepo,
|
||||||
mockLeagueRepo,
|
mockLeagueRepo,
|
||||||
mockLogger,
|
mockLogger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const error = new Error('Repository error');
|
const error = new Error('Repository error');
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ describe('GetAllTeamsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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({
|
expect(presented).toEqual({
|
||||||
teams: [
|
teams: [
|
||||||
@@ -127,7 +127,7 @@ describe('GetAllTeamsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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({
|
expect(presented).toEqual({
|
||||||
teams: [],
|
teams: [],
|
||||||
|
|||||||
@@ -3,19 +3,19 @@ import {
|
|||||||
GetDriversLeaderboardUseCase,
|
GetDriversLeaderboardUseCase,
|
||||||
type GetDriversLeaderboardResult,
|
type GetDriversLeaderboardResult,
|
||||||
type GetDriversLeaderboardInput,
|
type GetDriversLeaderboardInput,
|
||||||
type GetDriversLeaderboardErrorCode,
|
|
||||||
} from './GetDriversLeaderboardUseCase';
|
} from './GetDriversLeaderboardUseCase';
|
||||||
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
||||||
import type { IRankingService } from '../../domain/services/IRankingService';
|
import type { IRankingService } from '../../domain/services/IRankingService';
|
||||||
import type { IDriverStatsService } from '../../domain/services/IDriverStatsService';
|
import type { IDriverStatsService } from '../../domain/services/IDriverStatsService';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
|
|
||||||
describe('GetDriversLeaderboardUseCase', () => {
|
describe('GetDriversLeaderboardUseCase', () => {
|
||||||
const mockDriverFindAll = vi.fn();
|
const mockDriverFindAll = vi.fn();
|
||||||
const mockDriverRepo: IDriverRepository = {
|
const mockDriverRepo: IDriverRepository = {
|
||||||
findById: vi.fn(),
|
findById: vi.fn(),
|
||||||
|
findByIRacingId: vi.fn(),
|
||||||
|
existsByIRacingId: vi.fn(),
|
||||||
findAll: mockDriverFindAll,
|
findAll: mockDriverFindAll,
|
||||||
create: vi.fn(),
|
create: vi.fn(),
|
||||||
update: vi.fn(),
|
update: vi.fn(),
|
||||||
@@ -57,7 +57,6 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
mockDriverStatsService,
|
mockDriverStatsService,
|
||||||
mockGetDriverAvatar,
|
mockGetDriverAvatar,
|
||||||
mockLogger,
|
mockLogger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
|
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
|
||||||
@@ -99,7 +98,7 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
rating: 2500,
|
rating: 2500,
|
||||||
skillLevel: 'advanced',
|
skillLevel: 'advanced',
|
||||||
racesCompleted: 10,
|
racesCompleted: 10,
|
||||||
wins: 5,
|
wins:5,
|
||||||
podiums: 7,
|
podiums: 7,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
rank: 1,
|
rank: 1,
|
||||||
@@ -130,7 +129,6 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
mockDriverStatsService,
|
mockDriverStatsService,
|
||||||
mockGetDriverAvatar,
|
mockGetDriverAvatar,
|
||||||
mockLogger,
|
mockLogger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
mockDriverFindAll.mockResolvedValue([]);
|
mockDriverFindAll.mockResolvedValue([]);
|
||||||
@@ -161,7 +159,6 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
mockDriverStatsService,
|
mockDriverStatsService,
|
||||||
mockGetDriverAvatar,
|
mockGetDriverAvatar,
|
||||||
mockLogger,
|
mockLogger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
|
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
|
||||||
@@ -209,7 +206,6 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
mockDriverStatsService,
|
mockDriverStatsService,
|
||||||
mockGetDriverAvatar,
|
mockGetDriverAvatar,
|
||||||
mockLogger,
|
mockLogger,
|
||||||
output,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const error = new Error('Repository error');
|
const error = new Error('Repository error');
|
||||||
|
|||||||
@@ -5,16 +5,12 @@ import {
|
|||||||
type GetEntitySponsorshipPricingResult,
|
type GetEntitySponsorshipPricingResult,
|
||||||
} from './GetEntitySponsorshipPricingUseCase';
|
} from './GetEntitySponsorshipPricingUseCase';
|
||||||
import type { ISponsorshipPricingRepository } from '../../domain/repositories/ISponsorshipPricingRepository';
|
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 { Logger } from '@core/shared/application';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
|
|
||||||
describe('GetEntitySponsorshipPricingUseCase', () => {
|
describe('GetEntitySponsorshipPricingUseCase', () => {
|
||||||
let mockSponsorshipPricingRepo: ISponsorshipPricingRepository;
|
let mockSponsorshipPricingRepo: ISponsorshipPricingRepository;
|
||||||
let mockSponsorshipRequestRepo: ISponsorshipRequestRepository;
|
|
||||||
let mockSeasonSponsorshipRepo: ISeasonSponsorshipRepository;
|
|
||||||
let mockLogger: Logger;
|
let mockLogger: Logger;
|
||||||
let mockFindByEntity: Mock;
|
let mockFindByEntity: Mock;
|
||||||
let mockFindPendingByEntity: Mock;
|
let mockFindPendingByEntity: Mock;
|
||||||
@@ -37,37 +33,6 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
|
|||||||
exists: vi.fn(),
|
exists: vi.fn(),
|
||||||
findAcceptingApplications: vi.fn(),
|
findAcceptingApplications: vi.fn(),
|
||||||
} as ISponsorshipPricingRepository;
|
} 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 = {
|
mockLogger = {
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
@@ -80,8 +45,6 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
|
|||||||
it('should return PRICING_NOT_CONFIGURED when no pricing found', async () => {
|
it('should return PRICING_NOT_CONFIGURED when no pricing found', async () => {
|
||||||
const useCase = new GetEntitySponsorshipPricingUseCase(
|
const useCase = new GetEntitySponsorshipPricingUseCase(
|
||||||
mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository,
|
mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository,
|
||||||
mockSponsorshipRequestRepo as unknown as ISponsorshipRequestRepository,
|
|
||||||
mockSeasonSponsorshipRepo as unknown as ISeasonSponsorshipRepository,
|
|
||||||
mockLogger,
|
mockLogger,
|
||||||
output,
|
output,
|
||||||
);
|
);
|
||||||
@@ -108,8 +71,6 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
|
|||||||
it('should return pricing data when found', async () => {
|
it('should return pricing data when found', async () => {
|
||||||
const useCase = new GetEntitySponsorshipPricingUseCase(
|
const useCase = new GetEntitySponsorshipPricingUseCase(
|
||||||
mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository,
|
mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository,
|
||||||
mockSponsorshipRequestRepo as unknown as ISponsorshipRequestRepository,
|
|
||||||
mockSeasonSponsorshipRepo as unknown as ISeasonSponsorshipRepository,
|
|
||||||
mockLogger,
|
mockLogger,
|
||||||
output,
|
output,
|
||||||
);
|
);
|
||||||
@@ -118,6 +79,7 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
|
|||||||
entityType: 'season',
|
entityType: 'season',
|
||||||
entityId: 'season1',
|
entityId: 'season1',
|
||||||
};
|
};
|
||||||
|
|
||||||
const pricing = {
|
const pricing = {
|
||||||
acceptingApplications: true,
|
acceptingApplications: true,
|
||||||
customRequirements: 'Some requirements',
|
customRequirements: 'Some requirements',
|
||||||
@@ -145,7 +107,7 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.entityType).toBe('season');
|
||||||
expect(presented.entityId).toBe('season1');
|
expect(presented.entityId).toBe('season1');
|
||||||
@@ -170,8 +132,6 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
|
|||||||
it('should return error when repository throws', async () => {
|
it('should return error when repository throws', async () => {
|
||||||
const useCase = new GetEntitySponsorshipPricingUseCase(
|
const useCase = new GetEntitySponsorshipPricingUseCase(
|
||||||
mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository,
|
mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository,
|
||||||
mockSponsorshipRequestRepo as unknown as ISponsorshipRequestRepository,
|
|
||||||
mockSeasonSponsorshipRepo as unknown as ISeasonSponsorshipRepository,
|
|
||||||
mockLogger,
|
mockLogger,
|
||||||
output,
|
output,
|
||||||
);
|
);
|
||||||
@@ -180,6 +140,7 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
|
|||||||
entityType: 'season',
|
entityType: 'season',
|
||||||
entityId: 'season1',
|
entityId: 'season1',
|
||||||
};
|
};
|
||||||
|
|
||||||
const error = new Error('Repository error');
|
const error = new Error('Repository error');
|
||||||
|
|
||||||
mockFindByEntity.mockRejectedValue(error);
|
mockFindByEntity.mockRejectedValue(error);
|
||||||
@@ -195,4 +156,4 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
|
|||||||
expect(err.details.message).toBe('Repository error');
|
expect(err.details.message).toBe('Repository error');
|
||||||
expect(output.present).not.toHaveBeenCalled();
|
expect(output.present).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ISponsorshipPricingRepository } from '../../domain/repositories/ISponsorshipPricingRepository';
|
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 { Logger } from '@core/shared/application';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
@@ -47,8 +44,6 @@ export type GetEntitySponsorshipPricingErrorCode =
|
|||||||
export class GetEntitySponsorshipPricingUseCase {
|
export class GetEntitySponsorshipPricingUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly sponsorshipPricingRepo: ISponsorshipPricingRepository,
|
private readonly sponsorshipPricingRepo: ISponsorshipPricingRepository,
|
||||||
private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
|
||||||
private readonly seasonSponsorshipRepo: ISeasonSponsorshipRepository,
|
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly output: UseCaseOutputPort<GetEntitySponsorshipPricingResult>,
|
private readonly output: UseCaseOutputPort<GetEntitySponsorshipPricingResult>,
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.league).toBe(league);
|
||||||
expect(presented.permissions).toEqual({
|
expect(presented.permissions).toEqual({
|
||||||
canManageSchedule: true,
|
canManageSchedule: true,
|
||||||
@@ -155,7 +155,7 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.league).toBe(league);
|
||||||
expect(presented.permissions).toEqual({
|
expect(presented.permissions).toEqual({
|
||||||
canManageSchedule: true,
|
canManageSchedule: true,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export class GetLeagueAdminPermissionsUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const membership = await this.leagueMembershipRepository.getMembership(leagueId, performerDriverId);
|
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', {
|
this.logger.warn('User is not a member or not authorized for league admin permissions', {
|
||||||
leagueId,
|
leagueId,
|
||||||
performerDriverId,
|
performerDriverId,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ describe('GetLeagueAdminUseCase', () => {
|
|||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.id).toBe('league1');
|
||||||
expect(presented.league.ownerId).toBe('owner1');
|
expect(presented.league.ownerId).toBe('owner1');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,10 +55,29 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
|
|||||||
recalculate: vi.fn(),
|
recalculate: vi.fn(),
|
||||||
};
|
};
|
||||||
resultRepository = {
|
resultRepository = {
|
||||||
|
findById: vi.fn(),
|
||||||
|
findAll: vi.fn(),
|
||||||
|
findByRaceId: vi.fn(),
|
||||||
|
findByDriverId: vi.fn(),
|
||||||
findByDriverIdAndLeagueId: mockResultFindByDriverIdAndLeagueId,
|
findByDriverIdAndLeagueId: mockResultFindByDriverIdAndLeagueId,
|
||||||
|
create: vi.fn(),
|
||||||
|
createMany: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
deleteByRaceId: vi.fn(),
|
||||||
|
exists: vi.fn(),
|
||||||
|
existsByRaceId: vi.fn(),
|
||||||
};
|
};
|
||||||
penaltyRepository = {
|
penaltyRepository = {
|
||||||
|
findById: vi.fn(),
|
||||||
|
findByDriverId: vi.fn(),
|
||||||
|
findByProtestId: vi.fn(),
|
||||||
|
findPending: vi.fn(),
|
||||||
findByRaceId: mockPenaltyFindByRaceId,
|
findByRaceId: mockPenaltyFindByRaceId,
|
||||||
|
findIssuedBy: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
exists: vi.fn(),
|
||||||
};
|
};
|
||||||
raceRepository = {
|
raceRepository = {
|
||||||
findById: vi.fn(),
|
findById: vi.fn(),
|
||||||
@@ -75,12 +94,27 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
|
|||||||
};
|
};
|
||||||
driverRepository = {
|
driverRepository = {
|
||||||
findById: mockDriverFindById,
|
findById: mockDriverFindById,
|
||||||
|
findByIRacingId: vi.fn(),
|
||||||
|
findAll: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
exists: vi.fn(),
|
||||||
|
existsByIRacingId: vi.fn(),
|
||||||
};
|
};
|
||||||
teamRepository = {
|
teamRepository = {
|
||||||
findById: mockTeamFindById,
|
findById: mockTeamFindById,
|
||||||
|
findAll: vi.fn(),
|
||||||
|
findByLeagueId: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
exists: vi.fn(),
|
||||||
};
|
};
|
||||||
driverRatingPort = {
|
driverRatingPort = {
|
||||||
getRating: mockDriverRatingGetRating,
|
getDriverRating: mockDriverRatingGetRating,
|
||||||
|
calculateRatingChange: vi.fn(),
|
||||||
|
updateDriverRating: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
output = {
|
output = {
|
||||||
@@ -138,7 +172,7 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.leagueId).toBe('league-1');
|
||||||
expect(presented.stats).toHaveLength(2);
|
expect(presented.stats).toHaveLength(2);
|
||||||
expect(presented.stats[0]).toEqual({
|
expect(presented.stats[0]).toEqual({
|
||||||
@@ -188,8 +222,8 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.stats[0].penaltyPoints).toBe(0);
|
expect(presented?.stats[0]?.penaltyPoints).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return LEAGUE_NOT_FOUND when no standings are found', async () => {
|
it('should return LEAGUE_NOT_FOUND when no standings are found', async () => {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ describe('GetLeagueJoinRequestsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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).toHaveLength(1);
|
||||||
expect(presented.joinRequests[0]).toMatchObject({
|
expect(presented.joinRequests[0]).toMatchObject({
|
||||||
@@ -81,7 +81,7 @@ describe('GetLeagueJoinRequestsUseCase', () => {
|
|||||||
driverId: 'driver-1',
|
driverId: 'driver-1',
|
||||||
message: 'msg',
|
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 () => {
|
it('should return LEAGUE_NOT_FOUND error when league does not exist', async () => {
|
||||||
|
|||||||
@@ -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 type { UseCaseOutputPort } from '@core/shared/application';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
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 {
|
export interface GetLeagueJoinRequestsInput {
|
||||||
leagueId: string;
|
leagueId: string;
|
||||||
@@ -47,7 +47,7 @@ export class GetLeagueJoinRequestsUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const joinRequests = await this.leagueMembershipRepository.getJoinRequests(input.leagueId);
|
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 drivers = await Promise.all(driverIds.map(id => this.driverRepository.findById(id)));
|
||||||
|
|
||||||
const driverMap = new Map(
|
const driverMap = new Map(
|
||||||
@@ -55,10 +55,14 @@ export class GetLeagueJoinRequestsUseCase {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const enrichedJoinRequests: LeagueJoinRequestWithDriver[] = joinRequests
|
const enrichedJoinRequests: LeagueJoinRequestWithDriver[] = joinRequests
|
||||||
.filter(request => driverMap.has(request.driverId))
|
.filter(request => driverMap.has(request.driverId.toString()))
|
||||||
.map(request => ({
|
.map(request => ({
|
||||||
...request,
|
id: request.id,
|
||||||
driver: driverMap.get(request.driverId)!,
|
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 = {
|
const result: GetLeagueJoinRequestsResult = {
|
||||||
@@ -71,7 +75,7 @@ export class GetLeagueJoinRequestsUseCase {
|
|||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const message =
|
const message =
|
||||||
error && typeof error === 'object' && 'message' in error && typeof (error as any).message === 'string'
|
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';
|
: 'Failed to load league join requests';
|
||||||
|
|
||||||
return Result.err({
|
return Result.err({
|
||||||
|
|||||||
@@ -99,14 +99,14 @@ describe('GetLeagueMembershipsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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?.league).toEqual(league);
|
||||||
expect(presented.memberships).toHaveLength(2);
|
expect(presented?.memberships).toHaveLength(2);
|
||||||
expect(presented.memberships[0].membership).toEqual(memberships[0]);
|
expect(presented?.memberships[0]?.membership).toEqual(memberships[0]);
|
||||||
expect(presented.memberships[0].driver).toEqual(driver1);
|
expect(presented?.memberships[0]?.driver).toEqual(driver1);
|
||||||
expect(presented.memberships[1].membership).toEqual(memberships[1]);
|
expect(presented?.memberships[1]?.membership).toEqual(memberships[1]);
|
||||||
expect(presented.memberships[1].driver).toEqual(driver2);
|
expect(presented?.memberships[1]?.driver).toEqual(driver2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle drivers not found', async () => {
|
it('should handle drivers not found', async () => {
|
||||||
@@ -137,12 +137,12 @@ describe('GetLeagueMembershipsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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?.league).toEqual(league);
|
||||||
expect(presented.memberships).toHaveLength(1);
|
expect(presented?.memberships).toHaveLength(1);
|
||||||
expect(presented.memberships[0].membership).toEqual(memberships[0]);
|
expect(presented?.memberships[0]?.membership).toEqual(memberships[0]);
|
||||||
expect(presented.memberships[0].driver).toBeNull();
|
expect(presented?.memberships[0]?.driver).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error when league not found', async () => {
|
it('should return error when league not found', async () => {
|
||||||
|
|||||||
@@ -66,12 +66,12 @@ describe('GetLeagueOwnerSummaryUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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?.league).toBe(league);
|
||||||
expect(presented.owner).toBe(driver);
|
expect(presented?.owner).toBe(driver);
|
||||||
expect(presented.rating).toBe(0);
|
expect(presented?.rating).toBe(0);
|
||||||
expect(presented.rank).toBe(0);
|
expect(presented?.rank).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error when league does not exist', async () => {
|
it('should return error when league does not exist', async () => {
|
||||||
|
|||||||
@@ -111,15 +111,15 @@ describe('GetLeagueProtestsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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?.league).toEqual(league);
|
||||||
expect(presented.protests).toHaveLength(1);
|
expect(presented?.protests).toHaveLength(1);
|
||||||
const presentedProtest = presented.protests[0];
|
const presentedProtest = presented?.protests[0];
|
||||||
expect(presentedProtest.protest).toEqual(protest);
|
expect(presentedProtest?.protest).toEqual(protest);
|
||||||
expect(presentedProtest.race).toEqual(race);
|
expect(presentedProtest?.race).toEqual(race);
|
||||||
expect(presentedProtest.protestingDriver).toEqual(driver1);
|
expect(presentedProtest?.protestingDriver).toEqual(driver1);
|
||||||
expect(presentedProtest.accusedDriver).toEqual(driver2);
|
expect(presentedProtest?.accusedDriver).toEqual(driver2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return empty protests when no races', async () => {
|
it('should return empty protests when no races', async () => {
|
||||||
@@ -141,10 +141,10 @@ describe('GetLeagueProtestsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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?.league).toEqual(league);
|
||||||
expect(presented.protests).toEqual([]);
|
expect(presented?.protests).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return LEAGUE_NOT_FOUND when league does not exist', async () => {
|
it('should return LEAGUE_NOT_FOUND when league does not exist', async () => {
|
||||||
|
|||||||
@@ -70,11 +70,11 @@ describe('GetLeagueScheduleUseCase', () => {
|
|||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
expect(output.present).toHaveBeenCalledTimes(1);
|
||||||
const presented =
|
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.league).toBe(league);
|
||||||
expect(presented.races).toHaveLength(1);
|
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 () => {
|
it('should present empty schedule when no races exist', async () => {
|
||||||
@@ -92,7 +92,7 @@ describe('GetLeagueScheduleUseCase', () => {
|
|||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
expect(output.present).toHaveBeenCalledTimes(1);
|
||||||
const presented =
|
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.league).toBe(league);
|
||||||
expect(presented.races).toHaveLength(0);
|
expect(presented.races).toHaveLength(0);
|
||||||
@@ -119,7 +119,7 @@ describe('GetLeagueScheduleUseCase', () => {
|
|||||||
|
|
||||||
it('should return REPOSITORY_ERROR when repository throws', async () => {
|
it('should return REPOSITORY_ERROR when repository throws', async () => {
|
||||||
const leagueId = 'league-1';
|
const leagueId = 'league-1';
|
||||||
const league = { id: leagueId } as League;
|
const league = { id: leagueId } as unknown as League;
|
||||||
const repositoryError = new Error('DB down');
|
const repositoryError = new Error('DB down');
|
||||||
|
|
||||||
leagueRepository.findById.mockResolvedValue(league);
|
leagueRepository.findById.mockResolvedValue(league);
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ describe('GetLeagueScoringConfigUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
expect(output.present).toHaveBeenCalledTimes(1);
|
||||||
const presented =
|
const presented =
|
||||||
output.present.mock.calls[0][0] as GetLeagueScoringConfigResult;
|
output.present.mock.calls[0]?.[0] as GetLeagueScoringConfigResult;
|
||||||
expect(presented.league).toEqual(league);
|
expect(presented?.league).toEqual(league);
|
||||||
expect(presented.season).toEqual(season);
|
expect(presented?.season).toEqual(season);
|
||||||
expect(presented.scoringConfig).toEqual(scoringConfig);
|
expect(presented?.scoringConfig).toEqual(scoringConfig);
|
||||||
expect(presented.game).toEqual(game);
|
expect(presented?.game).toEqual(game);
|
||||||
expect(presented.preset).toEqual(preset);
|
expect(presented?.preset).toEqual(preset);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return scoring config for first season if no active', async () => {
|
it('should return scoring config for first season if no active', async () => {
|
||||||
@@ -86,12 +86,12 @@ describe('GetLeagueScoringConfigUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
expect(output.present).toHaveBeenCalledTimes(1);
|
||||||
const presented =
|
const presented =
|
||||||
output.present.mock.calls[0][0] as GetLeagueScoringConfigResult;
|
output.present.mock.calls[0]?.[0] as GetLeagueScoringConfigResult;
|
||||||
expect(presented.league).toEqual(league);
|
expect(presented?.league).toEqual(league);
|
||||||
expect(presented.season).toEqual(season);
|
expect(presented?.season).toEqual(season);
|
||||||
expect(presented.scoringConfig).toEqual(scoringConfig);
|
expect(presented?.scoringConfig).toEqual(scoringConfig);
|
||||||
expect(presented.game).toEqual(game);
|
expect(presented?.game).toEqual(game);
|
||||||
expect(presented.preset).toBeUndefined();
|
expect(presented?.preset).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error if league not found', async () => {
|
it('should return error if league not found', async () => {
|
||||||
|
|||||||
@@ -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 { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import type { League } from '../../domain/entities/League';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { Season } from '../../domain/entities/season/Season';
|
|
||||||
import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig';
|
|
||||||
import type { Game } from '../../domain/entities/Game';
|
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 = {
|
export type GetLeagueScoringConfigInput = {
|
||||||
leagueId: string;
|
leagueId: string;
|
||||||
@@ -101,7 +101,7 @@ export class GetLeagueScoringConfigUseCase {
|
|||||||
|
|
||||||
const presetId = scoringConfig.scoringPresetId;
|
const presetId = scoringConfig.scoringPresetId;
|
||||||
const preset = presetId
|
const preset = presetId
|
||||||
? this.presetProvider.getPresetById(presetId)
|
? this.presetProvider.getPresetById(presetId.toString())
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const result: GetLeagueScoringConfigResult = {
|
const result: GetLeagueScoringConfigResult = {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { UseCaseOutputPort } from '@core/shared/application';
|
|||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
|
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
|
||||||
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
|
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';
|
import { League } from '../../domain/entities/League';
|
||||||
|
|
||||||
describe('GetLeagueSeasonsUseCase', () => {
|
describe('GetLeagueSeasonsUseCase', () => {
|
||||||
@@ -83,18 +83,18 @@ describe('GetLeagueSeasonsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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?.league).toBe(league);
|
||||||
expect(presented.seasons).toHaveLength(2);
|
expect(presented?.seasons).toHaveLength(2);
|
||||||
|
|
||||||
expect(presented.seasons[0]!.season).toBe(seasons[0]);
|
expect(presented?.seasons[0]?.season).toBe(seasons[0]);
|
||||||
expect(presented.seasons[0]!.isPrimary).toBe(false);
|
expect(presented?.seasons[0]?.isPrimary).toBe(false);
|
||||||
expect(presented.seasons[0]!.isParallelActive).toBe(false);
|
expect(presented?.seasons[0]?.isParallelActive).toBe(false);
|
||||||
|
|
||||||
expect(presented.seasons[1]!.season).toBe(seasons[1]);
|
expect(presented?.seasons[1]?.season).toBe(seasons[1]);
|
||||||
expect(presented.seasons[1]!.isPrimary).toBe(false);
|
expect(presented?.seasons[1]?.isPrimary).toBe(false);
|
||||||
expect(presented.seasons[1]!.isParallelActive).toBe(false);
|
expect(presented?.seasons[1]?.isParallelActive).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set isParallelActive true for active seasons when multiple active', async () => {
|
it('should set isParallelActive true for active seasons when multiple active', async () => {
|
||||||
@@ -132,11 +132,11 @@ describe('GetLeagueSeasonsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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).toHaveLength(2);
|
||||||
expect(presented.seasons[0]!.isParallelActive).toBe(true);
|
expect(presented?.seasons[0]?.isParallelActive).toBe(true);
|
||||||
expect(presented.seasons[1]!.isParallelActive).toBe(true);
|
expect(presented?.seasons[1]?.isParallelActive).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return LEAGUE_NOT_FOUND error when league does not exist', async () => {
|
it('should return LEAGUE_NOT_FOUND error when league does not exist', async () => {
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ describe('GetLeagueWalletUseCase', () => {
|
|||||||
amount: Money.create(1200, 'USD'),
|
amount: Money.create(1200, 'USD'),
|
||||||
description: 'Main Sponsor - TechCorp',
|
description: 'Main Sponsor - TechCorp',
|
||||||
metadata: {},
|
metadata: {},
|
||||||
|
completedAt: undefined,
|
||||||
}).complete();
|
}).complete();
|
||||||
|
|
||||||
const membershipTx = Transaction.create({
|
const membershipTx = Transaction.create({
|
||||||
@@ -83,6 +84,7 @@ describe('GetLeagueWalletUseCase', () => {
|
|||||||
amount: Money.create(1600, 'USD'),
|
amount: Money.create(1600, 'USD'),
|
||||||
description: 'Season Fee - 32 drivers',
|
description: 'Season Fee - 32 drivers',
|
||||||
metadata: {},
|
metadata: {},
|
||||||
|
completedAt: undefined,
|
||||||
}).complete();
|
}).complete();
|
||||||
|
|
||||||
const withdrawalTx = Transaction.create({
|
const withdrawalTx = Transaction.create({
|
||||||
@@ -92,6 +94,7 @@ describe('GetLeagueWalletUseCase', () => {
|
|||||||
amount: Money.create(430, 'USD'),
|
amount: Money.create(430, 'USD'),
|
||||||
description: 'Bank Transfer - Season 1 Payout',
|
description: 'Bank Transfer - Season 1 Payout',
|
||||||
metadata: {},
|
metadata: {},
|
||||||
|
completedAt: undefined,
|
||||||
}).complete();
|
}).complete();
|
||||||
|
|
||||||
const pendingPrizeTx = Transaction.create({
|
const pendingPrizeTx = Transaction.create({
|
||||||
@@ -101,6 +104,7 @@ describe('GetLeagueWalletUseCase', () => {
|
|||||||
amount: Money.create(150, 'USD'),
|
amount: Money.create(150, 'USD'),
|
||||||
description: 'Championship Prize Pool (reserved)',
|
description: 'Championship Prize Pool (reserved)',
|
||||||
metadata: {},
|
metadata: {},
|
||||||
|
completedAt: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const refundTx = Transaction.create({
|
const refundTx = Transaction.create({
|
||||||
@@ -110,6 +114,7 @@ describe('GetLeagueWalletUseCase', () => {
|
|||||||
amount: Money.create(100, 'USD'),
|
amount: Money.create(100, 'USD'),
|
||||||
description: 'Refund for cancelled sponsorship',
|
description: 'Refund for cancelled sponsorship',
|
||||||
metadata: {},
|
metadata: {},
|
||||||
|
completedAt: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const transactions = [
|
const transactions = [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository';
|
import { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository';
|
||||||
import { ISponsorRepository } from '../../domain/repositories/ISponsorRepository';
|
import { ISponsorRepository } from '../../domain/repositories/ISponsorRepository';
|
||||||
import { SponsorshipRequest } from '../../domain/entities/SponsorshipRequest';
|
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 { Money } from '../../domain/value-objects/Money';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
@@ -72,16 +72,18 @@ describe('GetPendingSponsorshipRequestsUseCase', () => {
|
|||||||
expect(result.unwrap()).toBeUndefined();
|
expect(result.unwrap()).toBeUndefined();
|
||||||
|
|
||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
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.entityType).toBe('season');
|
||||||
expect(presented.entityId).toBe('entity-1');
|
expect(presented.entityId).toBe('entity-1');
|
||||||
expect(presented.totalCount).toBe(1);
|
expect(presented.totalCount).toBe(1);
|
||||||
expect(presented.requests).toHaveLength(1);
|
expect(presented.requests).toHaveLength(1);
|
||||||
const summary = presented.requests[0];
|
const summary = presented.requests[0];
|
||||||
expect(summary.sponsor?.name).toBe('Test Sponsor');
|
expect(summary).toBeDefined();
|
||||||
expect(summary.financials.offeredAmount.amount).toBe(10000);
|
expect(summary!.sponsor?.name).toBe('Test Sponsor');
|
||||||
expect(summary.financials.offeredAmount.currency).toBe('USD');
|
expect(summary!.financials.offeredAmount.amount).toBe(10000);
|
||||||
|
expect(summary!.financials.offeredAmount.currency).toBe('USD');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error when repository fails', async () => {
|
it('should return error when repository fails', async () => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
|
|||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import type { SponsorshipRequest } from '../../domain/entities/SponsorshipRequest';
|
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';
|
import { Money } from '../../domain/value-objects/Money';
|
||||||
|
|
||||||
export interface GetPendingSponsorshipRequestsInput {
|
export interface GetPendingSponsorshipRequestsInput {
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
|
import { Result as DomainResult, Result } from '@core/shared/application/Result';
|
||||||
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 type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
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 { League } from '../../domain/entities/League';
|
||||||
import type { Race } from '../../domain/entities/Race';
|
import type { Race } from '../../domain/entities/Race';
|
||||||
import type { RaceRegistration } from '../../domain/entities/RaceRegistration';
|
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 = {
|
export type GetRaceDetailInput = {
|
||||||
raceId: string;
|
raceId: string;
|
||||||
@@ -33,7 +32,7 @@ export type GetRaceDetailResult = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class GetRaceDetailUseCase {
|
export class GetRaceDetailUseCase {
|
||||||
private output: UseCaseOutputPort<GetRaceDetailResult> | null = null;
|
private output: UseCaseOutputPort<GetRaceDetailResult> | null = null; // TODO wtf this must be injected via constructor
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly raceRepository: IRaceRepository,
|
private readonly raceRepository: IRaceRepository,
|
||||||
@@ -44,7 +43,7 @@ export class GetRaceDetailUseCase {
|
|||||||
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
|
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
setOutput(output: UseCaseOutputPort<GetRaceDetailResult>) {
|
setOutput(output: UseCaseOutputPort<GetRaceDetailResult>) { // TODO must be removed
|
||||||
this.output = output;
|
this.output = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 type { Logger } from '@core/shared/application';
|
||||||
import { Result } from '@core/shared/application/Result';
|
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 { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { Race } from '../../domain/entities/Race';
|
import type { Race } from '../../domain/entities/Race';
|
||||||
|
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
|
||||||
|
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
|
||||||
|
|
||||||
export type GetRacesPageDataInput = {
|
export type GetRacesPageDataInput = {
|
||||||
leagueId: string;
|
leagueId: string;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export class GetSeasonDetailsUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const season = await this.seasonRepository.findById(input.seasonId);
|
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({
|
return Result.err({
|
||||||
code: 'SEASON_NOT_FOUND',
|
code: 'SEASON_NOT_FOUND',
|
||||||
details: {
|
details: {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
|
|||||||
import { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
|
import { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
|
||||||
import { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
|
import { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
|
||||||
import { IRaceRepository } from '../../domain/repositories/IRaceRepository';
|
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 { SeasonSponsorship } from '../../domain/entities/season/SeasonSponsorship';
|
||||||
import { Season } from '../../domain/entities/season/Season';
|
import { Season } from '../../domain/entities/season/Season';
|
||||||
import { League } from '../../domain/entities/League';
|
import { League } from '../../domain/entities/League';
|
||||||
@@ -121,6 +121,7 @@ describe('GetSponsorDashboardUseCase', () => {
|
|||||||
expect(output.present).toHaveBeenCalledTimes(1);
|
expect(output.present).toHaveBeenCalledTimes(1);
|
||||||
const dashboard = (output.present as Mock).mock.calls[0][0] as GetSponsorDashboardResult;
|
const dashboard = (output.present as Mock).mock.calls[0][0] as GetSponsorDashboardResult;
|
||||||
|
|
||||||
|
expect(dashboard).toBeDefined();
|
||||||
expect(dashboard.sponsorId).toBe(sponsorId);
|
expect(dashboard.sponsorId).toBe(sponsorId);
|
||||||
expect(dashboard.metrics.impressions).toBe(100); // 1 completed race * 1 driver * 100
|
expect(dashboard.metrics.impressions).toBe(100); // 1 completed race * 1 driver * 100
|
||||||
expect(dashboard.investment.totalInvestment.amount).toBe(10000);
|
expect(dashboard.investment.totalInvestment.amount).toBe(10000);
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export class GetSponsorDashboardUseCase {
|
|||||||
|
|
||||||
const result: GetSponsorDashboardResult = {
|
const result: GetSponsorDashboardResult = {
|
||||||
sponsorId,
|
sponsorId,
|
||||||
sponsorName: sponsor.name,
|
sponsorName: sponsor.name.toString(),
|
||||||
metrics: {
|
metrics: {
|
||||||
impressions: totalImpressions,
|
impressions: totalImpressions,
|
||||||
impressionsChange: 0,
|
impressionsChange: 0,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueReposit
|
|||||||
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
||||||
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
||||||
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
|
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 type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
@@ -154,15 +154,15 @@ export class ImportRaceResultsUseCase {
|
|||||||
|
|
||||||
this.logger.info('ImportRaceResultsUseCase:race results created', { raceId });
|
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', {
|
this.logger.info('ImportRaceResultsUseCase:standings recalculated', {
|
||||||
leagueId: league.id,
|
leagueId: league.id.toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const result: ImportRaceResultsResult = {
|
const result: ImportRaceResultsResult = {
|
||||||
raceId,
|
raceId,
|
||||||
leagueId: league.id,
|
leagueId: league.id.toString(),
|
||||||
driversProcessed: rows.length,
|
driversProcessed: rows.length,
|
||||||
resultsRecorded: validEntities.length,
|
resultsRecorded: validEntities.length,
|
||||||
errors: [],
|
errors: [],
|
||||||
|
|||||||
@@ -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 { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application';
|
import type { UseCaseOutputPort } from '@core/shared/application';
|
||||||
|
|
||||||
export type ListLeagueScoringPresetsInput = {};
|
export type ListLeagueScoringPresetsInput = {};
|
||||||
|
|
||||||
export type LeagueScoringPreset = {
|
export type { LeagueScoringPreset } from '../../domain/types/LeagueScoringPreset';
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
primaryChampionshipType: string;
|
|
||||||
sessionSummary: string;
|
|
||||||
bonusSummary: string;
|
|
||||||
dropPolicySummary: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ListLeagueScoringPresetsResult {
|
export interface ListLeagueScoringPresetsResult {
|
||||||
presets: LeagueScoringPreset[];
|
presets: LeagueScoringPreset[];
|
||||||
@@ -27,7 +19,7 @@ export type ListLeagueScoringPresetsErrorCode = 'REPOSITORY_ERROR';
|
|||||||
*/
|
*/
|
||||||
export class ListLeagueScoringPresetsUseCase {
|
export class ListLeagueScoringPresetsUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly presets: BootstrapLeagueScoringPreset[],
|
private readonly presets: LeagueScoringPreset[],
|
||||||
private readonly output: UseCaseOutputPort<ListLeagueScoringPresetsResult>,
|
private readonly output: UseCaseOutputPort<ListLeagueScoringPresetsResult>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export class RegisterForRaceUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const membership = await this.membershipRepository.getMembership(leagueId, driverId);
|
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}`);
|
this.logger.error(`RegisterForRaceUseCase: driver ${driverId} not an active member of league ${leagueId}`);
|
||||||
return Result.err<void, ApplicationErrorCode<RegisterForRaceErrorCode, { message: string }>>({
|
return Result.err<void, ApplicationErrorCode<RegisterForRaceErrorCode, { message: string }>>({
|
||||||
code: 'NOT_ACTIVE_MEMBER',
|
code: 'NOT_ACTIVE_MEMBER',
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ export class ReviewProtestUseCase {
|
|||||||
// Validate steward has authority (owner or admin of the league)
|
// Validate steward has authority (owner or admin of the league)
|
||||||
const memberships = await this.leagueMembershipRepository.getLeagueMembers(race.leagueId);
|
const memberships = await this.leagueMembershipRepository.getLeagueMembers(race.leagueId);
|
||||||
const stewardMembership = memberships.find(
|
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 });
|
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' } });
|
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);
|
return Result.ok(undefined);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'Failed to review protest';
|
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 } });
|
return Result.err({ code: 'REPOSITORY_ERROR', details: { message } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { Logger, UseCaseOutputPort } from '@core/shared/application';
|
|||||||
import type { NotificationService } from '../../../notifications/application/ports/NotificationService';
|
import type { NotificationService } from '../../../notifications/application/ports/NotificationService';
|
||||||
import type { NotificationType } from '../../../notifications/domain/types/NotificationTypes';
|
import type { NotificationType } from '../../../notifications/domain/types/NotificationTypes';
|
||||||
import type { RaceEvent } from '../../domain/entities/RaceEvent';
|
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 { IRaceEventRepository } from '../../domain/repositories/IRaceEventRepository';
|
||||||
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
||||||
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
|
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' } });
|
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)) {
|
if (!membership || !isLeagueStewardOrHigherRole(membership.role)) {
|
||||||
return Result.err({
|
return Result.err({
|
||||||
code: 'INSUFFICIENT_PERMISSIONS',
|
code: 'INSUFFICIENT_PERMISSIONS',
|
||||||
@@ -90,17 +90,17 @@ export class SendFinalResultsUseCase {
|
|||||||
|
|
||||||
for (const driverResult of results) {
|
for (const driverResult of results) {
|
||||||
await this.sendFinalResultsNotification(
|
await this.sendFinalResultsNotification(
|
||||||
driverResult.driverId,
|
driverResult.driverId.toString(),
|
||||||
raceEvent,
|
raceEvent,
|
||||||
driverResult,
|
driverResult,
|
||||||
league.id,
|
league.id.toString(),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
notificationsSent += 1;
|
notificationsSent += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: SendFinalResultsResult = {
|
const result: SendFinalResultsResult = {
|
||||||
leagueId: league.id,
|
leagueId: league.id.toString(),
|
||||||
raceId: raceEvent.id,
|
raceId: raceEvent.id,
|
||||||
notificationsSent,
|
notificationsSent,
|
||||||
};
|
};
|
||||||
@@ -127,8 +127,8 @@ export class SendFinalResultsUseCase {
|
|||||||
const incidents = driverResult?.incidents ?? 0;
|
const incidents = driverResult?.incidents ?? 0;
|
||||||
|
|
||||||
const finalRatingChange = this.calculateFinalRatingChange(
|
const finalRatingChange = this.calculateFinalRatingChange(
|
||||||
driverResult?.position,
|
driverResult?.position?.toNumber(),
|
||||||
driverResult?.incidents,
|
driverResult?.incidents?.toNumber(),
|
||||||
hadPenaltiesApplied,
|
hadPenaltiesApplied,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
|
|||||||
import type { NotificationService } from '../../../notifications/application/ports/NotificationService';
|
import type { NotificationService } from '../../../notifications/application/ports/NotificationService';
|
||||||
import type { NotificationType } from '../../../notifications/domain/types/NotificationTypes';
|
import type { NotificationType } from '../../../notifications/domain/types/NotificationTypes';
|
||||||
import type { RaceEvent } from '../../domain/entities/RaceEvent';
|
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 { IRaceEventRepository } from '../../domain/repositories/IRaceEventRepository';
|
||||||
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
||||||
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
|
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
|
||||||
@@ -72,7 +74,7 @@ export class SendPerformanceSummaryUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (input.triggeredById !== input.driverId) {
|
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)) {
|
if (!membership || !isLeagueStewardOrHigherRole(membership.role)) {
|
||||||
return Result.err({
|
return Result.err({
|
||||||
code: 'INSUFFICIENT_PERMISSIONS',
|
code: 'INSUFFICIENT_PERMISSIONS',
|
||||||
@@ -90,7 +92,7 @@ export class SendPerformanceSummaryUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const results = await this.resultRepository.findByRaceId(mainRaceSession.id);
|
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) {
|
if (!driverResult) {
|
||||||
return Result.err({
|
return Result.err({
|
||||||
@@ -101,11 +103,11 @@ export class SendPerformanceSummaryUseCase {
|
|||||||
|
|
||||||
let notificationsSent = 0;
|
let notificationsSent = 0;
|
||||||
|
|
||||||
await this.sendPerformanceSummaryNotification(input.driverId, raceEvent, driverResult, league.id);
|
await this.sendPerformanceSummaryNotification(input.driverId, raceEvent, driverResult, league.id.toString());
|
||||||
notificationsSent += 1;
|
notificationsSent += 1;
|
||||||
|
|
||||||
const result: SendPerformanceSummaryResult = {
|
const result: SendPerformanceSummaryResult = {
|
||||||
leagueId: league.id,
|
leagueId: league.id.toString(),
|
||||||
raceId: raceEvent.id,
|
raceId: raceEvent.id,
|
||||||
driverId: input.driverId,
|
driverId: input.driverId,
|
||||||
notificationsSent,
|
notificationsSent,
|
||||||
@@ -131,10 +133,12 @@ export class SendPerformanceSummaryUseCase {
|
|||||||
const positionChange = driverResult?.getPositionChange() ?? 0;
|
const positionChange = driverResult?.getPositionChange() ?? 0;
|
||||||
const incidents = driverResult?.incidents ?? 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 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({
|
await this.notificationService.sendNotification({
|
||||||
recipientId: driverId,
|
recipientId: driverId,
|
||||||
@@ -147,9 +151,9 @@ export class SendPerformanceSummaryUseCase {
|
|||||||
raceEventId: raceEvent.id,
|
raceEventId: raceEvent.id,
|
||||||
sessionId: raceEvent.getMainRaceSession()?.id ?? '',
|
sessionId: raceEvent.getMainRaceSession()?.id ?? '',
|
||||||
leagueId,
|
leagueId,
|
||||||
position,
|
position: positionValue,
|
||||||
positionChange,
|
positionChange,
|
||||||
incidents,
|
incidents: incidentValue,
|
||||||
provisionalRatingChange,
|
provisionalRatingChange,
|
||||||
},
|
},
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -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
|
* Enhanced race result generator with detailed incident types
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
* Enhanced Result entity with detailed incident tracking
|
* Enhanced Result entity with detailed incident tracking
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
|
||||||
import type { IEntity } from '@core/shared/domain';
|
import type { IEntity } from '@core/shared/domain';
|
||||||
|
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||||
import { RaceIncidents, type IncidentRecord } from '../value-objects/RaceIncidents';
|
import { RaceIncidents, type IncidentRecord } from '../value-objects/RaceIncidents';
|
||||||
import { RaceId } from './RaceId';
|
|
||||||
import { DriverId } from './DriverId';
|
import { DriverId } from './DriverId';
|
||||||
import { Position } from './result/Position';
|
import { RaceId } from './RaceId';
|
||||||
import { LapTime } from './result/LapTime';
|
import { LapTime } from './result/LapTime';
|
||||||
|
import { Position } from './result/Position';
|
||||||
|
|
||||||
export class ResultWithIncidents implements IEntity<string> {
|
export class ResultWithIncidents implements IEntity<string> {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
@@ -66,6 +66,7 @@ export class ResultWithIncidents implements IEntity<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO WE DONT NEED ANY LEGACY CODE WE ARE NOT EVEN LIVE
|
||||||
/**
|
/**
|
||||||
* Create from legacy Result data (with incidents as number)
|
* Create from legacy Result data (with incidents as number)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ describe('SponsorshipRequest', () => {
|
|||||||
const validProps = {
|
const validProps = {
|
||||||
id: 'request-123',
|
id: 'request-123',
|
||||||
sponsorId: 'sponsor-456',
|
sponsorId: 'sponsor-456',
|
||||||
entityType: 'driver',
|
entityType: 'driver' as SponsorableEntityType,
|
||||||
entityId: 'driver-789',
|
entityId: 'driver-789',
|
||||||
tier: 'main',
|
tier: 'main' as SponsorshipTier,
|
||||||
offeredAmount: validMoney,
|
offeredAmount: validMoney,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { LeagueWallet } from './LeagueWallet';
|
import { LeagueWallet } from './LeagueWallet';
|
||||||
import { Money } from '../value-objects/Money';
|
import { Money } from '../../value-objects/Money';
|
||||||
|
|
||||||
describe('LeagueWallet', () => {
|
describe('LeagueWallet', () => {
|
||||||
it('should create a league wallet', () => {
|
it('should create a league wallet', () => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import type { Money } from '../../value-objects/Money';
|
|||||||
import { Position } from '../championship/Position';
|
import { Position } from '../championship/Position';
|
||||||
import { PrizeId } from './PrizeId';
|
import { PrizeId } from './PrizeId';
|
||||||
import { PrizeStatus } from './PrizeStatus';
|
import { PrizeStatus } from './PrizeStatus';
|
||||||
import { SeasonId } from '../SeasonId';
|
import { SeasonId } from '../season/SeasonId';
|
||||||
import { DriverId } from '../DriverId';
|
import { DriverId } from '../DriverId';
|
||||||
|
|
||||||
export interface PrizeProps {
|
export interface PrizeProps {
|
||||||
|
|||||||
@@ -78,9 +78,9 @@ export class Sponsor implements IEntity<SponsorId> {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
name,
|
name,
|
||||||
contactEmail,
|
contactEmail,
|
||||||
logoUrl,
|
|
||||||
websiteUrl,
|
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
|
...(logoUrl !== undefined ? { logoUrl } : {}),
|
||||||
|
...(websiteUrl !== undefined ? { websiteUrl } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,14 @@
|
|||||||
* Defines operations for Prize entity persistence
|
* 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 {
|
export interface IPrizeRepository {
|
||||||
findById(id: string): Promise<Prize | null>;
|
findById(id: string): Promise<Prize | null>;
|
||||||
findBySeasonId(seasonId: string): Promise<Prize[]>;
|
findBySeasonId(seasonId: string): Promise<Prize[]>;
|
||||||
findByDriverId(driverId: string): Promise<Prize[]>;
|
findByDriverId(driverId: string): Promise<Prize[]>;
|
||||||
findByStatus(status: PrizeStatus): Promise<Prize[]>;
|
findByStatus(status: PrizeStatusValue): Promise<Prize[]>;
|
||||||
findBySeasonAndPosition(seasonId: string, position: number): Promise<Prize | null>;
|
findBySeasonAndPosition(seasonId: string, position: number): Promise<Prize | null>;
|
||||||
create(prize: Prize): Promise<Prize>;
|
create(prize: Prize): Promise<Prize>;
|
||||||
update(prize: Prize): Promise<Prize>;
|
update(prize: Prize): Promise<Prize>;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Defines operations for Sponsor aggregate persistence
|
* Defines operations for Sponsor aggregate persistence
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Sponsor } from '../entities/Sponsor';
|
import type { Sponsor } from '../entities/sponsor/Sponsor';
|
||||||
|
|
||||||
export interface ISponsorRepository {
|
export interface ISponsorRepository {
|
||||||
findById(id: string): Promise<Sponsor | null>;
|
findById(id: string): Promise<Sponsor | null>;
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import type { SessionType } from '@core/racing/domain/types/SessionType';
|
|||||||
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';
|
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';
|
||||||
import type { BonusRule } from '@core/racing/domain/types/BonusRule';
|
import type { BonusRule } from '@core/racing/domain/types/BonusRule';
|
||||||
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
|
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 { Penalty } from '@core/racing/domain/entities/Penalty';
|
||||||
import type { ChampionshipType } from '@core/racing/domain/types/ChampionshipType';
|
import type { ChampionshipType } from '@core/racing/domain/types/ChampionshipType';
|
||||||
import { makeDriverRef } from '../../testing/factories/racing/DriverRefFactory';
|
import { makeDriverRef } from '../../../testing/factories/racing/DriverRefFactory';
|
||||||
import { makePointsTable } from '../../testing/factories/racing/PointsTableFactory';
|
import { makePointsTable } from '../../../testing/factories/racing/PointsTableFactory';
|
||||||
import { makeChampionshipConfig } from '../../testing/factories/racing/ChampionshipConfigFactory';
|
import { makeChampionshipConfig } from '../../../testing/factories/racing/ChampionshipConfigFactory';
|
||||||
|
|
||||||
|
|
||||||
describe('EventScoringService', () => {
|
describe('EventScoringService', () => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { RaceTimeOfDay } from '../value-objects/RaceTimeOfDay';
|
|||||||
import type { Weekday } from '../types/Weekday';
|
import type { Weekday } from '../types/Weekday';
|
||||||
import { weekdayToIndex } from '../types/Weekday';
|
import { weekdayToIndex } from '../types/Weekday';
|
||||||
import type { IDomainCalculationService } from '@core/shared/domain';
|
import type { IDomainCalculationService } from '@core/shared/domain';
|
||||||
|
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||||
|
|
||||||
function cloneDate(date: Date): Date {
|
function cloneDate(date: Date): Date {
|
||||||
return new Date(date.getTime());
|
return new Date(date.getTime());
|
||||||
@@ -63,15 +64,15 @@ function generateWeeklyOrEveryNWeeksSlots(
|
|||||||
const result: ScheduledRaceSlot[] = [];
|
const result: ScheduledRaceSlot[] = [];
|
||||||
const recurrence = schedule.recurrence;
|
const recurrence = schedule.recurrence;
|
||||||
const weekdays =
|
const weekdays =
|
||||||
recurrence.kind === 'weekly' || recurrence.kind === 'everyNWeeks'
|
recurrence.props.kind === 'weekly' || recurrence.props.kind === 'everyNWeeks'
|
||||||
? recurrence.weekdays.getAll()
|
? recurrence.props.weekdays.getAll()
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (weekdays.length === 0) {
|
if (weekdays.length === 0) {
|
||||||
throw new RacingDomainValidationError('RecurrenceStrategy has no weekdays');
|
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 anchorWeekStart = cloneDate(schedule.startDate);
|
||||||
let roundNumber = 1;
|
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[] {
|
function generateMonthlySlots(schedule: SeasonSchedule, maxRounds: number): ScheduledRaceSlot[] {
|
||||||
const result: ScheduledRaceSlot[] = [];
|
const result: ScheduledRaceSlot[] = [];
|
||||||
const recurrence = schedule.recurrence;
|
const recurrence = schedule.recurrence;
|
||||||
if (recurrence.kind !== 'monthlyNthWeekday') {
|
if (recurrence.props.kind !== 'monthlyNthWeekday') {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ordinal, weekday } = recurrence.monthlyPattern;
|
const { ordinal, weekday } = recurrence.props.monthlyPattern;
|
||||||
let currentMonthDate = new Date(
|
let currentMonthDate = new Date(
|
||||||
schedule.startDate.getFullYear(),
|
schedule.startDate.getFullYear(),
|
||||||
schedule.startDate.getMonth(),
|
schedule.startDate.getMonth(),
|
||||||
@@ -168,7 +169,7 @@ export class SeasonScheduleGenerator {
|
|||||||
|
|
||||||
const recurrence: RecurrenceStrategy = schedule.recurrence;
|
const recurrence: RecurrenceStrategy = schedule.recurrence;
|
||||||
|
|
||||||
if (recurrence.kind === 'monthlyNthWeekday') {
|
if (recurrence.props.kind === 'monthlyNthWeekday') {
|
||||||
return generateMonthlySlots(schedule, maxRounds);
|
return generateMonthlySlots(schedule, maxRounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
* Utility functions for working with league membership roles.
|
* 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)
|
* Role hierarchy (higher number = more authority)
|
||||||
*/
|
*/
|
||||||
const ROLE_HIERARCHY: Record<MembershipRole, number> = {
|
const ROLE_HIERARCHY: Record<MembershipRoleValue, number> = {
|
||||||
member: 0,
|
member: 0,
|
||||||
steward: 1,
|
steward: 1,
|
||||||
admin: 2,
|
admin: 2,
|
||||||
@@ -19,21 +19,21 @@ const ROLE_HIERARCHY: Record<MembershipRole, number> = {
|
|||||||
/**
|
/**
|
||||||
* Check if a role is at least steward level
|
* 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;
|
return ROLE_HIERARCHY[role] >= ROLE_HIERARCHY.steward;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a role is at least admin level
|
* 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;
|
return ROLE_HIERARCHY[role] >= ROLE_HIERARCHY.admin;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a role is owner
|
* Check if a role is owner
|
||||||
*/
|
*/
|
||||||
export function isLeagueOwnerRole(role: MembershipRole): boolean {
|
export function isLeagueOwnerRole(role: MembershipRoleValue): boolean {
|
||||||
return role === 'owner';
|
return role === 'owner';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,33 +41,33 @@ export function isLeagueOwnerRole(role: MembershipRole): boolean {
|
|||||||
* Compare two roles
|
* Compare two roles
|
||||||
* Returns positive if role1 > role2, negative if role1 < role2, 0 if equal
|
* 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];
|
return ROLE_HIERARCHY[role1] - ROLE_HIERARCHY[role2];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get role display name
|
* Get role display name
|
||||||
*/
|
*/
|
||||||
export function getRoleDisplayName(role: MembershipRole): string {
|
export function getRoleDisplayName(role: MembershipRoleValue): string {
|
||||||
const names: Record<MembershipRole, string> = {
|
const names: Record<MembershipRoleValue, string> = {
|
||||||
member: 'Member',
|
member: 'Member',
|
||||||
steward: 'Steward',
|
steward: 'Steward',
|
||||||
admin: 'Admin',
|
admin: 'Admin',
|
||||||
owner: 'Owner',
|
owner: 'Owner',
|
||||||
};
|
};
|
||||||
return names[role];
|
return names[role] || role;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all roles in order of hierarchy
|
* Get all roles in order of hierarchy
|
||||||
*/
|
*/
|
||||||
export function getAllRolesOrdered(): MembershipRole[] {
|
export function getAllRolesOrdered(): MembershipRoleValue[] {
|
||||||
return ['member', 'steward', 'admin', 'owner'];
|
return ['member', 'steward', 'admin', 'owner'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get roles that can be assigned (excludes owner as it's transferred, not assigned)
|
* 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'];
|
return ['member', 'steward', 'admin'];
|
||||||
}
|
}
|
||||||
21
core/racing/domain/types/LeagueScoringPreset.ts
Normal file
21
core/racing/domain/types/LeagueScoringPreset.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -104,10 +104,10 @@ export class RaceIncidents implements IValueObject<IncidentRecord[]> {
|
|||||||
|
|
||||||
return sortedThis.every((incident, index) => {
|
return sortedThis.every((incident, index) => {
|
||||||
const otherIncident = sortedOther[index];
|
const otherIncident = sortedOther[index];
|
||||||
return incident.type === otherIncident.type &&
|
return incident.type === otherIncident?.type &&
|
||||||
incident.lap === otherIncident.lap &&
|
incident.lap === otherIncident?.lap &&
|
||||||
incident.description === otherIncident.description &&
|
incident.description === otherIncident?.description &&
|
||||||
incident.penaltyPoints === otherIncident.penaltyPoints;
|
incident.penaltyPoints === otherIncident?.penaltyPoints;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { ISocialGraphRepository } from '../../domain/repositories/ISocialGraphRepository';
|
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 => ({
|
const friends: FriendDTO[] = friendsDomain.map(friend => ({
|
||||||
driverId: friend.id,
|
driverId: friend.id,
|
||||||
displayName: friend.name,
|
displayName: friend.name.toString(),
|
||||||
avatarUrl: '',
|
avatarUrl: '',
|
||||||
isOnline: false,
|
isOnline: false,
|
||||||
lastSeen: new Date(),
|
lastSeen: new Date(),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { Driver } from '@core/racing/domain/entities/Driver';
|
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 { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository';
|
||||||
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
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 = {
|
export type Friendship = {
|
||||||
driverId: string;
|
driverId: string;
|
||||||
@@ -18,7 +18,6 @@ export type RacingSeedData = {
|
|||||||
export class InMemoryFeedRepository implements IFeedRepository {
|
export class InMemoryFeedRepository implements IFeedRepository {
|
||||||
private readonly feedEvents: FeedItem[];
|
private readonly feedEvents: FeedItem[];
|
||||||
private readonly friendships: Friendship[];
|
private readonly friendships: Friendship[];
|
||||||
private readonly driversById: Map<string, Driver>;
|
|
||||||
private readonly logger: Logger;
|
private readonly logger: Logger;
|
||||||
|
|
||||||
constructor(logger: Logger, seed: RacingSeedData) {
|
constructor(logger: Logger, seed: RacingSeedData) {
|
||||||
@@ -26,7 +25,6 @@ export class InMemoryFeedRepository implements IFeedRepository {
|
|||||||
this.logger.info('InMemoryFeedRepository initialized.');
|
this.logger.info('InMemoryFeedRepository initialized.');
|
||||||
this.feedEvents = seed.feedEvents;
|
this.feedEvents = seed.feedEvents;
|
||||||
this.friendships = seed.friendships;
|
this.friendships = seed.friendships;
|
||||||
this.driversById = new Map(seed.drivers.map((d) => [d.id, d]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFeedForDriver(driverId: string, limit?: number): Promise<FeedItem[]> {
|
async getFeedForDriver(driverId: string, limit?: number): Promise<FeedItem[]> {
|
||||||
@@ -52,7 +50,7 @@ export class InMemoryFeedRepository implements IFeedRepository {
|
|||||||
this.logger.info(`Found ${sorted.length} feed items for driver: ${driverId}.`);
|
this.logger.info(`Found ${sorted.length} feed items for driver: ${driverId}.`);
|
||||||
return typeof limit === 'number' ? sorted.slice(0, limit) : sorted;
|
return typeof limit === 'number' ? sorted.slice(0, limit) : sorted;
|
||||||
} catch (error) {
|
} 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;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +65,7 @@ export class InMemoryFeedRepository implements IFeedRepository {
|
|||||||
this.logger.info(`Found ${sorted.length} global feed items.`);
|
this.logger.info(`Found ${sorted.length} global feed items.`);
|
||||||
return typeof limit === 'number' ? sorted.slice(0, limit) : sorted;
|
return typeof limit === 'number' ? sorted.slice(0, limit) : sorted;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Error getting global feed:`, error);
|
this.logger.error(`Error getting global feed:`, error as Error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +92,7 @@ export class InMemorySocialGraphRepository implements ISocialGraphRepository {
|
|||||||
this.logger.info(`Found ${friendIds.length} friend IDs for driver: ${driverId}.`);
|
this.logger.info(`Found ${friendIds.length} friend IDs for driver: ${driverId}.`);
|
||||||
return friendIds;
|
return friendIds;
|
||||||
} catch (error) {
|
} 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;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +107,7 @@ export class InMemorySocialGraphRepository implements ISocialGraphRepository {
|
|||||||
this.logger.info(`Found ${friends.length} friends for driver: ${driverId}.`);
|
this.logger.info(`Found ${friends.length} friends for driver: ${driverId}.`);
|
||||||
return friends;
|
return friends;
|
||||||
} catch (error) {
|
} 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;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,7 +139,7 @@ export class InMemorySocialGraphRepository implements ISocialGraphRepository {
|
|||||||
this.logger.info(`Found ${result.length} suggested friends for driver: ${driverId}.`);
|
this.logger.info(`Found ${result.length} suggested friends for driver: ${driverId}.`);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} 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;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
core/testing/factories/racing/ChampionshipConfigFactory.ts
Normal file
58
core/testing/factories/racing/ChampionshipConfigFactory.ts
Normal file
@@ -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<SessionType, PointsTable> = {} as Record<SessionType, PointsTable>;
|
||||||
|
|
||||||
|
// Convert array format to PointsTable for each session type
|
||||||
|
sessionTypes.forEach(sessionType => {
|
||||||
|
const pointsArray = mainPoints;
|
||||||
|
const pointsMap: Record<number, number> = {};
|
||||||
|
pointsArray.forEach((points, index) => {
|
||||||
|
pointsMap[index + 1] = points;
|
||||||
|
});
|
||||||
|
pointsTableBySessionType[sessionType] = new PointsTable(pointsMap);
|
||||||
|
});
|
||||||
|
|
||||||
|
const bonusRulesBySessionType: Record<SessionType, BonusRule[]> = {} as Record<SessionType, BonusRule[]>;
|
||||||
|
|
||||||
|
// Add bonus rules for each session type
|
||||||
|
sessionTypes.forEach(sessionType => {
|
||||||
|
bonusRulesBySessionType[sessionType] = mainBonusRules;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
sessionTypes,
|
||||||
|
pointsTableBySessionType,
|
||||||
|
bonusRulesBySessionType,
|
||||||
|
dropScorePolicy,
|
||||||
|
};
|
||||||
|
}
|
||||||
9
core/testing/factories/racing/DriverRefFactory.ts
Normal file
9
core/testing/factories/racing/DriverRefFactory.ts
Normal file
@@ -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',
|
||||||
|
};
|
||||||
|
}
|
||||||
5
core/testing/factories/racing/PointsTableFactory.ts
Normal file
5
core/testing/factories/racing/PointsTableFactory.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { PointsTable } from '../../../racing/domain/value-objects/PointsTable';
|
||||||
|
|
||||||
|
export function makePointsTable(points: number[]): PointsTable {
|
||||||
|
return new PointsTable(points);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user