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); }); }); });