feat(07-02): create test harness with full system wiring

- Add TestHarness interface with all system components and helpers
- Add createTestHarness() factory wiring full system with mocks
- Add MockWorktreeManager (in-memory worktree simulation)
- Add CapturingEventBus (event capture for verification)
- Wire real DispatchManager and CoordinationManager with mocks
- Export all components via src/test/index.ts
This commit is contained in:
Lukas May
2026-01-31 08:47:15 +01:00
parent ba1f7ccd62
commit 4424a46c80
2 changed files with 317 additions and 0 deletions

291
src/test/harness.ts Normal file
View File

@@ -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<string, Worktree> = new Map();
private mergeResults: Map<string, MergeResult> = 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<Worktree> {
const worktree: Worktree = {
id,
branch,
path: `/tmp/test-worktrees/${id}`,
isMainWorktree: false,
};
this.worktrees.set(id, worktree);
return worktree;
}
async remove(id: string): Promise<void> {
if (!this.worktrees.has(id)) {
throw new Error(`Worktree not found: ${id}`);
}
this.worktrees.delete(id);
this.mergeResults.delete(id);
}
async list(): Promise<Worktree[]> {
return Array.from(this.worktrees.values());
}
async get(id: string): Promise<Worktree | null> {
return this.worktrees.get(id) ?? null;
}
async diff(id: string): Promise<WorktreeDiff> {
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<MergeResult> {
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<T extends DomainEvent>(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<SeededFixture>;
/**
* 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;
}

26
src/test/index.ts Normal file
View File

@@ -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';