feat(companion): implement hosted session automation POC with TDD approach
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemorySessionRepository } from '../../../src/infrastructure/repositories/InMemorySessionRepository';
|
||||
import { AutomationSession } from '../../../src/packages/domain/entities/AutomationSession';
|
||||
import { StepId } from '../../../src/packages/domain/value-objects/StepId';
|
||||
|
||||
describe('InMemorySessionRepository Integration Tests', () => {
|
||||
let repository: InMemorySessionRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
repository = new InMemorySessionRepository();
|
||||
});
|
||||
|
||||
describe('save', () => {
|
||||
it('should persist a new session', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved).toBeDefined();
|
||||
expect(retrieved?.id).toBe(session.id);
|
||||
});
|
||||
|
||||
it('should update existing session on duplicate save', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
session.start();
|
||||
session.transitionToStep(StepId.create(2));
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved?.currentStep.value).toBe(2);
|
||||
expect(retrieved?.state.isInProgress()).toBe(true);
|
||||
});
|
||||
|
||||
it('should preserve all session properties', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race Session',
|
||||
trackId: 'spa-francorchamps',
|
||||
carIds: ['dallara-f3', 'porsche-911-gt3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved?.config.sessionName).toBe('Test Race Session');
|
||||
expect(retrieved?.config.trackId).toBe('spa-francorchamps');
|
||||
expect(retrieved?.config.carIds).toEqual(['dallara-f3', 'porsche-911-gt3']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null for non-existent session', async () => {
|
||||
const result = await repository.findById('non-existent-id');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should retrieve existing session by ID', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved).toBeDefined();
|
||||
expect(retrieved?.id).toBe(session.id);
|
||||
});
|
||||
|
||||
it('should return domain entity not DTO', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved).toBeInstanceOf(AutomationSession);
|
||||
});
|
||||
|
||||
it('should retrieve session with correct state', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
session.start();
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved?.state.isInProgress()).toBe(true);
|
||||
expect(retrieved?.startedAt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update existing session', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
session.start();
|
||||
session.transitionToStep(StepId.create(2));
|
||||
|
||||
await repository.update(session);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved?.currentStep.value).toBe(2);
|
||||
});
|
||||
|
||||
it('should throw error when updating non-existent session', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await expect(repository.update(session)).rejects.toThrow('Session not found');
|
||||
});
|
||||
|
||||
it('should preserve unchanged properties', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Original Name',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
session.start();
|
||||
|
||||
await repository.update(session);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved?.config.sessionName).toBe('Original Name');
|
||||
expect(retrieved?.state.isInProgress()).toBe(true);
|
||||
});
|
||||
|
||||
it('should update session state correctly', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
session.start();
|
||||
session.pause();
|
||||
|
||||
await repository.update(session);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved?.state.value).toBe('PAUSED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should remove session from storage', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
await repository.delete(session.id);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw when deleting non-existent session', async () => {
|
||||
await expect(repository.delete('non-existent-id')).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should only delete specified session', async () => {
|
||||
const session1 = AutomationSession.create({
|
||||
sessionName: 'Race 1',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
const session2 = AutomationSession.create({
|
||||
sessionName: 'Race 2',
|
||||
trackId: 'monza',
|
||||
carIds: ['porsche-911-gt3'],
|
||||
});
|
||||
|
||||
await repository.save(session1);
|
||||
await repository.save(session2);
|
||||
|
||||
await repository.delete(session1.id);
|
||||
|
||||
const retrieved1 = await repository.findById(session1.id);
|
||||
const retrieved2 = await repository.findById(session2.id);
|
||||
|
||||
expect(retrieved1).toBeNull();
|
||||
expect(retrieved2).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return empty array when no sessions exist', async () => {
|
||||
const sessions = await repository.findAll();
|
||||
|
||||
expect(sessions).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return all saved sessions', async () => {
|
||||
const session1 = AutomationSession.create({
|
||||
sessionName: 'Race 1',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
const session2 = AutomationSession.create({
|
||||
sessionName: 'Race 2',
|
||||
trackId: 'monza',
|
||||
carIds: ['porsche-911-gt3'],
|
||||
});
|
||||
|
||||
await repository.save(session1);
|
||||
await repository.save(session2);
|
||||
|
||||
const sessions = await repository.findAll();
|
||||
|
||||
expect(sessions).toHaveLength(2);
|
||||
expect(sessions.map(s => s.id)).toContain(session1.id);
|
||||
expect(sessions.map(s => s.id)).toContain(session2.id);
|
||||
});
|
||||
|
||||
it('should return domain entities not DTOs', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
const sessions = await repository.findAll();
|
||||
|
||||
expect(sessions[0]).toBeInstanceOf(AutomationSession);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByState', () => {
|
||||
it('should return sessions matching state', async () => {
|
||||
const session1 = AutomationSession.create({
|
||||
sessionName: 'Race 1',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
session1.start();
|
||||
|
||||
const session2 = AutomationSession.create({
|
||||
sessionName: 'Race 2',
|
||||
trackId: 'monza',
|
||||
carIds: ['porsche-911-gt3'],
|
||||
});
|
||||
|
||||
await repository.save(session1);
|
||||
await repository.save(session2);
|
||||
|
||||
const inProgressSessions = await repository.findByState('IN_PROGRESS');
|
||||
|
||||
expect(inProgressSessions).toHaveLength(1);
|
||||
expect(inProgressSessions[0].id).toBe(session1.id);
|
||||
});
|
||||
|
||||
it('should return empty array when no sessions match state', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
|
||||
const completedSessions = await repository.findByState('COMPLETED');
|
||||
|
||||
expect(completedSessions).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle multiple sessions with same state', async () => {
|
||||
const session1 = AutomationSession.create({
|
||||
sessionName: 'Race 1',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
const session2 = AutomationSession.create({
|
||||
sessionName: 'Race 2',
|
||||
trackId: 'monza',
|
||||
carIds: ['porsche-911-gt3'],
|
||||
});
|
||||
|
||||
await repository.save(session1);
|
||||
await repository.save(session2);
|
||||
|
||||
const pendingSessions = await repository.findByState('PENDING');
|
||||
|
||||
expect(pendingSessions).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('concurrent operations', () => {
|
||||
it('should handle concurrent saves', async () => {
|
||||
const sessions = Array.from({ length: 10 }, (_, i) =>
|
||||
AutomationSession.create({
|
||||
sessionName: `Race ${i}`,
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(sessions.map(s => repository.save(s)));
|
||||
|
||||
const allSessions = await repository.findAll();
|
||||
expect(allSessions).toHaveLength(10);
|
||||
});
|
||||
|
||||
it('should handle concurrent updates', async () => {
|
||||
const session = AutomationSession.create({
|
||||
sessionName: 'Test Race',
|
||||
trackId: 'spa',
|
||||
carIds: ['dallara-f3'],
|
||||
});
|
||||
|
||||
await repository.save(session);
|
||||
session.start();
|
||||
|
||||
await Promise.all([
|
||||
repository.update(session),
|
||||
repository.update(session),
|
||||
repository.update(session),
|
||||
]);
|
||||
|
||||
const retrieved = await repository.findById(session.id);
|
||||
expect(retrieved?.state.isInProgress()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user