227 lines
8.1 KiB
TypeScript
227 lines
8.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { RacingDomainInvariantError, RacingDomainValidationError } from '../errors/RacingDomainError';
|
|
import { Protest } from './Protest';
|
|
|
|
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);
|
|
});
|
|
});
|
|
}); |