fix core tests

This commit is contained in:
2025-12-23 20:09:02 +01:00
parent 7290fe69b5
commit b5431355ca
25 changed files with 415 additions and 211 deletions

View File

@@ -61,11 +61,16 @@ describe('LoginWithEmailUseCase', () => {
id: 'user-1',
email: 'test@example.com',
displayName: 'Test User',
passwordHash: 'hashed-password',
passwordHash: '',
salt: 'salt',
createdAt: new Date(),
};
storedUser.passwordHash = await (useCase as unknown as { hashPassword: (p: string, s: string) => Promise<string> }).hashPassword(
input.password,
storedUser.salt,
);
const session = {
user: {
id: storedUser.id,
@@ -89,9 +94,8 @@ describe('LoginWithEmailUseCase', () => {
expect(userRepository.findByEmail).toHaveBeenCalledWith('test@example.com');
expect(sessionPort.createSession).toHaveBeenCalledWith({
id: storedUser.id,
email: storedUser.email,
displayName: storedUser.displayName,
primaryDriverId: undefined,
email: storedUser.email,
});
expect(output.present).toHaveBeenCalledTimes(1);

View File

@@ -166,12 +166,29 @@ describe('AcceptSponsorshipRequestUseCase', () => {
balance: 1000,
}),
);
expect(mockLeagueWalletRepo.update).toHaveBeenCalledWith(
expect.objectContaining({
id: 'league1',
balance: expect.objectContaining({ amount: 1400 }),
}),
);
expect(mockLeagueWalletRepo.update).toHaveBeenCalledTimes(1);
const updatedLeagueWallet = (mockLeagueWalletRepo.update as Mock).mock.calls[0]?.[0] as LeagueWallet;
type ToStringable = { toString(): string };
const asString = (value: unknown): string => {
if (typeof value === 'string') return value;
if (
value &&
typeof value === 'object' &&
'toString' in value &&
typeof (value as ToStringable).toString === 'function'
) {
return (value as ToStringable).toString();
}
return String(value);
};
const updatedLeagueWalletId = (updatedLeagueWallet as unknown as { id: unknown }).id;
const updatedLeagueWalletBalanceAmount = (updatedLeagueWallet as unknown as { balance: { amount: number } })
.balance.amount;
expect(asString(updatedLeagueWalletId)).toBe('league1');
expect(updatedLeagueWalletBalanceAmount).toBe(1400);
expect(output.present).toHaveBeenCalledWith({
requestId: 'req1',

View File

@@ -264,20 +264,30 @@ describe('ApplyForSponsorshipUseCase', () => {
});
expect(result.isOk()).toBe(true);
expect(result.value).toEqual({
expect(result.unwrap()).toBeUndefined();
expect(output.present).toHaveBeenCalledTimes(1);
const presented = (output.present as Mock).mock.calls[0]?.[0];
expect(presented).toEqual({
requestId: expect.any(String),
status: 'pending',
createdAt: expect.any(Date),
});
expect(mockSponsorshipRequestRepo.create).toHaveBeenCalledWith(
expect.objectContaining({
sponsorId: 'sponsor1',
entityType: 'season',
entityId: 'season1',
tier: 'main',
offeredAmount: expect.objectContaining({ amount: 1000 }),
message: 'Test message',
})
);
expect(mockSponsorshipRequestRepo.create).toHaveBeenCalledTimes(1);
const createdRequest = (mockSponsorshipRequestRepo.create as Mock).mock.calls[0]?.[0] as unknown as {
sponsorId: string;
entityType: string;
entityId: string;
tier: string;
offeredAmount: { amount: number };
message?: string;
};
expect(createdRequest.sponsorId).toBe('sponsor1');
expect(createdRequest.entityType).toBe('season');
expect(createdRequest.entityId).toBe('season1');
expect(createdRequest.tier).toBe('main');
expect(createdRequest.offeredAmount.amount).toBe(1000);
expect(createdRequest.message).toBe('Test message');
});
});

View File

@@ -267,21 +267,47 @@ describe('ApplyPenaltyUseCase', () => {
});
expect(result.isOk()).toBe(true);
expect(result.value).toEqual({
penaltyId: expect.any(String),
});
expect(mockPenaltyRepo.create).toHaveBeenCalledWith(
expect.objectContaining({
leagueId: 'league1',
raceId: 'race1',
driverId: 'driver1',
type: 'time_penalty',
value: 5,
reason: 'Test penalty',
issuedBy: 'steward1',
status: 'pending',
notes: 'Test notes',
})
);
expect(result.unwrap()).toBeUndefined();
expect(output.present).toHaveBeenCalledTimes(1);
const presented = (output.present as Mock).mock.calls[0]?.[0] as ApplyPenaltyResult;
expect(presented).toEqual({ penaltyId: expect.any(String) });
expect(mockPenaltyRepo.create).toHaveBeenCalledTimes(1);
const createdPenalty = (mockPenaltyRepo.create as Mock).mock.calls[0]?.[0] as unknown as {
leagueId: unknown;
raceId: unknown;
driverId: unknown;
type: string;
value?: number;
reason: string;
issuedBy: unknown;
status: unknown;
notes?: string;
};
type ToStringable = { toString(): string };
const asString = (value: unknown): string => {
if (typeof value === 'string') return value;
if (
value &&
typeof value === 'object' &&
'toString' in value &&
typeof (value as ToStringable).toString === 'function'
) {
return (value as ToStringable).toString();
}
return String(value);
};
expect(asString(createdPenalty.leagueId)).toBe('league1');
expect(asString(createdPenalty.raceId)).toBe('race1');
expect(asString(createdPenalty.driverId)).toBe('driver1');
expect(createdPenalty.type).toBe('time_penalty');
expect(createdPenalty.value).toBe(5);
expect(createdPenalty.reason).toBe('Test penalty');
expect(asString(createdPenalty.issuedBy)).toBe('steward1');
expect(asString(createdPenalty.status)).toBe('pending');
expect(createdPenalty.notes).toBe('Test notes');
});
});

