489 lines
14 KiB
TypeScript
489 lines
14 KiB
TypeScript
import { RatingEventFactory, RaceFactsDto } from './RatingEventFactory';
|
|
|
|
describe('RatingEventFactory', () => {
|
|
describe('createFromRaceFinish', () => {
|
|
it('should create events from race finish data', () => {
|
|
const events = RatingEventFactory.createFromRaceFinish({
|
|
userId: 'user-123',
|
|
raceId: 'race-456',
|
|
position: 3,
|
|
totalDrivers: 10,
|
|
startPosition: 5,
|
|
incidents: 1,
|
|
fieldStrength: 2500,
|
|
status: 'finished',
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const event = events[0];
|
|
expect(event).toBeDefined();
|
|
if (event) {
|
|
expect(event.userId).toBe('user-123');
|
|
expect(event.source.type).toBe('race');
|
|
expect(event.source.id).toBe('race-456');
|
|
}
|
|
});
|
|
|
|
it('should create events for DNS status', () => {
|
|
const events = RatingEventFactory.createFromRaceFinish({
|
|
userId: 'user-123',
|
|
raceId: 'race-456',
|
|
position: 10,
|
|
totalDrivers: 10,
|
|
startPosition: 5,
|
|
incidents: 0,
|
|
fieldStrength: 2500,
|
|
status: 'dns',
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const dnsEvent = events.find(e => e.reason.code.includes('DNS'));
|
|
expect(dnsEvent).toBeDefined();
|
|
});
|
|
|
|
it('should create events for DNF status', () => {
|
|
const events = RatingEventFactory.createFromRaceFinish({
|
|
userId: 'user-123',
|
|
raceId: 'race-456',
|
|
position: 10,
|
|
totalDrivers: 10,
|
|
startPosition: 5,
|
|
incidents: 2,
|
|
fieldStrength: 2500,
|
|
status: 'dnf',
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const dnfEvent = events.find(e => e.reason.code.includes('DNF'));
|
|
expect(dnfEvent).toBeDefined();
|
|
});
|
|
|
|
it('should create events for DSQ status', () => {
|
|
const events = RatingEventFactory.createFromRaceFinish({
|
|
userId: 'user-123',
|
|
raceId: 'race-456',
|
|
position: 10,
|
|
totalDrivers: 10,
|
|
startPosition: 5,
|
|
incidents: 5,
|
|
fieldStrength: 2500,
|
|
status: 'dsq',
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const dsqEvent = events.find(e => e.reason.code.includes('DSQ'));
|
|
expect(dsqEvent).toBeDefined();
|
|
});
|
|
|
|
it('should create events for AFK status', () => {
|
|
const events = RatingEventFactory.createFromRaceFinish({
|
|
userId: 'user-123',
|
|
raceId: 'race-456',
|
|
position: 10,
|
|
totalDrivers: 10,
|
|
startPosition: 5,
|
|
incidents: 0,
|
|
fieldStrength: 2500,
|
|
status: 'afk',
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const afkEvent = events.find(e => e.reason.code.includes('AFK'));
|
|
expect(afkEvent).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('createDrivingEventsFromRace', () => {
|
|
it('should create events for single driver with good performance', () => {
|
|
const raceFacts: RaceFactsDto = {
|
|
raceId: 'race-123',
|
|
results: [
|
|
{
|
|
userId: 'user-123',
|
|
startPos: 5,
|
|
finishPos: 2,
|
|
incidents: 0,
|
|
status: 'finished',
|
|
sof: 2500,
|
|
},
|
|
],
|
|
};
|
|
|
|
const eventsByUser = RatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
|
|
|
expect(eventsByUser.has('user-123')).toBe(true);
|
|
const events = eventsByUser.get('user-123')!;
|
|
expect(events.length).toBeGreaterThan(0);
|
|
|
|
const performanceEvent = events.find(e => e.reason.code === 'DRIVING_FINISH_STRENGTH_GAIN');
|
|
expect(performanceEvent).toBeDefined();
|
|
expect(performanceEvent?.delta.value).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should create events for multiple drivers', () => {
|
|
const raceFacts: RaceFactsDto = {
|
|
raceId: 'race-123',
|
|
results: [
|
|
{
|
|
userId: 'user-123',
|
|
startPos: 5,
|
|
finishPos: 2,
|
|
incidents: 0,
|
|
status: 'finished',
|
|
sof: 2500,
|
|
},
|
|
{
|
|
userId: 'user-456',
|
|
startPos: 3,
|
|
finishPos: 8,
|
|
incidents: 2,
|
|
status: 'finished',
|
|
sof: 2500,
|
|
},
|
|
{
|
|
userId: 'user-789',
|
|
startPos: 5,
|
|
finishPos: 5,
|
|
incidents: 0,
|
|
status: 'dns',
|
|
sof: 2500,
|
|
},
|
|
],
|
|
};
|
|
|
|
const eventsByUser = RatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
|
|
|
expect(eventsByUser.has('user-123')).toBe(true);
|
|
expect(eventsByUser.has('user-456')).toBe(true);
|
|
expect(eventsByUser.has('user-789')).toBe(true);
|
|
|
|
// user-123 should have performance events
|
|
const user123Events = eventsByUser.get('user-123')!;
|
|
expect(user123Events.some(e => e.reason.code === 'DRIVING_FINISH_STRENGTH_GAIN')).toBe(true);
|
|
|
|
// user-456 should have incident penalty
|
|
const user456Events = eventsByUser.get('user-456')!;
|
|
expect(user456Events.some(e => e.reason.code === 'DRIVING_INCIDENTS_PENALTY')).toBe(true);
|
|
|
|
// user-789 should have DNS penalty
|
|
const user789Events = eventsByUser.get('user-789')!;
|
|
expect(user789Events.some(e => e.reason.code === 'DRIVING_DNS_PENALTY')).toBe(true);
|
|
});
|
|
|
|
it('should create positions gained bonus', () => {
|
|
const raceFacts: RaceFactsDto = {
|
|
raceId: 'race-123',
|
|
results: [
|
|
{
|
|
userId: 'user-123',
|
|
startPos: 10,
|
|
finishPos: 3,
|
|
incidents: 0,
|
|
status: 'finished',
|
|
sof: 2500,
|
|
},
|
|
],
|
|
};
|
|
|
|
const eventsByUser = RatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
|
const events = eventsByUser.get('user-123')!;
|
|
|
|
const gainEvent = events.find(e => e.reason.code === 'DRIVING_POSITIONS_GAINED_BONUS');
|
|
expect(gainEvent).toBeDefined();
|
|
expect(gainEvent?.delta.value).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should handle DNF status', () => {
|
|
const raceFacts: RaceFactsDto = {
|
|
raceId: 'race-123',
|
|
results: [
|
|
{
|
|
userId: 'user-123',
|
|
startPos: 5,
|
|
finishPos: 10,
|
|
incidents: 0,
|
|
status: 'dnf',
|
|
sof: 2500,
|
|
},
|
|
],
|
|
};
|
|
|
|
const eventsByUser = RatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
|
const events = eventsByUser.get('user-123')!;
|
|
|
|
const dnfEvent = events.find(e => e.reason.code === 'DRIVING_DNF_PENALTY');
|
|
expect(dnfEvent).toBeDefined();
|
|
expect(dnfEvent?.delta.value).toBe(-10);
|
|
});
|
|
|
|
it('should handle DSQ status', () => {
|
|
const raceFacts: RaceFactsDto = {
|
|
raceId: 'race-123',
|
|
results: [
|
|
{
|
|
userId: 'user-123',
|
|
startPos: 5,
|
|
finishPos: 10,
|
|
incidents: 0,
|
|
status: 'dsq',
|
|
sof: 2500,
|
|
},
|
|
],
|
|
};
|
|
|
|
const eventsByUser = RatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
|
const events = eventsByUser.get('user-123')!;
|
|
|
|
const dsqEvent = events.find(e => e.reason.code === 'DRIVING_DSQ_PENALTY');
|
|
expect(dsqEvent).toBeDefined();
|
|
expect(dsqEvent?.delta.value).toBe(-25);
|
|
});
|
|
|
|
it('should handle AFK status', () => {
|
|
const raceFacts: RaceFactsDto = {
|
|
raceId: 'race-123',
|
|
results: [
|
|
{
|
|
userId: 'user-123',
|
|
startPos: 5,
|
|
finishPos: 10,
|
|
incidents: 0,
|
|
status: 'afk',
|
|
sof: 2500,
|
|
},
|
|
],
|
|
};
|
|
|
|
const eventsByUser = RatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
|
const events = eventsByUser.get('user-123')!;
|
|
|
|
const afkEvent = events.find(e => e.reason.code === 'DRIVING_AFK_PENALTY');
|
|
expect(afkEvent).toBeDefined();
|
|
expect(afkEvent?.delta.value).toBe(-20);
|
|
});
|
|
|
|
it('should handle incident penalties', () => {
|
|
const raceFacts: RaceFactsDto = {
|
|
raceId: 'race-123',
|
|
results: [
|
|
{
|
|
userId: 'user-123',
|
|
startPos: 5,
|
|
finishPos: 5,
|
|
incidents: 3,
|
|
status: 'finished',
|
|
sof: 2500,
|
|
},
|
|
],
|
|
};
|
|
|
|
const eventsByUser = RatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
|
const events = eventsByUser.get('user-123')!;
|
|
|
|
const incidentEvent = events.find(e => e.reason.code === 'DRIVING_INCIDENTS_PENALTY');
|
|
expect(incidentEvent).toBeDefined();
|
|
expect(incidentEvent?.delta.value).toBeLessThan(0);
|
|
});
|
|
|
|
it('should calculate SoF if not provided', () => {
|
|
const raceFacts: RaceFactsDto = {
|
|
raceId: 'race-123',
|
|
results: [
|
|
{
|
|
userId: 'user-123',
|
|
startPos: 5,
|
|
finishPos: 2,
|
|
incidents: 0,
|
|
status: 'finished',
|
|
// No sof
|
|
},
|
|
{
|
|
userId: 'user-456',
|
|
startPos: 3,
|
|
finishPos: 8,
|
|
incidents: 0,
|
|
status: 'finished',
|
|
// No sof
|
|
},
|
|
],
|
|
};
|
|
|
|
const eventsByUser = RatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
|
|
|
// Should still work without errors
|
|
expect(eventsByUser.size).toBe(2);
|
|
expect(eventsByUser.get('user-123')!.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should handle empty results', () => {
|
|
const raceFacts: RaceFactsDto = {
|
|
raceId: 'race-123',
|
|
results: [],
|
|
};
|
|
|
|
const eventsByUser = RatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
|
expect(eventsByUser.size).toBe(0);
|
|
});
|
|
|
|
it('should handle mixed statuses', () => {
|
|
const raceFacts: RaceFactsDto = {
|
|
raceId: 'race-123',
|
|
results: [
|
|
{
|
|
userId: 'user-123',
|
|
startPos: 5,
|
|
finishPos: 2,
|
|
incidents: 1,
|
|
status: 'finished',
|
|
sof: 2500,
|
|
},
|
|
{
|
|
userId: 'user-456',
|
|
startPos: 3,
|
|
finishPos: 10,
|
|
incidents: 0,
|
|
status: 'dnf',
|
|
sof: 2500,
|
|
},
|
|
{
|
|
userId: 'user-789',
|
|
startPos: 5,
|
|
finishPos: 5,
|
|
incidents: 0,
|
|
status: 'dns',
|
|
sof: 2500,
|
|
},
|
|
],
|
|
};
|
|
|
|
const eventsByUser = RatingEventFactory.createDrivingEventsFromRace(raceFacts);
|
|
|
|
expect(eventsByUser.size).toBe(3);
|
|
|
|
// user-123: performance + incidents
|
|
const user123Events = eventsByUser.get('user-123')!;
|
|
expect(user123Events.length).toBeGreaterThanOrEqual(2);
|
|
|
|
// user-456: DNF
|
|
const user456Events = eventsByUser.get('user-456')!;
|
|
expect(user456Events.some(e => e.reason.code === 'DRIVING_DNF_PENALTY')).toBe(true);
|
|
|
|
// user-789: DNS
|
|
const user789Events = eventsByUser.get('user-789')!;
|
|
expect(user789Events.some(e => e.reason.code === 'DRIVING_DNS_PENALTY')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('createFromPenalty', () => {
|
|
it('should create driving penalty event', () => {
|
|
const events = RatingEventFactory.createFromPenalty({
|
|
userId: 'user-123',
|
|
penaltyId: 'penalty-789',
|
|
penaltyType: 'incident',
|
|
severity: 'major',
|
|
reason: 'Caused collision',
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const event = events[0];
|
|
expect(event).toBeDefined();
|
|
if (event) {
|
|
expect(event.dimension.value).toBe('driving');
|
|
expect(event.source.type).toBe('penalty');
|
|
expect(event.source.id).toBe('penalty-789');
|
|
}
|
|
});
|
|
|
|
it('should create admin trust penalty event', () => {
|
|
const events = RatingEventFactory.createFromPenalty({
|
|
userId: 'user-123',
|
|
penaltyId: 'penalty-789',
|
|
penaltyType: 'admin_violation',
|
|
severity: 'major',
|
|
reason: 'Abuse of power',
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const event = events[0];
|
|
expect(event).toBeDefined();
|
|
if (event) {
|
|
expect(event.dimension.value).toBe('adminTrust');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('createFromVote', () => {
|
|
it('should create positive vote event', () => {
|
|
const events = RatingEventFactory.createFromVote({
|
|
userId: 'user-123',
|
|
voteSessionId: 'vote-101',
|
|
outcome: 'positive',
|
|
voteCount: 8,
|
|
eligibleVoterCount: 10,
|
|
percentPositive: 80,
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const event = events[0];
|
|
expect(event).toBeDefined();
|
|
if (event) {
|
|
expect(event.dimension.value).toBe('adminTrust');
|
|
expect(event.delta.value).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it('should create negative vote event', () => {
|
|
const events = RatingEventFactory.createFromVote({
|
|
userId: 'user-123',
|
|
voteSessionId: 'vote-101',
|
|
outcome: 'negative',
|
|
voteCount: 2,
|
|
eligibleVoterCount: 10,
|
|
percentPositive: 20,
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const event = events[0];
|
|
expect(event).toBeDefined();
|
|
if (event) {
|
|
expect(event.delta.value).toBeLessThan(0);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('createFromAdminAction', () => {
|
|
it('should create admin action bonus event', () => {
|
|
const events = RatingEventFactory.createFromAdminAction({
|
|
userId: 'user-123',
|
|
adminActionId: 'admin-202',
|
|
actionType: 'sla_response',
|
|
details: { responseTime: 30 },
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const event = events[0];
|
|
expect(event).toBeDefined();
|
|
if (event) {
|
|
expect(event.dimension.value).toBe('adminTrust');
|
|
expect(event.delta.value).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it('should create admin action penalty event', () => {
|
|
const events = RatingEventFactory.createFromAdminAction({
|
|
userId: 'user-123',
|
|
adminActionId: 'admin-202',
|
|
actionType: 'abuse_report',
|
|
details: { validated: true },
|
|
});
|
|
|
|
expect(events.length).toBeGreaterThan(0);
|
|
const event = events[0];
|
|
expect(event).toBeDefined();
|
|
if (event) {
|
|
expect(event.dimension.value).toBe('adminTrust');
|
|
expect(event.delta.value).toBeLessThan(0);
|
|
}
|
|
});
|
|
});
|
|
}); |