team rating
This commit is contained in:
312
core/racing/domain/services/TeamRatingEventFactory.test.ts
Normal file
312
core/racing/domain/services/TeamRatingEventFactory.test.ts
Normal file
@@ -0,0 +1,312 @@
|
||||
import { TeamRatingEventFactory, TeamRaceFactsDto } from './TeamRatingEventFactory';
|
||||
|
||||
describe('TeamRatingEventFactory', () => {
|
||||
describe('createFromRaceFinish', () => {
|
||||
it('should create events from race finish data', () => {
|
||||
const events = TeamRatingEventFactory.createFromRaceFinish({
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
});
|
||||
|
||||
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 events = TeamRatingEventFactory.createFromRaceFinish({
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
status: 'dns',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
});
|
||||
|
||||
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 events = TeamRatingEventFactory.createFromRaceFinish({
|
||||
teamId: 'team-123',
|
||||
position: 5,
|
||||
incidents: 2,
|
||||
status: 'dnf',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
});
|
||||
|
||||
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 events = TeamRatingEventFactory.createFromRaceFinish({
|
||||
teamId: 'team-123',
|
||||
position: 5,
|
||||
incidents: 0,
|
||||
status: 'dsq',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
});
|
||||
|
||||
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 events = TeamRatingEventFactory.createFromRaceFinish({
|
||||
teamId: 'team-123',
|
||||
position: 5,
|
||||
incidents: 0,
|
||||
status: 'afk',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
});
|
||||
|
||||
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 events = TeamRatingEventFactory.createFromRaceFinish({
|
||||
teamId: 'team-123',
|
||||
position: 3,
|
||||
incidents: 5,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 55,
|
||||
raceId: 'race-456',
|
||||
});
|
||||
|
||||
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 events = TeamRatingEventFactory.createFromRaceFinish({
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 10,
|
||||
strengthOfField: 65, // High strength
|
||||
raceId: 'race-456',
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createDrivingEventsFromRace', () => {
|
||||
it('should create events for multiple teams', () => {
|
||||
const raceFacts: TeamRaceFactsDto = {
|
||||
raceId: 'race-456',
|
||||
teamId: 'team-123',
|
||||
results: [
|
||||
{
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 3,
|
||||
strengthOfField: 55,
|
||||
},
|
||||
{
|
||||
teamId: 'team-456',
|
||||
position: 2,
|
||||
incidents: 1,
|
||||
status: 'finished',
|
||||
fieldSize: 3,
|
||||
strengthOfField: 55,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const eventsByTeam = TeamRatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
||||
|
||||
expect(eventsByTeam.size).toBe(2);
|
||||
expect(eventsByTeam.get('team-123')).toBeDefined();
|
||||
expect(eventsByTeam.get('team-456')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle empty results', () => {
|
||||
const raceFacts: TeamRaceFactsDto = {
|
||||
raceId: 'race-456',
|
||||
teamId: 'team-123',
|
||||
results: [],
|
||||
};
|
||||
|
||||
const eventsByTeam = TeamRatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
||||
|
||||
expect(eventsByTeam.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should skip teams with no events', () => {
|
||||
const raceFacts: TeamRaceFactsDto = {
|
||||
raceId: 'race-456',
|
||||
teamId: 'team-123',
|
||||
results: [
|
||||
{
|
||||
teamId: 'team-123',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
status: 'finished',
|
||||
fieldSize: 1,
|
||||
strengthOfField: 55,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const eventsByTeam = TeamRatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
||||
|
||||
expect(eventsByTeam.size).toBe(1);
|
||||
expect(eventsByTeam.get('team-123')?.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFromPenalty', () => {
|
||||
it('should create driving penalty event', () => {
|
||||
const events = TeamRatingEventFactory.createFromPenalty({
|
||||
teamId: 'team-123',
|
||||
penaltyType: 'minor',
|
||||
severity: 'low',
|
||||
});
|
||||
|
||||
const drivingEvent = events.find(e => e.dimension.value === 'driving');
|
||||
expect(drivingEvent).toBeDefined();
|
||||
expect(drivingEvent?.delta.value).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it('should create admin trust penalty event', () => {
|
||||
const events = TeamRatingEventFactory.createFromPenalty({
|
||||
teamId: 'team-123',
|
||||
penaltyType: 'minor',
|
||||
severity: 'low',
|
||||
});
|
||||
|
||||
const adminEvent = events.find(e => e.dimension.value === 'adminTrust');
|
||||
expect(adminEvent).toBeDefined();
|
||||
expect(adminEvent?.delta.value).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it('should apply severity multipliers', () => {
|
||||
const lowEvents = TeamRatingEventFactory.createFromPenalty({
|
||||
teamId: 'team-123',
|
||||
penaltyType: 'major',
|
||||
severity: 'low',
|
||||
});
|
||||
|
||||
const highEvents = TeamRatingEventFactory.createFromPenalty({
|
||||
teamId: 'team-123',
|
||||
penaltyType: 'major',
|
||||
severity: 'high',
|
||||
});
|
||||
|
||||
const lowDelta = lowEvents.find(e => e.dimension.value === 'driving')?.delta.value || 0;
|
||||
const highDelta = highEvents.find(e => e.dimension.value === 'driving')?.delta.value || 0;
|
||||
|
||||
expect(highDelta).toBeLessThan(lowDelta);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFromVote', () => {
|
||||
it('should create positive vote event', () => {
|
||||
const events = TeamRatingEventFactory.createFromVote({
|
||||
teamId: 'team-123',
|
||||
outcome: 'positive',
|
||||
voteCount: 10,
|
||||
eligibleVoterCount: 15,
|
||||
percentPositive: 80,
|
||||
});
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
expect(events[0].dimension.value).toBe('adminTrust');
|
||||
expect(events[0].delta.value).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should create negative vote event', () => {
|
||||
const events = TeamRatingEventFactory.createFromVote({
|
||||
teamId: 'team-123',
|
||||
outcome: 'negative',
|
||||
voteCount: 10,
|
||||
eligibleVoterCount: 15,
|
||||
percentPositive: 20,
|
||||
});
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
expect(events[0].dimension.value).toBe('adminTrust');
|
||||
expect(events[0].delta.value).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it('should weight by vote count', () => {
|
||||
const events = TeamRatingEventFactory.createFromVote({
|
||||
teamId: 'team-123',
|
||||
outcome: 'positive',
|
||||
voteCount: 20,
|
||||
eligibleVoterCount: 20,
|
||||
percentPositive: 100,
|
||||
});
|
||||
|
||||
expect(events[0].weight).toBe(20);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFromAdminAction', () => {
|
||||
it('should create admin action bonus event', () => {
|
||||
const events = TeamRatingEventFactory.createFromAdminAction({
|
||||
teamId: 'team-123',
|
||||
actionType: 'bonus',
|
||||
});
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
expect(events[0].dimension.value).toBe('adminTrust');
|
||||
expect(events[0].delta.value).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should create admin action penalty event', () => {
|
||||
const events = TeamRatingEventFactory.createFromAdminAction({
|
||||
teamId: 'team-123',
|
||||
actionType: 'penalty',
|
||||
severity: 'high',
|
||||
});
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
expect(events[0].dimension.value).toBe('adminTrust');
|
||||
expect(events[0].delta.value).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it('should create admin warning response event', () => {
|
||||
const events = TeamRatingEventFactory.createFromAdminAction({
|
||||
teamId: 'team-123',
|
||||
actionType: 'warning',
|
||||
});
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
expect(events[0].dimension.value).toBe('adminTrust');
|
||||
expect(events[0].delta.value).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user