diff --git a/src/test/harness.ts b/src/test/harness.ts new file mode 100644 index 0000000..c978588 --- /dev/null +++ b/src/test/harness.ts @@ -0,0 +1,291 @@ +/** + * Test Harness for E2E Testing + * + * Wires up the full system with mocks for E2E testing. + * Uses real managers (DispatchManager, CoordinationManager) with + * MockAgentManager and MockWorktreeManager for isolation. + */ + +import { randomUUID } from 'crypto'; +import type { DrizzleDatabase } from '../db/index.js'; +import type { EventBus, DomainEvent } from '../events/types.js'; +import { EventEmitterBus } from '../events/bus.js'; +import type { AgentManager } from '../agent/types.js'; +import { MockAgentManager, type MockAgentScenario } from '../agent/mock-manager.js'; +import type { WorktreeManager, Worktree, WorktreeDiff, MergeResult } from '../git/types.js'; +import type { DispatchManager } from '../dispatch/types.js'; +import { DefaultDispatchManager } from '../dispatch/manager.js'; +import type { CoordinationManager } from '../coordination/types.js'; +import { DefaultCoordinationManager } from '../coordination/manager.js'; +import type { TaskRepository } from '../db/repositories/task-repository.js'; +import type { MessageRepository } from '../db/repositories/message-repository.js'; +import type { AgentRepository } from '../db/repositories/agent-repository.js'; +import { + DrizzleTaskRepository, + DrizzleMessageRepository, + DrizzleAgentRepository, +} from '../db/repositories/drizzle/index.js'; +import { createTestDatabase } from '../db/repositories/drizzle/test-helpers.js'; +import { + seedFixture, + type InitiativeFixture, + type SeededFixture, +} from './fixtures.js'; + +// ============================================================================= +// MockWorktreeManager +// ============================================================================= + +/** + * Simple in-memory WorktreeManager for testing. + * Creates fake worktrees without actual git operations. + */ +export class MockWorktreeManager implements WorktreeManager { + private worktrees: Map = new Map(); + private mergeResults: Map = new Map(); + + /** + * Set a custom merge result for a specific worktree. + * Used to test conflict scenarios. + */ + setMergeResult(worktreeId: string, result: MergeResult): void { + this.mergeResults.set(worktreeId, result); + } + + async create(id: string, branch: string, baseBranch?: string): Promise { + const worktree: Worktree = { + id, + branch, + path: `/tmp/test-worktrees/${id}`, + isMainWorktree: false, + }; + this.worktrees.set(id, worktree); + return worktree; + } + + async remove(id: string): Promise { + if (!this.worktrees.has(id)) { + throw new Error(`Worktree not found: ${id}`); + } + this.worktrees.delete(id); + this.mergeResults.delete(id); + } + + async list(): Promise { + return Array.from(this.worktrees.values()); + } + + async get(id: string): Promise { + return this.worktrees.get(id) ?? null; + } + + async diff(id: string): Promise { + if (!this.worktrees.has(id)) { + throw new Error(`Worktree not found: ${id}`); + } + return { + files: [], + summary: 'No changes (mock)', + }; + } + + async merge(id: string, targetBranch: string): Promise { + if (!this.worktrees.has(id)) { + throw new Error(`Worktree not found: ${id}`); + } + + // Return custom result if set, otherwise success + const customResult = this.mergeResults.get(id); + if (customResult) { + return customResult; + } + + return { + success: true, + message: `Merged ${id} into ${targetBranch} (mock)`, + }; + } + + /** + * Clear all worktrees. + * Useful for test cleanup. + */ + clear(): void { + this.worktrees.clear(); + this.mergeResults.clear(); + } +} + +// ============================================================================= +// CapturingEventBus +// ============================================================================= + +/** + * EventBus wrapper that captures all emitted events. + * Extends EventEmitterBus with event capture functionality. + */ +export class CapturingEventBus extends EventEmitterBus { + /** All emitted events */ + emittedEvents: DomainEvent[] = []; + + emit(event: T): void { + this.emittedEvents.push(event); + super.emit(event); + } + + /** + * Get events by type. + */ + getEventsByType(type: string): DomainEvent[] { + return this.emittedEvents.filter((e) => e.type === type); + } + + /** + * Clear captured events. + */ + clearEvents(): void { + this.emittedEvents = []; + } +} + +// ============================================================================= +// TestHarness Interface +// ============================================================================= + +/** + * Test harness for E2E testing. + * Provides access to all system components and helper methods. + */ +export interface TestHarness { + // Core components + /** In-memory SQLite database */ + db: DrizzleDatabase; + /** Event bus with event capture */ + eventBus: CapturingEventBus; + /** Mock agent manager */ + agentManager: MockAgentManager; + /** Mock worktree manager */ + worktreeManager: MockWorktreeManager; + /** Real dispatch manager wired to mocks */ + dispatchManager: DispatchManager; + /** Real coordination manager wired to mocks */ + coordinationManager: CoordinationManager; + + // Repositories + /** Task repository */ + taskRepository: TaskRepository; + /** Message repository */ + messageRepository: MessageRepository; + /** Agent repository */ + agentRepository: AgentRepository; + + // Helpers + /** + * Seed a fixture into the database. + */ + seedFixture(fixture: InitiativeFixture): Promise; + + /** + * Set scenario for a specific agent name. + */ + setAgentScenario(agentName: string, scenario: MockAgentScenario): void; + + /** + * Get events by type. + */ + getEventsByType(type: string): DomainEvent[]; + + /** + * Clear all captured events. + */ + clearEvents(): void; + + /** + * Clean up all resources. + */ + cleanup(): void; +} + +// ============================================================================= +// createTestHarness Factory +// ============================================================================= + +/** + * Create a fully wired test harness for E2E testing. + * + * Wires: + * - In-memory SQLite database + * - CapturingEventBus (captures all events) + * - MockAgentManager (simulates agent behavior) + * - MockWorktreeManager (fake worktrees) + * - Real DefaultDispatchManager (with mock agent manager) + * - Real DefaultCoordinationManager (with mock worktree manager) + * - All repositories (Drizzle implementations) + */ +export function createTestHarness(): TestHarness { + // Create database + const db = createTestDatabase(); + + // Create event bus with capture + const eventBus = new CapturingEventBus(); + + // Create mock managers + const agentManager = new MockAgentManager({ eventBus }); + const worktreeManager = new MockWorktreeManager(); + + // Create repositories + const taskRepository = new DrizzleTaskRepository(db); + const messageRepository = new DrizzleMessageRepository(db); + const agentRepository = new DrizzleAgentRepository(db); + + // Create real managers wired to mocks + const dispatchManager = new DefaultDispatchManager( + taskRepository, + messageRepository, + agentManager, + eventBus + ); + + const coordinationManager = new DefaultCoordinationManager( + worktreeManager, + taskRepository, + agentRepository, + messageRepository, + eventBus + ); + + // Build harness + const harness: TestHarness = { + // Core components + db, + eventBus, + agentManager, + worktreeManager, + dispatchManager, + coordinationManager, + + // Repositories + taskRepository, + messageRepository, + agentRepository, + + // Helpers + seedFixture: (fixture: InitiativeFixture) => seedFixture(db, fixture), + + setAgentScenario: (agentName: string, scenario: MockAgentScenario) => { + agentManager.setScenario(agentName, scenario); + }, + + getEventsByType: (type: string) => eventBus.getEventsByType(type), + + clearEvents: () => eventBus.clearEvents(), + + cleanup: () => { + agentManager.clear(); + worktreeManager.clear(); + eventBus.clearEvents(); + }, + }; + + return harness; +} diff --git a/src/test/index.ts b/src/test/index.ts new file mode 100644 index 0000000..31c4e21 --- /dev/null +++ b/src/test/index.ts @@ -0,0 +1,26 @@ +/** + * Test Module + * + * Provides test harness and fixtures for E2E testing. + */ + +// Fixture helpers +export { + seedFixture, + type TaskFixture, + type PlanFixture, + type PhaseFixture, + type InitiativeFixture, + type SeededFixture, + SIMPLE_FIXTURE, + PARALLEL_FIXTURE, + COMPLEX_FIXTURE, +} from './fixtures.js'; + +// Test harness +export { + createTestHarness, + MockWorktreeManager, + CapturingEventBus, + type TestHarness, +} from './harness.js';