import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; import { StartAutomationSessionUseCase } from '../../../../packages/application/use-cases/StartAutomationSessionUseCase'; import { IAutomationEngine } from '../../../../packages/application/ports/IAutomationEngine'; import { IBrowserAutomation } from '../../../../packages/application/ports/IBrowserAutomation'; import { ISessionRepository } from '../../../../packages/application/ports/ISessionRepository'; import { AutomationSession } from '../../../../packages/domain/entities/AutomationSession'; describe('StartAutomationSessionUseCase', () => { let mockAutomationEngine: { executeStep: Mock; validateConfiguration: Mock; }; let mockBrowserAutomation: { navigateToPage: Mock; fillFormField: Mock; clickElement: Mock; waitForElement: Mock; handleModal: Mock; }; let mockSessionRepository: { save: Mock; findById: Mock; update: Mock; delete: Mock; }; let useCase: StartAutomationSessionUseCase; beforeEach(() => { mockAutomationEngine = { executeStep: vi.fn(), validateConfiguration: vi.fn(), }; mockBrowserAutomation = { navigateToPage: vi.fn(), fillFormField: vi.fn(), clickElement: vi.fn(), waitForElement: vi.fn(), handleModal: vi.fn(), }; mockSessionRepository = { save: vi.fn(), findById: vi.fn(), update: vi.fn(), delete: vi.fn(), }; useCase = new StartAutomationSessionUseCase( mockAutomationEngine as unknown as IAutomationEngine, mockBrowserAutomation as unknown as IBrowserAutomation, mockSessionRepository as unknown as ISessionRepository ); }); describe('execute - happy path', () => { it('should create and persist a new automation session', async () => { const config = { sessionName: 'Test Race Session', trackId: 'spa', carIds: ['dallara-f3'], }; mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true }); mockSessionRepository.save.mockResolvedValue(undefined); const result = await useCase.execute(config); expect(result.sessionId).toBeDefined(); expect(result.state).toBe('PENDING'); expect(result.currentStep).toBe(1); expect(mockAutomationEngine.validateConfiguration).toHaveBeenCalledWith(config); expect(mockSessionRepository.save).toHaveBeenCalledWith( expect.objectContaining({ config, currentStep: expect.objectContaining({ value: 1 }), }) ); }); it('should return session DTO with correct structure', async () => { const config = { sessionName: 'Test Race Session', trackId: 'spa', carIds: ['dallara-f3'], }; mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true }); mockSessionRepository.save.mockResolvedValue(undefined); const result = await useCase.execute(config); expect(result).toMatchObject({ sessionId: expect.any(String), state: 'PENDING', currentStep: 1, config: { sessionName: 'Test Race Session', trackId: 'spa', carIds: ['dallara-f3'], }, }); expect(result.startedAt).toBeUndefined(); expect(result.completedAt).toBeUndefined(); expect(result.errorMessage).toBeUndefined(); }); it('should validate configuration before creating session', async () => { const config = { sessionName: 'Test Race Session', trackId: 'spa', carIds: ['dallara-f3'], }; mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true }); mockSessionRepository.save.mockResolvedValue(undefined); await useCase.execute(config); expect(mockAutomationEngine.validateConfiguration).toHaveBeenCalledWith(config); expect(mockSessionRepository.save).toHaveBeenCalled(); }); }); describe('execute - validation failures', () => { it('should throw error for empty session name', async () => { const config = { sessionName: '', trackId: 'spa', carIds: ['dallara-f3'], }; await expect(useCase.execute(config)).rejects.toThrow('Session name cannot be empty'); expect(mockSessionRepository.save).not.toHaveBeenCalled(); }); it('should throw error for missing track ID', async () => { const config = { sessionName: 'Test Race', trackId: '', carIds: ['dallara-f3'], }; await expect(useCase.execute(config)).rejects.toThrow('Track ID is required'); expect(mockSessionRepository.save).not.toHaveBeenCalled(); }); it('should throw error for empty car list', async () => { const config = { sessionName: 'Test Race', trackId: 'spa', carIds: [], }; await expect(useCase.execute(config)).rejects.toThrow('At least one car must be selected'); expect(mockSessionRepository.save).not.toHaveBeenCalled(); }); it('should throw error when automation engine validation fails', async () => { const config = { sessionName: 'Test Race', trackId: 'invalid-track', carIds: ['dallara-f3'], }; mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: false, error: 'Invalid track ID: invalid-track', }); await expect(useCase.execute(config)).rejects.toThrow('Invalid track ID: invalid-track'); expect(mockSessionRepository.save).not.toHaveBeenCalled(); }); it('should throw error when automation engine validation rejects', async () => { const config = { sessionName: 'Test Race', trackId: 'spa', carIds: ['invalid-car'], }; mockAutomationEngine.validateConfiguration.mockRejectedValue( new Error('Validation service unavailable') ); await expect(useCase.execute(config)).rejects.toThrow('Validation service unavailable'); expect(mockSessionRepository.save).not.toHaveBeenCalled(); }); }); describe('execute - port interactions', () => { it('should call automation engine before saving session', async () => { const config = { sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }; const callOrder: string[] = []; mockAutomationEngine.validateConfiguration.mockImplementation(async () => { callOrder.push('validateConfiguration'); return { isValid: true }; }); mockSessionRepository.save.mockImplementation(async () => { callOrder.push('save'); }); await useCase.execute(config); expect(callOrder).toEqual(['validateConfiguration', 'save']); }); it('should persist session with domain entity', async () => { const config = { sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }; mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true }); mockSessionRepository.save.mockResolvedValue(undefined); await useCase.execute(config); expect(mockSessionRepository.save).toHaveBeenCalledWith( expect.any(AutomationSession) ); }); it('should throw error when repository save fails', async () => { const config = { sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }; mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true }); mockSessionRepository.save.mockRejectedValue(new Error('Database connection failed')); await expect(useCase.execute(config)).rejects.toThrow('Database connection failed'); }); }); describe('execute - edge cases', () => { it('should handle very long session names', async () => { const config = { sessionName: 'A'.repeat(200), trackId: 'spa', carIds: ['dallara-f3'], }; mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true }); mockSessionRepository.save.mockResolvedValue(undefined); const result = await useCase.execute(config); expect(result.config.sessionName).toBe('A'.repeat(200)); }); it('should handle multiple cars in configuration', async () => { const config = { sessionName: 'Multi-car Race', trackId: 'spa', carIds: ['dallara-f3', 'porsche-911-gt3', 'bmw-m4-gt4'], }; mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true }); mockSessionRepository.save.mockResolvedValue(undefined); const result = await useCase.execute(config); expect(result.config.carIds).toEqual(['dallara-f3', 'porsche-911-gt3', 'bmw-m4-gt4']); }); it('should handle special characters in session name', async () => { const config = { sessionName: 'Test & Race #1 (2025)', trackId: 'spa', carIds: ['dallara-f3'], }; mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true }); mockSessionRepository.save.mockResolvedValue(undefined); const result = await useCase.execute(config); expect(result.config.sessionName).toBe('Test & Race #1 (2025)'); }); }); });