View File

@@ -44,16 +44,22 @@ describe('ApproveLeagueJoinRequestUseCase', () => {
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(mockLeagueMembershipRepo.removeJoinRequest).toHaveBeenCalledWith(requestId);
expect(mockLeagueMembershipRepo.saveMembership).toHaveBeenCalledWith(
expect.objectContaining({
id: expect.any(String),
leagueId: expect.objectContaining({ toString: expect.any(Function) }),
driverId: expect.objectContaining({ toString: expect.any(Function) }),
role: expect.objectContaining({ toString: expect.any(Function) }),
status: expect.objectContaining({ toString: expect.any(Function) }),
joinedAt: expect.any(Date),
})
);
expect(mockLeagueMembershipRepo.saveMembership).toHaveBeenCalledTimes(1);
const savedMembership = (mockLeagueMembershipRepo.saveMembership as Mock).mock.calls[0]?.[0] as unknown as {
id: string;
leagueId: { toString(): string };
driverId: { toString(): string };
role: { toString(): string };
status: { toString(): string };
joinedAt: { toDate(): Date };
};
expect(savedMembership.id).toEqual(expect.any(String));
expect(savedMembership.leagueId.toString()).toBe('league-1');
expect(savedMembership.driverId.toString()).toBe('driver-1');
expect(savedMembership.role.toString()).toBe('member');
expect(savedMembership.status.toString()).toBe('active');
expect(savedMembership.joinedAt.toDate()).toBeInstanceOf(Date);
expect(output.present).toHaveBeenCalledWith({ success: true, message: 'Join request approved.' });
});

View File

