import { describe, it, expect } from 'vitest'; import { AutomationSession } from '@gridpilot/automation/domain/entities/AutomationSession'; import { StepId } from '@gridpilot/automation/domain/value-objects/StepId'; import { SessionState } from '@gridpilot/automation/domain/value-objects/SessionState'; describe('AutomationSession Entity', () => { describe('create', () => { it('should create a new session with PENDING state', () => { const config = { sessionName: 'Test Race Session', trackId: 'spa', carIds: ['dallara-f3'], }; const session = AutomationSession.create(config); expect(session.id).toBeDefined(); expect(session.currentStep.value).toBe(1); expect(session.state.isPending()).toBe(true); expect(session.config).toEqual(config); }); it('should throw error for empty session name', () => { const config = { sessionName: '', trackId: 'spa', carIds: ['dallara-f3'], }; expect(() => AutomationSession.create(config)).toThrow('Session name cannot be empty'); }); it('should throw error for missing track ID', () => { const config = { sessionName: 'Test Race', trackId: '', carIds: ['dallara-f3'], }; expect(() => AutomationSession.create(config)).toThrow('Track ID is required'); }); it('should throw error for empty car list', () => { const config = { sessionName: 'Test Race', trackId: 'spa', carIds: [], }; expect(() => AutomationSession.create(config)).toThrow('At least one car must be selected'); }); }); describe('start', () => { it('should transition from PENDING to IN_PROGRESS', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); expect(session.state.isInProgress()).toBe(true); expect(session.startedAt).toBeDefined(); }); it('should throw error when starting non-PENDING session', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); expect(() => session.start()).toThrow('Cannot start session that is not pending'); }); }); describe('transitionToStep', () => { it('should advance to next step when in progress', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); session.transitionToStep(StepId.create(2)); expect(session.currentStep.value).toBe(2); }); it('should throw error when transitioning while not in progress', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); expect(() => session.transitionToStep(StepId.create(2))).toThrow( 'Cannot transition steps when session is not in progress' ); }); it('should throw error when skipping steps', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); expect(() => session.transitionToStep(StepId.create(3))).toThrow( 'Cannot skip steps - must transition sequentially' ); }); it('should throw error when moving backward', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); session.transitionToStep(StepId.create(2)); expect(() => session.transitionToStep(StepId.create(1))).toThrow( 'Cannot move backward - steps must progress forward only' ); }); it('should stop at step 17 (safety checkpoint)', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); // Advance through all steps to 17 for (let i = 2; i <= 17; i++) { session.transitionToStep(StepId.create(i)); } expect(session.currentStep.value).toBe(17); expect(session.state.isStoppedAtStep18()).toBe(true); expect(session.completedAt).toBeDefined(); }); }); describe('pause', () => { it('should transition from IN_PROGRESS to PAUSED', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); session.pause(); expect(session.state.value).toBe('PAUSED'); }); it('should throw error when pausing non-in-progress session', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); expect(() => session.pause()).toThrow('Cannot pause session that is not in progress'); }); }); describe('resume', () => { it('should transition from PAUSED to IN_PROGRESS', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); session.pause(); session.resume(); expect(session.state.isInProgress()).toBe(true); }); it('should throw error when resuming non-paused session', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); expect(() => session.resume()).toThrow('Cannot resume session that is not paused'); }); }); describe('fail', () => { it('should transition to FAILED state with error message', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); const errorMessage = 'Browser automation failed at step 5'; session.fail(errorMessage); expect(session.state.isFailed()).toBe(true); expect(session.errorMessage).toBe(errorMessage); expect(session.completedAt).toBeDefined(); }); it('should allow failing from PENDING state', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.fail('Initialization failed'); expect(session.state.isFailed()).toBe(true); }); it('should allow failing from PAUSED state', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); session.pause(); session.fail('Failed during pause'); expect(session.state.isFailed()).toBe(true); }); it('should throw error when failing already completed session', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); // Advance to step 17 for (let i = 2; i <= 17; i++) { session.transitionToStep(StepId.create(i)); } expect(() => session.fail('Too late')).toThrow('Cannot fail terminal session'); }); }); describe('isAtModalStep', () => { it('should return true when at step 6', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); for (let i = 2; i <= 6; i++) { session.transitionToStep(StepId.create(i)); } expect(session.isAtModalStep()).toBe(true); }); it('should return true when at step 9', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); for (let i = 2; i <= 9; i++) { session.transitionToStep(StepId.create(i)); } expect(session.isAtModalStep()).toBe(true); }); it('should return true when at step 12', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); for (let i = 2; i <= 12; i++) { session.transitionToStep(StepId.create(i)); } expect(session.isAtModalStep()).toBe(true); }); it('should return false when at non-modal step', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); expect(session.isAtModalStep()).toBe(false); }); }); describe('getElapsedTime', () => { it('should return 0 for non-started session', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); expect(session.getElapsedTime()).toBe(0); }); it('should return elapsed milliseconds for in-progress session', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); // Wait a bit (in real test, this would be mocked) const elapsed = session.getElapsedTime(); expect(elapsed).toBeGreaterThan(0); }); it('should return total duration for completed session', () => { const session = AutomationSession.create({ sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }); session.start(); // Advance to step 17 for (let i = 2; i <= 17; i++) { session.transitionToStep(StepId.create(i)); } const elapsed = session.getElapsedTime(); expect(elapsed).toBeGreaterThan(0); expect(session.state.isStoppedAtStep18()).toBe(true); }); }); });