team rating
This commit is contained in:
452
core/racing/domain/services/TeamDrivingRatingCalculator.test.ts
Normal file
452
core/racing/domain/services/TeamDrivingRatingCalculator.test.ts
Normal file
@@ -0,0 +1,452 @@
|
||||
import { TeamDrivingRatingCalculator, TeamDrivingRaceResult, TeamDrivingQualifyingResult, TeamDrivingOvertakeStats } from './TeamDrivingRatingCalculator';
|
||||
|
||||
describe('TeamDrivingRatingCalculator', () => {
|
||||
describe('calculateFromRaceFinish', () => {
|
||||
it('should create events from race finish data', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
expect(events[0].teamId).toBe('team-123');
|
||||
expect(events[0].dimension.value).toBe('driving');
|
||||
});
|
||||
|
||||
it('should create events for DNS status', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
status: 'dns',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
const dnsEvent = events.find(e => e.reason.code === 'RACE_DNS');
|
||||
expect(dnsEvent).toBeDefined();
|
||||
expect(dnsEvent?.delta.value).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it('should create events for DNF status', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 5,
|
||||
incidents: 2,
|
||||
status: 'dnf',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
const dnfEvent = events.find(e => e.reason.code === 'RACE_DNF');
|
||||
expect(dnfEvent).toBeDefined();
|
||||
expect(dnfEvent?.delta.value).toBe(-15);
|
||||
});
|
||||
|
||||
it('should create events for DSQ status', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 5,
|
||||
incidents: 0,
|
||||
status: 'dsq',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
const dsqEvent = events.find(e => e.reason.code === 'RACE_DSQ');
|
||||
expect(dsqEvent).toBeDefined();
|
||||
expect(dsqEvent?.delta.value).toBe(-25);
|
||||
});
|
||||
|
||||
it('should create events for AFK status', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 5,
|
||||
incidents: 0,
|
||||
status: 'afk',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
const afkEvent = events.find(e => e.reason.code === 'RACE_AFK');
|
||||
expect(afkEvent).toBeDefined();
|
||||
expect(afkEvent?.delta.value).toBe(-20);
|
||||
});
|
||||
|
||||
it('should apply incident penalties', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 3,
|
||||
incidents: 5,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
const incidentEvent = events.find(e => e.reason.code === 'RACE_INCIDENTS');
|
||||
expect(incidentEvent).toBeDefined();
|
||||
expect(incidentEvent?.delta.value).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it('should apply gain bonus for beating higher-rated teams', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 65, // High strength
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
const gainEvent = events.find(e => e.reason.code === 'RACE_GAIN_BONUS');
|
||||
expect(gainEvent).toBeDefined();
|
||||
expect(gainEvent?.delta.value).toBeGreaterThan(0);
|
||||
expect(gainEvent?.weight).toBe(0.5);
|
||||
});
|
||||
|
||||
it('should create pace events when pace is provided', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 3,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
pace: 80,
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
const paceEvent = events.find(e => e.reason.code === 'RACE_PACE');
|
||||
expect(paceEvent).toBeDefined();
|
||||
expect(paceEvent?.delta.value).toBeGreaterThan(0);
|
||||
expect(paceEvent?.weight).toBe(0.3);
|
||||
});
|
||||
|
||||
it('should create consistency events when consistency is provided', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 3,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
consistency: 85,
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
const consistencyEvent = events.find(e => e.reason.code === 'RACE_CONSISTENCY');
|
||||
expect(consistencyEvent).toBeDefined();
|
||||
expect(consistencyEvent?.delta.value).toBeGreaterThan(0);
|
||||
expect(consistencyEvent?.weight).toBe(0.3);
|
||||
});
|
||||
|
||||
it('should create teamwork events when teamwork is provided', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 3,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
teamwork: 90,
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
const teamworkEvent = events.find(e => e.reason.code === 'RACE_TEAMWORK');
|
||||
expect(teamworkEvent).toBeDefined();
|
||||
expect(teamworkEvent?.delta.value).toBeGreaterThan(0);
|
||||
expect(teamworkEvent?.weight).toBe(0.4);
|
||||
});
|
||||
|
||||
it('should create sportsmanship events when sportsmanship is provided', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 3,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
sportsmanship: 95,
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
const sportsmanshipEvent = events.find(e => e.reason.code === 'RACE_SPORTSMANSHIP');
|
||||
expect(sportsmanshipEvent).toBeDefined();
|
||||
expect(sportsmanshipEvent?.delta.value).toBeGreaterThan(0);
|
||||
expect(sportsmanshipEvent?.weight).toBe(0.3);
|
||||
});
|
||||
|
||||
it('should handle all optional ratings together', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 1,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 65, // High enough for gain bonus
|
||||
raceId: 'race-456',
|
||||
pace: 75,
|
||||
consistency: 80,
|
||||
teamwork: 85,
|
||||
sportsmanship: 90,
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
// Should have multiple events
|
||||
expect(events.length).toBeGreaterThan(5);
|
||||
|
||||
// Check for specific events
|
||||
expect(events.find(e => e.reason.code === 'RACE_PERFORMANCE')).toBeDefined();
|
||||
expect(events.find(e => e.reason.code === 'RACE_GAIN_BONUS')).toBeDefined();
|
||||
expect(events.find(e => e.reason.code === 'RACE_INCIDENTS')).toBeDefined();
|
||||
expect(events.find(e => e.reason.code === 'RACE_PACE')).toBeDefined();
|
||||
expect(events.find(e => e.reason.code === 'RACE_CONSISTENCY')).toBeDefined();
|
||||
expect(events.find(e => e.reason.code === 'RACE_TEAMWORK')).toBeDefined();
|
||||
expect(events.find(e => e.reason.code === 'RACE_SPORTSMANSHIP')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateFromQualifying', () => {
|
||||
it('should create qualifying events', () => {
|
||||
const result: TeamDrivingQualifyingResult = {
|
||||
teamId: 'team-123',
|
||||
qualifyingPosition: 3,
|
||||
fieldSize: 10,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromQualifying(result);
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
expect(events[0].teamId).toBe('team-123');
|
||||
expect(events[0].dimension.value).toBe('driving');
|
||||
expect(events[0].reason.code).toBe('RACE_QUALIFYING');
|
||||
expect(events[0].weight).toBe(0.25);
|
||||
});
|
||||
|
||||
it('should create positive delta for good qualifying position', () => {
|
||||
const result: TeamDrivingQualifyingResult = {
|
||||
teamId: 'team-123',
|
||||
qualifyingPosition: 1,
|
||||
fieldSize: 10,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromQualifying(result);
|
||||
|
||||
expect(events[0].delta.value).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should create negative delta for poor qualifying position', () => {
|
||||
const result: TeamDrivingQualifyingResult = {
|
||||
teamId: 'team-123',
|
||||
qualifyingPosition: 10,
|
||||
fieldSize: 10,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromQualifying(result);
|
||||
|
||||
expect(events[0].delta.value).toBeLessThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateFromOvertakeStats', () => {
|
||||
it('should create overtake events', () => {
|
||||
const stats: TeamDrivingOvertakeStats = {
|
||||
teamId: 'team-123',
|
||||
overtakes: 5,
|
||||
successfulDefenses: 3,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromOvertakeStats(stats);
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
const overtakeEvent = events.find(e => e.reason.code === 'RACE_OVERTAKE');
|
||||
expect(overtakeEvent).toBeDefined();
|
||||
expect(overtakeEvent?.delta.value).toBeGreaterThan(0);
|
||||
expect(overtakeEvent?.weight).toBe(0.5);
|
||||
});
|
||||
|
||||
it('should create defense events', () => {
|
||||
const stats: TeamDrivingOvertakeStats = {
|
||||
teamId: 'team-123',
|
||||
overtakes: 0,
|
||||
successfulDefenses: 4,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromOvertakeStats(stats);
|
||||
|
||||
const defenseEvent = events.find(e => e.reason.code === 'RACE_DEFENSE');
|
||||
expect(defenseEvent).toBeDefined();
|
||||
expect(defenseEvent?.delta.value).toBeGreaterThan(0);
|
||||
expect(defenseEvent?.weight).toBe(0.4);
|
||||
});
|
||||
|
||||
it('should create both overtake and defense events', () => {
|
||||
const stats: TeamDrivingOvertakeStats = {
|
||||
teamId: 'team-123',
|
||||
overtakes: 3,
|
||||
successfulDefenses: 2,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromOvertakeStats(stats);
|
||||
|
||||
expect(events.length).toBe(2);
|
||||
expect(events.find(e => e.reason.code === 'RACE_OVERTAKE')).toBeDefined();
|
||||
expect(events.find(e => e.reason.code === 'RACE_DEFENSE')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return empty array for zero stats', () => {
|
||||
const stats: TeamDrivingOvertakeStats = {
|
||||
teamId: 'team-123',
|
||||
overtakes: 0,
|
||||
successfulDefenses: 0,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromOvertakeStats(stats);
|
||||
|
||||
expect(events.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle extreme field sizes', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 100,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
expect(events[0].delta.value).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should handle many incidents', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 5,
|
||||
incidents: 20,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
const incidentEvent = events.find(e => e.reason.code === 'RACE_INCIDENTS');
|
||||
expect(incidentEvent).toBeDefined();
|
||||
// Should be capped at 20
|
||||
expect(incidentEvent?.delta.value).toBe(-20);
|
||||
});
|
||||
|
||||
it('should handle low ratings', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 5,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
pace: 10,
|
||||
consistency: 15,
|
||||
teamwork: 20,
|
||||
sportsmanship: 25,
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
const paceEvent = events.find(e => e.reason.code === 'RACE_PACE');
|
||||
expect(paceEvent?.delta.value).toBeLessThan(0);
|
||||
|
||||
const consistencyEvent = events.find(e => e.reason.code === 'RACE_CONSISTENCY');
|
||||
expect(consistencyEvent?.delta.value).toBeLessThan(0);
|
||||
|
||||
const teamworkEvent = events.find(e => e.reason.code === 'RACE_TEAMWORK');
|
||||
expect(teamworkEvent?.delta.value).toBeLessThan(0);
|
||||
|
||||
const sportsmanshipEvent = events.find(e => e.reason.code === 'RACE_SPORTSMANSHIP');
|
||||
expect(sportsmanshipEvent?.delta.value).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it('should handle high ratings', () => {
|
||||
const result: TeamDrivingRaceResult = {
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 65,
|
||||
raceId: 'race-456',
|
||||
pace: 95,
|
||||
consistency: 98,
|
||||
teamwork: 92,
|
||||
sportsmanship: 97,
|
||||
};
|
||||
|
||||
const events = TeamDrivingRatingCalculator.calculateFromRaceFinish(result);
|
||||
|
||||
const paceEvent = events.find(e => e.reason.code === 'RACE_PACE');
|
||||
expect(paceEvent?.delta.value).toBeGreaterThan(0);
|
||||
|
||||
const consistencyEvent = events.find(e => e.reason.code === 'RACE_CONSISTENCY');
|
||||
expect(consistencyEvent?.delta.value).toBeGreaterThan(0);
|
||||
|
||||
const teamworkEvent = events.find(e => e.reason.code === 'RACE_TEAMWORK');
|
||||
expect(teamworkEvent?.delta.value).toBeGreaterThan(0);
|
||||
|
||||
const sportsmanshipEvent = events.find(e => e.reason.code === 'RACE_SPORTSMANSHIP');
|
||||
expect(sportsmanshipEvent?.delta.value).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user