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:
291
src/test/harness.ts
Normal file
291
src/test/harness.ts
Normal 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
26
src/test/index.ts
Normal 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';
|
||||
Reference in New Issue
Block a user