Files
gridpilot.gg/core/racing/domain/entities/Protest.test.ts
2025-12-17 00:33:13 +01:00

226 lines
8.0 KiB
TypeScript

import { Protest } from './Protest';
import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';
describe('Protest', () => {
describe('create', () => {
it('should create a protest with required fields', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
});
expect(protest.id).toBe('protest-1');
expect(protest.raceId).toBe('race-1');
expect(protest.protestingDriverId).toBe('driver-1');
expect(protest.accusedDriverId).toBe('driver-2');
expect(protest.incident.lap.toNumber()).toBe(5);
expect(protest.incident.description.toString()).toBe('Unsafe overtake');
expect(protest.status.toString()).toBe('pending');
expect(protest.filedAt).toBeInstanceOf(Date);
});
it('should create a protest with all fields', () => {
const filedAt = new Date('2023-01-01');
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake', timeInRace: 120.5 },
comment: 'He cut me off',
proofVideoUrl: 'https://example.com/video.mp4',
status: 'under_review',
reviewedBy: 'steward-1',
decisionNotes: 'Reviewed and upheld',
filedAt,
reviewedAt: new Date('2023-01-02'),
defense: { statement: 'It was a racing incident', videoUrl: 'https://example.com/defense.mp4', submittedAt: new Date('2023-01-01T12:00:00Z') },
defenseRequestedAt: new Date('2023-01-01T10:00:00Z'),
defenseRequestedBy: 'steward-1',
});
expect(protest.id).toBe('protest-1');
expect(protest.comment).toBe('He cut me off');
expect(protest.proofVideoUrl).toBe('https://example.com/video.mp4');
expect(protest.status.toString()).toBe('under_review');
expect(protest.reviewedBy).toBe('steward-1');
expect(protest.decisionNotes).toBe('Reviewed and upheld');
expect(protest.filedAt).toEqual(filedAt);
expect(protest.defense).toBeDefined();
expect(protest.defenseRequestedAt).toBeInstanceOf(Date);
expect(protest.defenseRequestedBy).toBe('steward-1');
});
it('should throw error for invalid id', () => {
expect(() => Protest.create({
id: '',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
})).toThrow(RacingDomainValidationError);
});
it('should throw error for invalid lap', () => {
expect(() => Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: -1, description: 'Unsafe overtake' },
})).toThrow(RacingDomainValidationError);
});
it('should throw error for empty description', () => {
expect(() => Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: '' },
})).toThrow(RacingDomainValidationError);
});
});
describe('requestDefense', () => {
it('should request defense for pending protest', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
});
const updated = protest.requestDefense('steward-1');
expect(updated.status.toString()).toBe('awaiting_defense');
expect(updated.defenseRequestedAt).toBeInstanceOf(Date);
expect(updated.defenseRequestedBy).toBe('steward-1');
});
it('should throw error if not pending', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
status: 'upheld',
});
expect(() => protest.requestDefense('steward-1')).toThrow(RacingDomainInvariantError);
});
});
describe('submitDefense', () => {
it('should submit defense for awaiting defense protest', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
status: 'awaiting_defense',
});
const updated = protest.submitDefense('It was a racing incident');
expect(updated.status.toString()).toBe('under_review');
expect(updated.defense).toBeDefined();
if (updated.defense) {
expect(updated.defense.statement.toString()).toBe('It was a racing incident');
}
});
it('should throw error if not awaiting defense', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
});
expect(() => protest.submitDefense('Statement')).toThrow(RacingDomainInvariantError);
});
});
describe('uphold', () => {
it('should uphold pending protest', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
});
const updated = protest.uphold('steward-1', 'Penalty applied');
expect(updated.status.toString()).toBe('upheld');
expect(updated.reviewedBy).toBe('steward-1');
expect(updated.decisionNotes).toBe('Penalty applied');
expect(updated.reviewedAt).toBeInstanceOf(Date);
});
it('should throw error if resolved', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
status: 'upheld',
});
expect(() => protest.uphold('steward-1', 'Notes')).toThrow(RacingDomainInvariantError);
});
});
describe('withdraw', () => {
it('should withdraw pending protest', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
});
const updated = protest.withdraw();
expect(updated.status.toString()).toBe('withdrawn');
expect(updated.reviewedAt).toBeInstanceOf(Date);
});
it('should throw error if resolved', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
status: 'upheld',
});
expect(() => protest.withdraw()).toThrow(RacingDomainInvariantError);
});
});
describe('isPending', () => {
it('should return true for pending status', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
});
expect(protest.isPending()).toBe(true);
});
it('should return false for upheld status', () => {
const protest = Protest.create({
id: 'protest-1',
raceId: 'race-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Unsafe overtake' },
status: 'upheld',
});
expect(protest.isPending()).toBe(false);
});
});
});