292 lines
9.1 KiB
TypeScript
292 lines
9.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
|
import { StartAutomationSessionUseCase } from '../../../../src/packages/application/use-cases/StartAutomationSessionUseCase';
|
|
import { IAutomationEngine } from '../../../../src/packages/application/ports/IAutomationEngine';
|
|
import { IBrowserAutomation } from '../../../../src/packages/application/ports/IBrowserAutomation';
|
|
import { ISessionRepository } from '../../../../src/packages/application/ports/ISessionRepository';
|
|
import { AutomationSession } from '../../../../src/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)');
|
|
});
|
|
});
|
|
}); |