@@ -14,6 +14,7 @@ describe('CloseRaceEventStewardingUseCase', () => {
let useCase: CloseRaceEventStewardingUseCase;
let raceEventRepository: {
findAwaitingStewardingClose: Mock;
findById: Mock;
update: Mock;
};
let raceRegistrationRepository: {
@@ -33,6 +34,7 @@ describe('CloseRaceEventStewardingUseCase', () => {
beforeEach(() => {
raceEventRepository = {
findAwaitingStewardingClose: vi.fn(),
findById: vi.fn(),
update: vi.fn(),
};
raceRegistrationRepository = {
@@ -80,6 +82,7 @@ describe('CloseRaceEventStewardingUseCase', () => {
});
raceEventRepository.findAwaitingStewardingClose.mockResolvedValue([raceEvent]);
raceEventRepository.findById.mockResolvedValue(raceEvent.closeStewarding());
raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1', 'driver-2']);
penaltyRepository.findByRaceId.mockResolvedValue([]);
domainEventPublisher.publish.mockResolvedValue(undefined);
@@ -90,13 +93,22 @@ describe('CloseRaceEventStewardingUseCase', () => {
expect(result.unwrap()).toBeUndefined();
expect(raceEventRepository.findAwaitingStewardingClose).toHaveBeenCalled();
expect(raceEventRepository.update).toHaveBeenCalledWith(
expect.objectContaining({ id: 'event-1', status: 'closed' })
expect.objectContaining({ status: 'closed' })
);
expect(domainEventPublisher.publish).toHaveBeenCalled();
expect(output.present).toHaveBeenCalledTimes(1);
expect(output.present).toHaveBeenCalledWith({
race: expect.objectContaining({ id: 'event-1', status: 'closed' })
});
const presentedRace = (output.present as Mock).mock.calls[0]?.[0]?.race as unknown as {
id?: unknown;
status?: unknown;
};
const presentedId =
presentedRace?.id && typeof presentedRace.id === 'object' && typeof presentedRace.id.toString === 'function'
? presentedRace.id.toString()
: presentedRace?.id;
expect(presentedId).toBe('event-1');
expect(presentedRace?.status).toBe('closed');
});
it('should handle no expired events', async () => {

View File

@@ -6,6 +6,10 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueReposit
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import type { ChampionshipConfig } from '../../domain/types/ChampionshipConfig';
import type { SessionType } from '../../domain/types/SessionType';
import type { BonusRule } from '../../domain/types/BonusRule';
import { PointsTable } from '../../domain/value-objects/PointsTable';
import {
LeagueVisibility,
MIN_RANKED_LEAGUE_DRIVERS,
@@ -114,10 +118,11 @@ export class CreateLeagueWithSeasonAndScoringUseCase {
}
this.logger.info(`Scoring preset ${preset.name} (${preset.id}) retrieved.`);
const championships = this.createDefaultChampionshipConfigs(command);
const scoringConfig = LeagueScoringConfig.create({
seasonId,
scoringPresetId: preset.id,
championships: [], // Empty array - will be populated by preset
championships,
});
this.logger.debug(`Scoring configuration created from preset ${preset.id}.`);
@@ -142,6 +147,92 @@ export class CreateLeagueWithSeasonAndScoringUseCase {
}
}
private createDefaultChampionshipConfigs(
command: CreateLeagueWithSeasonAndScoringCommand,
): ChampionshipConfig[] {
const sessionTypes: SessionType[] = ['main'];
const defaultPoints = [25, 18, 15, 12, 10, 8, 6, 4, 2, 1];
const pointsMap: Record<number, number> = {};
defaultPoints.forEach((points, index) => {
pointsMap[index + 1] = points;
});
const pointsTableBySessionType: Record<SessionType, PointsTable> =
{} as Record<SessionType, PointsTable>;
const bonusRulesBySessionType: Record<SessionType, BonusRule[]> =
{} as Record<SessionType, BonusRule[]>;
for (const sessionType of sessionTypes) {
pointsTableBySessionType[sessionType] = new PointsTable(pointsMap);
bonusRulesBySessionType[sessionType] = [];
}
const configs: ChampionshipConfig[] = [];
if (command.enableDriverChampionship) {
configs.push({
id: uuidv4(),
name: 'Driver Championship',
type: 'driver',
sessionTypes,
pointsTableBySessionType,
bonusRulesBySessionType,
dropScorePolicy: { strategy: 'none' },
});
}
if (command.enableTeamChampionship) {
configs.push({
id: uuidv4(),
name: 'Team Championship',
type: 'team',
sessionTypes,
pointsTableBySessionType,
bonusRulesBySessionType,
dropScorePolicy: { strategy: 'none' },
});
}
if (command.enableNationsChampionship) {
configs.push({
id: uuidv4(),
name: 'Nations Championship',
type: 'nations',
sessionTypes,
pointsTableBySessionType,
bonusRulesBySessionType,
dropScorePolicy: { strategy: 'none' },
});
}
if (command.enableTrophyChampionship) {
configs.push({
id: uuidv4(),
name: 'Trophy Championship',
type: 'trophy',
sessionTypes,
pointsTableBySessionType,
bonusRulesBySessionType,
dropScorePolicy: { strategy: 'none' },
});
}
if (configs.length === 0) {
configs.push({
id: uuidv4(),
name: 'Driver Championship',
type: 'driver',
sessionTypes,
pointsTableBySessionType,
bonusRulesBySessionType,
dropScorePolicy: { strategy: 'none' },
});
}
return configs;
}
private validate(
command: CreateLeagueWithSeasonAndScoringCommand,
): Result<void, ApplicationErrorCode<'VALIDATION_ERROR', { message: string }>> {

View File

@@ -56,11 +56,14 @@ describe('CreateSponsorUseCase', () => {
expect(output.present).toHaveBeenCalledTimes(1);
const presented = (output.present as Mock).mock.calls[0]?.[0];
expect(presented?.sponsor.id).toBeDefined();
expect(presented?.sponsor.name).toBe('Test Sponsor');
expect(presented?.sponsor.contactEmail).toBe('test@example.com');
expect(presented?.sponsor.websiteUrl).toBe('https://example.com');
expect(presented?.sponsor.logoUrl).toBe('https://example.com/logo.png');
expect(presented?.sponsor.createdAt).toBeInstanceOf(Date);
const sponsor = presented!.sponsor;
expect(sponsor.name.toString()).toBe('Test Sponsor');
expect(sponsor.contactEmail.toString()).toBe('test@example.com');
expect(sponsor.websiteUrl?.toString()).toBe('https://example.com');
expect(sponsor.logoUrl?.toString()).toBe('https://example.com/logo.png');
expect(sponsor.createdAt.toDate()).toBeInstanceOf(Date);
expect(sponsorRepository.create).toHaveBeenCalledTimes(1);
});

View File

@@ -133,25 +133,42 @@ describe('FileProtestUseCase', () => {
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(mockProtestRepo.create).toHaveBeenCalledWith(
expect.objectContaining({
raceId: 'race1',
protestingDriverId: 'driver1',
accusedDriverId: 'driver2',
incident: { lap: 5, description: 'Collision' },
comment: 'Test comment',
proofVideoUrl: 'http://example.com/video',
status: 'pending',
})
);
expect(mockProtestRepo.create).toHaveBeenCalledTimes(1);
const created = (mockProtestRepo.create as unknown as Mock).mock.calls[0]?.[0] as unknown as {
raceId: { toString(): string };
protestingDriverId: { toString(): string };
accusedDriverId: { toString(): string };
comment?: string;
proofVideoUrl: { toString(): string };
status: { toString(): string };
incident: {
lap: { toNumber(): number };
description: { toString(): string };
timeInRace?: unknown;
};
};
expect(created.raceId.toString()).toBe('race1');
expect(created.protestingDriverId.toString()).toBe('driver1');
expect(created.accusedDriverId.toString()).toBe('driver2');
expect(created.comment).toBe('Test comment');
expect(created.proofVideoUrl.toString()).toBe('http://example.com/video');
expect(created.status.toString()).toBe('pending');
expect(created.incident.lap.toNumber()).toBe(5);
expect(created.incident.description.toString()).toBe('Collision');
expect(created.incident.timeInRace).toBeUndefined();
expect(output.present).toHaveBeenCalledTimes(1);
const presented = (output.present as unknown as Mock).mock.calls[0]?.[0] as FileProtestResult;
expect(presented.protest.raceId).toBe('race1');
expect(presented.protest.protestingDriverId).toBe('driver1');
expect(presented.protest.accusedDriverId).toBe('driver2');
expect(presented.protest.incident).toEqual({ lap: 5, description: 'Collision', timeInRace: undefined });
expect(presented.protest.raceId.toString()).toBe('race1');
expect(presented.protest.protestingDriverId.toString()).toBe('driver1');
expect(presented.protest.accusedDriverId.toString()).toBe('driver2');
expect(presented.protest.incident.lap.toNumber()).toBe(5);
expect(presented.protest.incident.description.toString()).toBe('Collision');
expect(presented.protest.incident.timeInRace).toBeUndefined();
expect(presented.protest.comment).toBe('Test comment');
expect(presented.protest.proofVideoUrl).toBe('http://example.com/video');
expect(presented.protest.proofVideoUrl).toBeDefined();
expect(presented.protest.proofVideoUrl!.toString()).toBe('http://example.com/video');
});
});

View File

@@ -1,22 +1,18 @@
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import {
GetAllLeaguesWithCapacityUseCase,
type GetAllLeaguesWithCapacityInput,
type GetAllLeaguesWithCapacityResult,
} from './GetAllLeaguesWithCapacityUseCase';
describe('GetAllLeaguesWithCapacityUseCase', () => {
let mockLeagueRepo: { findAll: Mock };
let mockMembershipRepo: { getLeagueMembers: Mock };
let output: UseCaseOutputPort<GetAllLeaguesWithCapacityResult> & { present: Mock };
beforeEach(() => {
mockLeagueRepo = { findAll: vi.fn() };
mockMembershipRepo = { getLeagueMembers: vi.fn() };
output = { present: vi.fn() } as unknown as typeof output;
});
it('should return leagues with capacity information', async () => {
@@ -32,34 +28,30 @@ describe('GetAllLeaguesWithCapacityUseCase', () => {
{ status: 'active', role: 'owner' },
{ status: 'inactive', role: 'member' },
];
const members2 = [
{ status: 'active', role: 'admin' },
];
const members2 = [{ status: 'active', role: 'admin' }];
mockLeagueRepo.findAll.mockResolvedValue([league1, league2]);
mockMembershipRepo.getLeagueMembers
.mockResolvedValueOnce(members1)
.mockResolvedValueOnce(members2);
mockMembershipRepo.getLeagueMembers.mockResolvedValueOnce(members1).mockResolvedValueOnce(members2);
const result = await useCase.execute({} as GetAllLeaguesWithCapacityInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const resultValue = result.unwrap();
expect(resultValue).toBeDefined();
expect(resultValue?.leagues).toHaveLength(2);
expect(resultValue.leagues).toHaveLength(2);
const [first, second] = resultValue?.leagues ?? [];
const first = resultValue.leagues[0]!;
const second = resultValue.leagues[1]!;
expect(first?.league).toEqual(league1);
expect(first?.currentDrivers).toBe(2);
expect(first?.maxDrivers).toBe(10);
expect(first.league).toEqual(league1);
expect(first.currentDrivers).toBe(2);
expect(first.maxDrivers).toBe(10);
expect(second?.league).toEqual(league2);
expect(second?.currentDrivers).toBe(1);
expect(second?.maxDrivers).toBe(20);
expect(second.league).toEqual(league2);
expect(second.currentDrivers).toBe(1);
expect(second.maxDrivers).toBe(20);
});
it('should return empty result when no leagues', async () => {
@@ -73,10 +65,9 @@ describe('GetAllLeaguesWithCapacityUseCase', () => {
const result = await useCase.execute({} as GetAllLeaguesWithCapacityInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const resultValue = result.unwrap();
expect(resultValue).toBeDefined();
expect(resultValue?.leagues).toEqual([]);
expect(resultValue.leagues).toEqual([]);
});
});

View File

@@ -56,21 +56,21 @@ describe('GetAllTeamsUseCase', () => {
const team1 = {
id: 'team1',
name: 'Team One',
tag: 'TO',
description: 'Description One',
ownerId: 'owner1',
leagues: ['league1'],
createdAt: new Date('2023-01-01T00:00:00Z'),
name: { props: 'Team One' },
tag: { props: 'TO' },
description: { props: 'Description One' },
ownerId: { toString: () => 'owner1' },
leagues: [{ toString: () => 'league1' }],
createdAt: { toDate: () => new Date('2023-01-01T00:00:00Z') },
};
const team2 = {
id: 'team2',
name: 'Team Two',
tag: 'TT',
description: 'Description Two',
ownerId: 'owner2',
leagues: ['league2'],
createdAt: new Date('2023-01-02T00:00:00Z'),
name: { props: 'Team Two' },
tag: { props: 'TT' },
description: { props: 'Description Two' },
ownerId: { toString: () => 'owner2' },
leagues: [{ toString: () => 'league2' }],
createdAt: { toDate: () => new Date('2023-01-02T00:00:00Z') },
};
mockTeamFindAll.mockResolvedValue([team1, team2]);

View File

@@ -1,14 +1,12 @@
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import {
GetDriversLeaderboardUseCase,
type GetDriversLeaderboardResult,
type GetDriversLeaderboardInput,
} from './GetDriversLeaderboardUseCase';
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
import type { IRankingService } from '../../domain/services/IRankingService';
import type { IDriverStatsService } from '../../domain/services/IDriverStatsService';
import type { Logger } from '@core/shared/application';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
describe('GetDriversLeaderboardUseCase', () => {
const mockDriverFindAll = vi.fn();
@@ -41,15 +39,6 @@ describe('GetDriversLeaderboardUseCase', () => {
error: vi.fn(),
};
let output: UseCaseOutputPort<GetDriversLeaderboardResult> & { present: Mock };
beforeEach(() => {
vi.clearAllMocks();
output = {
present: vi.fn(),
} as unknown as UseCaseOutputPort<GetDriversLeaderboardResult> & { present: Mock };
});
it('should return drivers leaderboard data', async () => {
const useCase = new GetDriversLeaderboardUseCase(
mockDriverRepo,
@@ -86,10 +75,7 @@ describe('GetDriversLeaderboardUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(output.present).toHaveBeenCalledTimes(1);
const presented = output.present.mock.calls[0]![0] as GetDriversLeaderboardResult;
const presented = result.unwrap();
expect(presented).toEqual({
items: [
@@ -98,7 +84,7 @@ describe('GetDriversLeaderboardUseCase', () => {
rating: 2500,
skillLevel: 'advanced',
racesCompleted: 10,
wins:5,
wins: 5,
podiums: 7,
isActive: true,
rank: 1,
@@ -139,10 +125,7 @@ describe('GetDriversLeaderboardUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(output.present).toHaveBeenCalledTimes(1);
const presented = output.present.mock.calls[0]![0] as GetDriversLeaderboardResult;
const presented = result.unwrap();
expect(presented).toEqual({
items: [],
@@ -174,10 +157,7 @@ describe('GetDriversLeaderboardUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(output.present).toHaveBeenCalledTimes(1);
const presented = output.present.mock.calls[0]![0] as GetDriversLeaderboardResult;
const presented = result.unwrap();
expect(presented).toEqual({
items: [
@@ -221,6 +201,5 @@ describe('GetDriversLeaderboardUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('Repository error');
}
expect(output.present).not.toHaveBeenCalled();
});
});

View File

@@ -127,17 +127,42 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
const input: GetLeagueDriverSeasonStatsInput = { leagueId: 'league-1' };
const mockStandings = [
{ driverId: 'driver-1', position: 1, points: 100, racesCompleted: 5 },
{ driverId: 'driver-2', position: 2, points: 80, racesCompleted: 5 },
{
driverId: { toString: () => 'driver-1' },
position: { toNumber: () => 1 },
points: { toNumber: () => 100 },
},
{
driverId: { toString: () => 'driver-2' },
position: { toNumber: () => 2 },
points: { toNumber: () => 80 },
},
];
const mockRaces = [
{ id: 'race-1' },
{ id: 'race-2' },
{ id: 'race-3' },
{ id: 'race-4' },
{ id: 'race-5' },
];
const mockRaces = [{ id: 'race-1' }, { id: 'race-2' }];
const mockPenalties = [
{ driverId: 'driver-1', status: 'applied', type: 'points_deduction', value: 10 },
];
const mockResults = [{ position: 1 }];
const mockRating = { rating: 1500, ratingChange: 50 };
const mockDriver = { id: 'driver-1', name: 'Driver One', teamId: 'team-1' };
const mockTeam = { id: 'team-1', name: 'Team One' };
const mockDriver1Results = [
{ position: { toNumber: () => 1 } },
{ position: { toNumber: () => 1 } },
{ position: { toNumber: () => 1 } },
{ position: { toNumber: () => 1 } },
{ position: { toNumber: () => 1 } },
];
const mockDriver2Results = [
{ position: { toNumber: () => 2 } },
{ position: { toNumber: () => 2 } },
{ position: { toNumber: () => 2 } },
{ position: { toNumber: () => 2 } },
{ position: { toNumber: () => 2 } },
];
mockStandingFindByLeagueId.mockResolvedValue(mockStandings);
mockRaceFindByLeagueId.mockResolvedValue(mockRaces);
@@ -145,14 +170,24 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
if (raceId === 'race-1') return Promise.resolve(mockPenalties);
return Promise.resolve([]);
});
mockDriverRatingGetRating.mockReturnValue(mockRating);
mockResultFindByDriverIdAndLeagueId.mockResolvedValue(mockResults);
mockDriverFindById.mockImplementation((id: string) => {
if (id === 'driver-1') return Promise.resolve(mockDriver);
if (id === 'driver-2') return Promise.resolve({ id: 'driver-2', name: 'Driver Two' });
mockDriverRatingGetRating.mockImplementation((driverId: string) => {
if (driverId === 'driver-1') return Promise.resolve(1500);
if (driverId === 'driver-2') return Promise.resolve(1400);
return Promise.resolve(null);
});
mockResultFindByDriverIdAndLeagueId.mockImplementation((driverId: string) => {
if (driverId === 'driver-1') return Promise.resolve(mockDriver1Results);
if (driverId === 'driver-2') return Promise.resolve(mockDriver2Results);
return Promise.resolve([]);
});
mockDriverFindById.mockImplementation((id: string) => {
if (id === 'driver-1') return Promise.resolve({ id: 'driver-1', name: { toString: () => 'Driver One' } });
if (id === 'driver-2') return Promise.resolve({ id: 'driver-2', name: { toString: () => 'Driver Two' } });
return Promise.resolve(null);
});
mockTeamFindById.mockResolvedValue(mockTeam);
const result = await useCase.execute(input);
@@ -163,25 +198,26 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
const presented = output.present.mock.calls[0]?.[0] as GetLeagueDriverSeasonStatsResult;
expect(presented.leagueId).toBe('league-1');
expect(presented.stats).toHaveLength(2);
expect(presented.stats[0]).toEqual({
leagueId: 'league-1',
driverId: 'driver-1',
position: 1,
driverName: 'Driver One',
teamId: 'team-1',
teamName: 'Team One',
teamId: undefined,
teamName: undefined,
totalPoints: 100,
basePoints: 90,
basePoints: 110,
penaltyPoints: -10,
bonusPoints: 0,
pointsPerRace: 20,
racesStarted: 1,
racesFinished: 1,
racesStarted: 5,
racesFinished: 5,
dnfs: 0,
noShows: 1,
noShows: 0,
avgFinish: 1,
rating: 1500,
ratingChange: 50,
ratingChange: null,
});
});
@@ -189,20 +225,21 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
const input: GetLeagueDriverSeasonStatsInput = { leagueId: 'league-1' };
const mockStandings = [
{ driverId: 'driver-1', position: 1, points: 100, racesCompleted: 5 },
{
driverId: { toString: () => 'driver-1' },
position: { toNumber: () => 1 },
points: { toNumber: () => 100 },
},
];
const mockRaces = [{ id: 'race-1' }];
const mockResults = [{ position: 1 }];
const mockRating = { rating: null, ratingChange: null };
const mockDriver = { id: 'driver-1', name: 'Driver One' };
const mockResults = [{ position: { toNumber: () => 1 } }];
mockStandingFindByLeagueId.mockResolvedValue(mockStandings);
mockRaceFindByLeagueId.mockResolvedValue(mockRaces);
mockPenaltyFindByRaceId.mockResolvedValue([]);
mockDriverRatingGetRating.mockReturnValue(mockRating);
mockDriverRatingGetRating.mockResolvedValue(null);
mockResultFindByDriverIdAndLeagueId.mockResolvedValue(mockResults);
mockDriverFindById.mockResolvedValue(mockDriver);
mockTeamFindById.mockResolvedValue(null);
mockDriverFindById.mockResolvedValue({ id: 'driver-1', name: { toString: () => 'Driver One' } });
const result = await useCase.execute(input);

View File

@@ -52,7 +52,13 @@ describe('GetLeagueJoinRequestsUseCase', () => {
const input: GetLeagueJoinRequestsInput = { leagueId };
const joinRequests = [
{ id: 'req-1', leagueId, driverId: 'driver-1', requestedAt: new Date(), message: 'msg' },
{
id: 'req-1',
leagueId: { toString: () => leagueId },
driverId: { toString: () => 'driver-1' },
requestedAt: { toDate: () => new Date() },
message: 'msg',
},
];
const driver = Driver.create({

View File

@@ -60,7 +60,7 @@ describe('GetPendingSponsorshipRequestsUseCase', () => {
id: 'sponsor-1',
name: 'Test Sponsor',
contactEmail: 'test@example.com',
logoUrl: 'logo.png',
logoUrl: 'https://example.com/logo.png',
});
sponsorshipRequestRepo.findPendingByEntity.mockResolvedValue([request]);
@@ -81,7 +81,8 @@ describe('GetPendingSponsorshipRequestsUseCase', () => {
expect(presented.requests).toHaveLength(1);
const summary = presented.requests[0];
expect(summary).toBeDefined();
expect(summary!.sponsor?.name).toBe('Test Sponsor');
expect(summary!.sponsor).toBeDefined();
expect(summary!.sponsor!.name.toString()).toBe('Test Sponsor');
expect(summary!.financials.offeredAmount.amount).toBe(10000);
expect(summary!.financials.offeredAmount.currency).toBe('USD');
});

View File

@@ -52,7 +52,7 @@ describe('GetRaceDetailUseCase', () => {
leagueId: 'league-1',
track: 'Track 1',
car: 'Car 1',
scheduledAt: new Date('2023-01-01T10:00:00Z'),
scheduledAt: new Date('2099-01-01T10:00:00Z'),
sessionType: 'race' as const,
status: 'scheduled' as const,
strengthOfField: 1500,

View File

@@ -32,7 +32,7 @@ describe('GetSponsorsUseCase', () => {
id: 'sponsor-1',
name: 'Sponsor One',
contactEmail: 'one@example.com',
logoUrl: 'logo1.png',
logoUrl: 'https://example.com/logo1.png',
}),
Sponsor.create({
id: 'sponsor-2',

View File

@@ -145,7 +145,7 @@ describe('GetTeamDetailsUseCase', () => {
{ message: string }
>;
expect(errorResult.code).toBe('REPOSITORY_ERROR');
expect(errorResult.details?.message).toBe('Failed to load team details');
expect(errorResult.details?.message).toBe('DB error');
expect(output.present).not.toHaveBeenCalled();
});
});

View File

@@ -143,7 +143,7 @@ describe('GetTeamsLeaderboardUseCase', () => {
{ message: string }
>;
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('Failed to load teams leaderboard');
expect(err.details.message).toBe('Repository error');
expect(output.present).not.toHaveBeenCalled();
});
});

View File

@@ -199,17 +199,26 @@ describe('ImportRaceResultsApiUseCase', () => {
expect(presented.resultsRecorded).toBe(1);
expect(presented.errors).toEqual([]);
expect(resultRepository.createMany).toHaveBeenCalledWith([
expect.objectContaining({
id: 'result-1',
raceId: 'race-1',
driverId: 'driver-1',
position: 1,
fastestLap: 100,
incidents: 0,
startPosition: 1,
}),
]);
expect(resultRepository.createMany).toHaveBeenCalledTimes(1);
const createdManyArg = resultRepository.createMany.mock.calls[0]?.[0] as unknown[];
expect(createdManyArg).toHaveLength(1);
const created = createdManyArg[0] as unknown as {
id: string;
raceId: { toString(): string };
driverId: { toString(): string };
position: { toNumber(): number };
fastestLap: { toNumber(): number };
incidents: { toNumber(): number };
startPosition: { toNumber(): number };
};
expect(created.id).toBe('result-1');
expect(created.raceId.toString()).toBe('race-1');
expect(created.driverId.toString()).toBe('driver-1');
expect(created.position.toNumber()).toBe(1);
expect(created.fastestLap.toNumber()).toBe(100);
expect(created.incidents.toNumber()).toBe(0);
expect(created.startPosition.toNumber()).toBe(1);
expect(standingRepository.recalculate).toHaveBeenCalledWith('league-1');
});
});

View File

@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { JoinLeagueUseCase, type JoinLeagueResult, type JoinLeagueInput, type JoinLeagueErrorCode } from './JoinLeagueUseCase';
import { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import { LeagueMembership } from '../../domain/entities/LeagueMembership';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
@@ -44,24 +45,16 @@ describe('JoinLeagueUseCase', () => {
const command: JoinLeagueInput = { leagueId: 'league-1', driverId: 'driver-1' };
membershipRepository.getMembership.mockResolvedValue(null);
membershipRepository.saveMembership.mockResolvedValue({
id: 'membership-1',
leagueId: {
value: 'league-1',
},
driverId: {
value: 'driver-1',
},
role: {
value: 'member',
},
status: {
value: 'active',
},
joinedAt: {
value: expect.any(Date),
},
});
membershipRepository.saveMembership.mockResolvedValue(
LeagueMembership.create({
id: 'membership-1',
leagueId: 'league-1',
driverId: 'driver-1',
role: 'member',
status: 'active',
joinedAt: new Date(),
}),
);
const result = await useCase.execute(command);

View File

@@ -184,7 +184,7 @@ describe('ReviewProtestUseCase', () => {
expect(result.isErr()).toBe(true);
const error = result.unwrapErr() as ApplicationErrorCode<ReviewProtestErrorCode, { message: string }>;
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details?.message).toBe('Failed to review protest');
expect(error.details?.message).toBe('DB error');
expect(output.present).not.toHaveBeenCalled();
});
});

View File

@@ -74,7 +74,7 @@ describe('SendFinalResultsUseCase', () => {
};
const mockLeague = { id: 'league-1' };
const mockMembership = { role: 'steward' };
const mockMembership = { role: { toString: () => 'steward' } };
leagueRepository.findById.mockResolvedValue(mockLeague);
raceEventRepository.findById.mockResolvedValue(mockRaceEvent);
@@ -82,15 +82,15 @@ describe('SendFinalResultsUseCase', () => {
const mockResults = [
{
driverId: 'driver-1',
position: 1,
incidents: 0,
driverId: { toString: () => 'driver-1' },
position: { toNumber: () => 1 },
incidents: { toNumber: () => 0 },
getPositionChange: vi.fn().mockReturnValue(2),
},
{
driverId: 'driver-2',
position: 2,
incidents: 1,
driverId: { toString: () => 'driver-2' },
position: { toNumber: () => 2 },
incidents: { toNumber: () => 1 },
getPositionChange: vi.fn().mockReturnValue(-1),
},
];
@@ -165,6 +165,7 @@ describe('SendFinalResultsUseCase', () => {
status: 'in_progress',
getMainRaceSession: vi.fn(),
});
membershipRepository.getMembership.mockResolvedValue({ role: { toString: () => 'steward' } });
const result = await useCase.execute(createInput());
@@ -183,6 +184,7 @@ describe('SendFinalResultsUseCase', () => {
status: 'closed',
getMainRaceSession: vi.fn().mockReturnValue(undefined),
});
membershipRepository.getMembership.mockResolvedValue({ role: { toString: () => 'steward' } });
const result = await useCase.execute(createInput());

View File

@@ -86,9 +86,9 @@ describe('SendPerformanceSummaryUseCase', () => {
const mockResults = [
{
driverId: 'driver-1',
position: 1,
incidents: 0,
driverId: { toString: () => 'driver-1' },
position: { toNumber: () => 1 },
incidents: { toNumber: () => 0 },
getPositionChange: vi.fn().mockReturnValue(2),
},
];

View File

@@ -13,11 +13,11 @@ describe('UpdateLeagueMemberRoleUseCase', () => {
it('updates league member role successfully', async () => {
const mockMembership = {
id: 'league-1:driver-1',
leagueId: 'league-1',
driverId: 'driver-1',
role: 'member',
status: 'active',
joinedAt: new Date(),
leagueId: { toString: () => 'league-1' },
driverId: { toString: () => 'driver-1' },
role: { toString: () => 'member' },
status: { toString: () => 'active' },
joinedAt: { toDate: () => new Date() },
};
const mockLeagueMembershipRepository = {
@@ -112,7 +112,7 @@ describe('UpdateLeagueMemberRoleUseCase', () => {
>;
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details.message).toBe('Failed to update league member role');
expect(error.details.message).toBe('Database connection failed');
expect(output.present).not.toHaveBeenCalled();
});
});