core tests
This commit is contained in:
57
core/racing/application/use-cases/DriverStatsUseCase.test.ts
Normal file
57
core/racing/application/use-cases/DriverStatsUseCase.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { DriverStatsUseCase, type DriverStats } from './DriverStatsUseCase';
|
||||
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
|
||||
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
|
||||
import type { DriverStatsRepository } from '../../domain/repositories/DriverStatsRepository';
|
||||
import type { Logger } from '@core/shared/domain/Logger';
|
||||
|
||||
describe('DriverStatsUseCase', () => {
|
||||
const mockResultRepository = {} as ResultRepository;
|
||||
const mockStandingRepository = {} as StandingRepository;
|
||||
const mockDriverStatsRepository = {
|
||||
getDriverStats: vi.fn(),
|
||||
} as unknown as DriverStatsRepository;
|
||||
const mockLogger = {
|
||||
debug: vi.fn(),
|
||||
} as unknown as Logger;
|
||||
|
||||
const useCase = new DriverStatsUseCase(
|
||||
mockResultRepository,
|
||||
mockStandingRepository,
|
||||
mockDriverStatsRepository,
|
||||
mockLogger
|
||||
);
|
||||
|
||||
it('should return driver stats when found', async () => {
|
||||
const mockStats: DriverStats = {
|
||||
rating: 1500,
|
||||
safetyRating: 4.5,
|
||||
sportsmanshipRating: 4.8,
|
||||
totalRaces: 10,
|
||||
wins: 2,
|
||||
podiums: 5,
|
||||
dnfs: 0,
|
||||
avgFinish: 3.5,
|
||||
bestFinish: 1,
|
||||
worstFinish: 8,
|
||||
consistency: 0.9,
|
||||
experienceLevel: 'Intermediate',
|
||||
overallRank: 42,
|
||||
};
|
||||
vi.mocked(mockDriverStatsRepository.getDriverStats).mockResolvedValue(mockStats);
|
||||
|
||||
const result = await useCase.getDriverStats('driver-1');
|
||||
|
||||
expect(result).toEqual(mockStats);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Getting stats for driver driver-1');
|
||||
expect(mockDriverStatsRepository.getDriverStats).toHaveBeenCalledWith('driver-1');
|
||||
});
|
||||
|
||||
it('should return null when stats are not found', async () => {
|
||||
vi.mocked(mockDriverStatsRepository.getDriverStats).mockResolvedValue(null);
|
||||
|
||||
const result = await useCase.getDriverStats('non-existent');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
43
core/racing/application/use-cases/GetDriverUseCase.test.ts
Normal file
43
core/racing/application/use-cases/GetDriverUseCase.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { GetDriverUseCase } from './GetDriverUseCase';
|
||||
import { Result } from '@core/shared/domain/Result';
|
||||
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
|
||||
import type { Driver } from '../../domain/entities/Driver';
|
||||
|
||||
describe('GetDriverUseCase', () => {
|
||||
const mockDriverRepository = {
|
||||
findById: vi.fn(),
|
||||
} as unknown as DriverRepository;
|
||||
|
||||
const useCase = new GetDriverUseCase(mockDriverRepository);
|
||||
|
||||
it('should return a driver when found', async () => {
|
||||
const mockDriver = { id: 'driver-1', name: 'John Doe' } as unknown as Driver;
|
||||
vi.mocked(mockDriverRepository.findById).mockResolvedValue(mockDriver);
|
||||
|
||||
const result = await useCase.execute({ driverId: 'driver-1' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(result.unwrap()).toBe(mockDriver);
|
||||
expect(mockDriverRepository.findById).toHaveBeenCalledWith('driver-1');
|
||||
});
|
||||
|
||||
it('should return null when driver is not found', async () => {
|
||||
vi.mocked(mockDriverRepository.findById).mockResolvedValue(null);
|
||||
|
||||
const result = await useCase.execute({ driverId: 'non-existent' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(result.unwrap()).toBeNull();
|
||||
});
|
||||
|
||||
it('should return an error when repository throws', async () => {
|
||||
const error = new Error('Repository error');
|
||||
vi.mocked(mockDriverRepository.findById).mockRejectedValue(error);
|
||||
|
||||
const result = await useCase.execute({ driverId: 'driver-1' });
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.error).toBe(error);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { GetTeamsLeaderboardUseCase } from './GetTeamsLeaderboardUseCase';
|
||||
import { Result } from '@core/shared/domain/Result';
|
||||
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
|
||||
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
|
||||
import type { Logger } from '@core/shared/domain/Logger';
|
||||
import type { Team } from '../../domain/entities/Team';
|
||||
|
||||
describe('GetTeamsLeaderboardUseCase', () => {
|
||||
const mockTeamRepository = {
|
||||
findAll: vi.fn(),
|
||||
} as unknown as TeamRepository;
|
||||
|
||||
const mockTeamMembershipRepository = {
|
||||
getTeamMembers: vi.fn(),
|
||||
} as unknown as TeamMembershipRepository;
|
||||
|
||||
const mockGetDriverStats = vi.fn();
|
||||
|
||||
const mockLogger = {
|
||||
error: vi.fn(),
|
||||
} as unknown as Logger;
|
||||
|
||||
const useCase = new GetTeamsLeaderboardUseCase(
|
||||
mockTeamRepository,
|
||||
mockTeamMembershipRepository,
|
||||
mockGetDriverStats,
|
||||
mockLogger
|
||||
);
|
||||
|
||||
it('should return teams leaderboard with calculated stats', async () => {
|
||||
const mockTeam1 = { id: 'team-1', name: 'Team 1' } as unknown as Team;
|
||||
const mockTeam2 = { id: 'team-2', name: 'Team 2' } as unknown as Team;
|
||||
vi.mocked(mockTeamRepository.findAll).mockResolvedValue([mockTeam1, mockTeam2]);
|
||||
|
||||
vi.mocked(mockTeamMembershipRepository.getTeamMembers).mockImplementation(async (teamId) => {
|
||||
if (teamId === 'team-1') return [{ driverId: 'driver-1' }, { driverId: 'driver-2' }] as any;
|
||||
if (teamId === 'team-2') return [{ driverId: 'driver-3' }] as any;
|
||||
return [];
|
||||
});
|
||||
|
||||
mockGetDriverStats.mockImplementation((driverId) => {
|
||||
if (driverId === 'driver-1') return { rating: 1000, wins: 1, totalRaces: 5 };
|
||||
if (driverId === 'driver-2') return { rating: 2000, wins: 2, totalRaces: 10 };
|
||||
if (driverId === 'driver-3') return { rating: 1500, wins: 0, totalRaces: 2 };
|
||||
return null;
|
||||
});
|
||||
|
||||
const result = await useCase.execute({ leagueId: 'league-1' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const data = result.unwrap();
|
||||
expect(data.items).toHaveLength(2);
|
||||
|
||||
const item1 = data.items.find(i => i.team.id === 'team-1');
|
||||
expect(item1?.rating).toBe(1500); // (1000 + 2000) / 2
|
||||
expect(item1?.totalWins).toBe(3);
|
||||
expect(item1?.totalRaces).toBe(15);
|
||||
|
||||
const item2 = data.items.find(i => i.team.id === 'team-2');
|
||||
expect(item2?.rating).toBe(1500);
|
||||
expect(item2?.totalWins).toBe(0);
|
||||
expect(item2?.totalRaces).toBe(2);
|
||||
|
||||
expect(data.topItems).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should handle teams with no members', async () => {
|
||||
const mockTeam = { id: 'team-empty', name: 'Empty Team' } as unknown as Team;
|
||||
vi.mocked(mockTeamRepository.findAll).mockResolvedValue([mockTeam]);
|
||||
vi.mocked(mockTeamMembershipRepository.getTeamMembers).mockResolvedValue([]);
|
||||
|
||||
const result = await useCase.execute({ leagueId: 'league-1' });
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const data = result.unwrap();
|
||||
expect(data.items[0].rating).toBeNull();
|
||||
expect(data.items[0].performanceLevel).toBe('beginner');
|
||||
});
|
||||
|
||||
it('should return error when repository fails', async () => {
|
||||
vi.mocked(mockTeamRepository.findAll).mockRejectedValue(new Error('DB Error'));
|
||||
|
||||
const result = await useCase.execute({ leagueId: 'league-1' });
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.error.code).toBe('REPOSITORY_ERROR');
|
||||
expect(mockLogger.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
59
core/racing/application/use-cases/RankingUseCase.test.ts
Normal file
59
core/racing/application/use-cases/RankingUseCase.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { RankingUseCase, type DriverRanking } from './RankingUseCase';
|
||||
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
|
||||
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
|
||||
import type { DriverStatsRepository } from '../../domain/repositories/DriverStatsRepository';
|
||||
import type { Logger } from '@core/shared/domain/Logger';
|
||||
|
||||
describe('RankingUseCase', () => {
|
||||
const mockStandingRepository = {} as StandingRepository;
|
||||
const mockDriverRepository = {} as DriverRepository;
|
||||
const mockDriverStatsRepository = {
|
||||
getAllStats: vi.fn(),
|
||||
} as unknown as DriverStatsRepository;
|
||||
const mockLogger = {
|
||||
debug: vi.fn(),
|
||||
} as unknown as Logger;
|
||||
|
||||
const useCase = new RankingUseCase(
|
||||
mockStandingRepository,
|
||||
mockDriverRepository,
|
||||
mockDriverStatsRepository,
|
||||
mockLogger
|
||||
);
|
||||
|
||||
it('should return all driver rankings', async () => {
|
||||
const mockStatsMap = new Map([
|
||||
['driver-1', { rating: 1500, wins: 2, totalRaces: 10, overallRank: 1 }],
|
||||
['driver-2', { rating: 1200, wins: 0, totalRaces: 5, overallRank: 2 }],
|
||||
]);
|
||||
vi.mocked(mockDriverStatsRepository.getAllStats).mockResolvedValue(mockStatsMap as any);
|
||||
|
||||
const result = await useCase.getAllDriverRankings();
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContainEqual({
|
||||
driverId: 'driver-1',
|
||||
rating: 1500,
|
||||
wins: 2,
|
||||
totalRaces: 10,
|
||||
overallRank: 1,
|
||||
});
|
||||
expect(result).toContainEqual({
|
||||
driverId: 'driver-2',
|
||||
rating: 1200,
|
||||
wins: 0,
|
||||
totalRaces: 5,
|
||||
overallRank: 2,
|
||||
});
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Getting all driver rankings');
|
||||
});
|
||||
|
||||
it('should return empty array when no stats exist', async () => {
|
||||
vi.mocked(mockDriverStatsRepository.getAllStats).mockResolvedValue(new Map());
|
||||
|
||||
const result = await useCase.getAllDriverRankings();
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
44
core/racing/application/utils/RaceResultGenerator.test.ts
Normal file
44
core/racing/application/utils/RaceResultGenerator.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { RaceResultGenerator } from './RaceResultGenerator';
|
||||
|
||||
describe('RaceResultGenerator', () => {
|
||||
it('should generate results for all drivers', () => {
|
||||
const raceId = 'race-1';
|
||||
const driverIds = ['d1', 'd2', 'd3'];
|
||||
const driverRatings = new Map([
|
||||
['d1', 2000],
|
||||
['d2', 1500],
|
||||
['d3', 1000],
|
||||
]);
|
||||
|
||||
const results = RaceResultGenerator.generateRaceResults(raceId, driverIds, driverRatings);
|
||||
|
||||
expect(results).toHaveLength(3);
|
||||
const resultDriverIds = results.map(r => r.driverId.toString());
|
||||
expect(resultDriverIds).toContain('d1');
|
||||
expect(resultDriverIds).toContain('d2');
|
||||
expect(resultDriverIds).toContain('d3');
|
||||
|
||||
results.forEach(r => {
|
||||
expect(r.raceId.toString()).toBe(raceId);
|
||||
expect(r.position.toNumber()).toBeGreaterThan(0);
|
||||
expect(r.position.toNumber()).toBeLessThanOrEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide incident descriptions', () => {
|
||||
expect(RaceResultGenerator.getIncidentDescription(0)).toBe('Clean race');
|
||||
expect(RaceResultGenerator.getIncidentDescription(1)).toBe('Track limits violation');
|
||||
expect(RaceResultGenerator.getIncidentDescription(2)).toBe('Contact with another car');
|
||||
expect(RaceResultGenerator.getIncidentDescription(3)).toBe('Off-track incident');
|
||||
expect(RaceResultGenerator.getIncidentDescription(4)).toBe('Collision requiring safety car');
|
||||
expect(RaceResultGenerator.getIncidentDescription(5)).toBe('5 incidents');
|
||||
});
|
||||
|
||||
it('should calculate incident penalty points', () => {
|
||||
expect(RaceResultGenerator.getIncidentPenaltyPoints(0)).toBe(0);
|
||||
expect(RaceResultGenerator.getIncidentPenaltyPoints(1)).toBe(0);
|
||||
expect(RaceResultGenerator.getIncidentPenaltyPoints(2)).toBe(2);
|
||||
expect(RaceResultGenerator.getIncidentPenaltyPoints(3)).toBe(4);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { RaceResultGeneratorWithIncidents } from './RaceResultGeneratorWithIncidents';
|
||||
import { RaceIncidents } from '../../domain/value-objects/RaceIncidents';
|
||||
|
||||
describe('RaceResultGeneratorWithIncidents', () => {
|
||||
it('should generate results for all drivers', () => {
|
||||
const raceId = 'race-1';
|
||||
const driverIds = ['d1', 'd2'];
|
||||
const driverRatings = new Map([
|
||||
['d1', 2000],
|
||||
['d2', 1500],
|
||||
]);
|
||||
|
||||
const results = RaceResultGeneratorWithIncidents.generateRaceResults(raceId, driverIds, driverRatings);
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
results.forEach(r => {
|
||||
expect(r.raceId.toString()).toBe(raceId);
|
||||
expect(r.incidents).toBeInstanceOf(RaceIncidents);
|
||||
});
|
||||
});
|
||||
|
||||
it('should calculate incident penalty points', () => {
|
||||
const incidents = new RaceIncidents([
|
||||
{ type: 'contact', lap: 1, description: 'desc', penaltyPoints: 2 },
|
||||
{ type: 'unsafe_rejoin', lap: 5, description: 'desc', penaltyPoints: 3 },
|
||||
]);
|
||||
|
||||
expect(RaceResultGeneratorWithIncidents.getIncidentPenaltyPoints(incidents)).toBe(5);
|
||||
});
|
||||
|
||||
it('should get incident description', () => {
|
||||
const incidents = new RaceIncidents([
|
||||
{ type: 'contact', lap: 1, description: 'desc', penaltyPoints: 2 },
|
||||
]);
|
||||
|
||||
const description = RaceResultGeneratorWithIncidents.getIncidentDescription(incidents);
|
||||
expect(description).toContain('1 incidents');
|
||||
});
|
||||
});
|
||||
75
core/racing/domain/services/ChampionshipAggregator.test.ts
Normal file
75
core/racing/domain/services/ChampionshipAggregator.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { ChampionshipAggregator } from './ChampionshipAggregator';
|
||||
import type { DropScoreApplier } from './DropScoreApplier';
|
||||
import { Points } from '../value-objects/Points';
|
||||
|
||||
describe('ChampionshipAggregator', () => {
|
||||
const mockDropScoreApplier = {
|
||||
apply: vi.fn(),
|
||||
} as unknown as DropScoreApplier;
|
||||
|
||||
const aggregator = new ChampionshipAggregator(mockDropScoreApplier);
|
||||
|
||||
it('should aggregate points and sort standings by total points', () => {
|
||||
const seasonId = 'season-1';
|
||||
const championship = {
|
||||
id: 'champ-1',
|
||||
dropScorePolicy: { strategy: 'none' },
|
||||
} as any;
|
||||
|
||||
const eventPointsByEventId = {
|
||||
'event-1': [
|
||||
{
|
||||
participant: { id: 'p1', type: 'driver' },
|
||||
totalPoints: 10,
|
||||
basePoints: 10,
|
||||
bonusPoints: 0,
|
||||
penaltyPoints: 0
|
||||
},
|
||||
{
|
||||
participant: { id: 'p2', type: 'driver' },
|
||||
totalPoints: 20,
|
||||
basePoints: 20,
|
||||
bonusPoints: 0,
|
||||
penaltyPoints: 0
|
||||
},
|
||||
],
|
||||
'event-2': [
|
||||
{
|
||||
participant: { id: 'p1', type: 'driver' },
|
||||
totalPoints: 15,
|
||||
basePoints: 15,
|
||||
bonusPoints: 0,
|
||||
penaltyPoints: 0
|
||||
},
|
||||
],
|
||||
} as any;
|
||||
|
||||
vi.mocked(mockDropScoreApplier.apply).mockImplementation((policy, events) => {
|
||||
const total = events.reduce((sum, e) => sum + e.points, 0);
|
||||
return {
|
||||
totalPoints: total,
|
||||
counted: events,
|
||||
dropped: [],
|
||||
};
|
||||
});
|
||||
|
||||
const standings = aggregator.aggregate({
|
||||
seasonId,
|
||||
championship,
|
||||
eventPointsByEventId,
|
||||
});
|
||||
|
||||
expect(standings).toHaveLength(2);
|
||||
|
||||
// p1 should be first (10 + 15 = 25 points)
|
||||
expect(standings[0].participant.id).toBe('p1');
|
||||
expect(standings[0].totalPoints.toNumber()).toBe(25);
|
||||
expect(standings[0].position.toNumber()).toBe(1);
|
||||
|
||||
// p2 should be second (20 points)
|
||||
expect(standings[1].participant.id).toBe('p2');
|
||||
expect(standings[1].totalPoints.toNumber()).toBe(20);
|
||||
expect(standings[1].position.toNumber()).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -59,7 +59,7 @@ export class ChampionshipAggregator {
|
||||
totalPoints,
|
||||
resultsCounted,
|
||||
resultsDropped,
|
||||
position: 0,
|
||||
position: 1,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
74
core/racing/domain/services/SeasonScheduleGenerator.test.ts
Normal file
74
core/racing/domain/services/SeasonScheduleGenerator.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { SeasonScheduleGenerator } from './SeasonScheduleGenerator';
|
||||
import { SeasonSchedule } from '../value-objects/SeasonSchedule';
|
||||
import { RecurrenceStrategy } from '../value-objects/RecurrenceStrategy';
|
||||
import { RaceTimeOfDay } from '../value-objects/RaceTimeOfDay';
|
||||
import { WeekdaySet } from '../value-objects/WeekdaySet';
|
||||
import { LeagueTimezone } from '../value-objects/LeagueTimezone';
|
||||
import { MonthlyRecurrencePattern } from '../value-objects/MonthlyRecurrencePattern';
|
||||
|
||||
describe('SeasonScheduleGenerator', () => {
|
||||
it('should generate weekly slots', () => {
|
||||
const startDate = new Date(2024, 0, 1); // Monday, Jan 1st 2024
|
||||
const schedule = new SeasonSchedule({
|
||||
startDate,
|
||||
plannedRounds: 4,
|
||||
timeOfDay: new RaceTimeOfDay(20, 0),
|
||||
timezone: LeagueTimezone.create('UTC'),
|
||||
recurrence: RecurrenceStrategy.weekly(WeekdaySet.fromArray(['Mon'])),
|
||||
});
|
||||
|
||||
const slots = SeasonScheduleGenerator.generateSlots(schedule);
|
||||
|
||||
expect(slots).toHaveLength(4);
|
||||
expect(slots[0].roundNumber).toBe(1);
|
||||
expect(slots[0].scheduledAt.getHours()).toBe(20);
|
||||
expect(slots[0].scheduledAt.getMinutes()).toBe(0);
|
||||
expect(slots[0].scheduledAt.getFullYear()).toBe(2024);
|
||||
expect(slots[0].scheduledAt.getMonth()).toBe(0);
|
||||
expect(slots[0].scheduledAt.getDate()).toBe(1);
|
||||
|
||||
expect(slots[1].roundNumber).toBe(2);
|
||||
expect(slots[1].scheduledAt.getDate()).toBe(8);
|
||||
expect(slots[2].roundNumber).toBe(3);
|
||||
expect(slots[2].scheduledAt.getDate()).toBe(15);
|
||||
expect(slots[3].roundNumber).toBe(4);
|
||||
expect(slots[3].scheduledAt.getDate()).toBe(22);
|
||||
});
|
||||
|
||||
it('should generate slots every 2 weeks', () => {
|
||||
const startDate = new Date(2024, 0, 1);
|
||||
const schedule = new SeasonSchedule({
|
||||
startDate,
|
||||
plannedRounds: 2,
|
||||
timeOfDay: new RaceTimeOfDay(20, 0),
|
||||
timezone: LeagueTimezone.create('UTC'),
|
||||
recurrence: RecurrenceStrategy.everyNWeeks(2, WeekdaySet.fromArray(['Mon'])),
|
||||
});
|
||||
|
||||
const slots = SeasonScheduleGenerator.generateSlots(schedule);
|
||||
|
||||
expect(slots).toHaveLength(2);
|
||||
expect(slots[0].scheduledAt.getDate()).toBe(1);
|
||||
expect(slots[1].scheduledAt.getDate()).toBe(15);
|
||||
});
|
||||
|
||||
it('should generate monthly slots (nth weekday)', () => {
|
||||
const startDate = new Date(2024, 0, 1);
|
||||
const schedule = new SeasonSchedule({
|
||||
startDate,
|
||||
plannedRounds: 2,
|
||||
timeOfDay: new RaceTimeOfDay(20, 0),
|
||||
timezone: LeagueTimezone.create('UTC'),
|
||||
recurrence: RecurrenceStrategy.monthlyNthWeekday(MonthlyRecurrencePattern.create(1, 'Mon')),
|
||||
});
|
||||
|
||||
const slots = SeasonScheduleGenerator.generateSlots(schedule);
|
||||
|
||||
expect(slots).toHaveLength(2);
|
||||
expect(slots[0].scheduledAt.getMonth()).toBe(0);
|
||||
expect(slots[0].scheduledAt.getDate()).toBe(1);
|
||||
expect(slots[1].scheduledAt.getMonth()).toBe(1);
|
||||
expect(slots[1].scheduledAt.getDate()).toBe(5);
|
||||
});
|
||||
});
|
||||
50
core/racing/domain/services/SkillLevelService.test.ts
Normal file
50
core/racing/domain/services/SkillLevelService.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { SkillLevelService } from './SkillLevelService';
|
||||
|
||||
describe('SkillLevelService', () => {
|
||||
describe('getSkillLevel', () => {
|
||||
it('should return pro for rating >= 3000', () => {
|
||||
expect(SkillLevelService.getSkillLevel(3000)).toBe('pro');
|
||||
expect(SkillLevelService.getSkillLevel(5000)).toBe('pro');
|
||||
});
|
||||
|
||||
it('should return advanced for rating >= 2500 and < 3000', () => {
|
||||
expect(SkillLevelService.getSkillLevel(2500)).toBe('advanced');
|
||||
expect(SkillLevelService.getSkillLevel(2999)).toBe('advanced');
|
||||
});
|
||||
|
||||
it('should return intermediate for rating >= 1800 and < 2500', () => {
|
||||
expect(SkillLevelService.getSkillLevel(1800)).toBe('intermediate');
|
||||
expect(SkillLevelService.getSkillLevel(2499)).toBe('intermediate');
|
||||
});
|
||||
|
||||
it('should return beginner for rating < 1800', () => {
|
||||
expect(SkillLevelService.getSkillLevel(1799)).toBe('beginner');
|
||||
expect(SkillLevelService.getSkillLevel(500)).toBe('beginner');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTeamPerformanceLevel', () => {
|
||||
it('should return beginner for null rating', () => {
|
||||
expect(SkillLevelService.getTeamPerformanceLevel(null)).toBe('beginner');
|
||||
});
|
||||
|
||||
it('should return pro for rating >= 4500', () => {
|
||||
expect(SkillLevelService.getTeamPerformanceLevel(4500)).toBe('pro');
|
||||
});
|
||||
|
||||
it('should return advanced for rating >= 3000 and < 4500', () => {
|
||||
expect(SkillLevelService.getTeamPerformanceLevel(3000)).toBe('advanced');
|
||||
expect(SkillLevelService.getTeamPerformanceLevel(4499)).toBe('advanced');
|
||||
});
|
||||
|
||||
it('should return intermediate for rating >= 2000 and < 3000', () => {
|
||||
expect(SkillLevelService.getTeamPerformanceLevel(2000)).toBe('intermediate');
|
||||
expect(SkillLevelService.getTeamPerformanceLevel(2999)).toBe('intermediate');
|
||||
});
|
||||
|
||||
it('should return beginner for rating < 2000', () => {
|
||||
expect(SkillLevelService.getTeamPerformanceLevel(1999)).toBe('beginner');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { AverageStrengthOfFieldCalculator } from './StrengthOfFieldCalculator';
|
||||
|
||||
describe('AverageStrengthOfFieldCalculator', () => {
|
||||
const calculator = new AverageStrengthOfFieldCalculator();
|
||||
|
||||
it('should calculate average SOF and round it', () => {
|
||||
const ratings = [
|
||||
{ driverId: 'd1', rating: 1500 },
|
||||
{ driverId: 'd2', rating: 2000 },
|
||||
{ driverId: 'd3', rating: 1750 },
|
||||
];
|
||||
|
||||
const sof = calculator.calculate(ratings);
|
||||
|
||||
expect(sof).toBe(1750);
|
||||
});
|
||||
|
||||
it('should handle rounding correctly', () => {
|
||||
const ratings = [
|
||||
{ driverId: 'd1', rating: 1000 },
|
||||
{ driverId: 'd2', rating: 1001 },
|
||||
];
|
||||
|
||||
const sof = calculator.calculate(ratings);
|
||||
|
||||
expect(sof).toBe(1001); // (1000 + 1001) / 2 = 1000.5 -> 1001
|
||||
});
|
||||
|
||||
it('should return null for empty ratings', () => {
|
||||
expect(calculator.calculate([])).toBeNull();
|
||||
});
|
||||
|
||||
it('should filter out non-positive ratings', () => {
|
||||
const ratings = [
|
||||
{ driverId: 'd1', rating: 1500 },
|
||||
{ driverId: 'd2', rating: 0 },
|
||||
{ driverId: 'd3', rating: -100 },
|
||||
];
|
||||
|
||||
const sof = calculator.calculate(ratings);
|
||||
|
||||
expect(sof).toBe(1500);
|
||||
});
|
||||
|
||||
it('should return null if all ratings are non-positive', () => {
|
||||
const ratings = [
|
||||
{ driverId: 'd1', rating: 0 },
|
||||
{ driverId: 'd2', rating: -500 },
|
||||
];
|
||||
|
||||
expect(calculator.calculate(ratings)).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user