/** * Test Fixtures for E2E Testing * * Provides fixture helpers that seed complete task hierarchies * for integration and E2E tests. */ import { nanoid } from 'nanoid'; import type { DrizzleDatabase } from '../db/index.js'; import { DrizzleInitiativeRepository, DrizzlePhaseRepository, DrizzleTaskRepository, } from '../db/repositories/drizzle/index.js'; import { taskDependencies } from '../db/schema.js'; // ============================================================================= // Fixture Interfaces // ============================================================================= /** * Task fixture definition. */ export interface TaskFixture { /** Unique identifier for this task (used for dependency references) */ id: string; /** Task name */ name: string; /** Task priority */ priority?: 'low' | 'medium' | 'high'; /** Task category */ category?: 'execute' | 'research' | 'discuss' | 'plan' | 'detail' | 'refine' | 'verify' | 'merge' | 'review'; /** Names of other tasks in same fixture this task depends on */ dependsOn?: string[]; } /** * Task group fixture definition (replaces Plan). * Tasks are grouped by parent task in the new model. */ export interface TaskGroupFixture { /** Group name (becomes a detail task) */ name: string; /** Tasks in this group */ tasks: TaskFixture[]; } /** * Phase fixture definition. */ export interface PhaseFixture { /** Phase name */ name: string; /** Task groups in this phase (each group becomes a parent detail task) */ taskGroups: TaskGroupFixture[]; } /** * Initiative fixture definition (top-level). */ export interface InitiativeFixture { /** Initiative name */ name: string; /** Phases in this initiative */ phases: PhaseFixture[]; } /** * Result of seeding a fixture. * Maps names to IDs for all created entities. */ export interface SeededFixture { /** ID of the created initiative */ initiativeId: string; /** Map of phase names to IDs */ phases: Map; /** Map of task group names to parent task IDs */ taskGroups: Map; /** Map of task names to IDs */ tasks: Map; } // ============================================================================= // Seed Function // ============================================================================= /** * Seed a complete task hierarchy from a fixture definition. * * Creates initiative, phases, detail tasks (as parent), and child tasks. * Resolves task dependencies by name to actual task IDs. * * @param db - Drizzle database instance * @param fixture - The fixture definition to seed * @returns SeededFixture with all created entity IDs */ export async function seedFixture( db: DrizzleDatabase, fixture: InitiativeFixture ): Promise { // Create repositories const initiativeRepo = new DrizzleInitiativeRepository(db); const phaseRepo = new DrizzlePhaseRepository(db); const taskRepo = new DrizzleTaskRepository(db); // Result maps const phasesMap = new Map(); const taskGroupsMap = new Map(); const tasksMap = new Map(); // Collect all task dependencies to resolve after creation const pendingDependencies: Array<{ taskId: string; dependsOnNames: string[] }> = []; // Create initiative const initiative = await initiativeRepo.create({ name: fixture.name, status: 'active', }); // Create phases for (const phaseFixture of fixture.phases) { const phase = await phaseRepo.create({ initiativeId: initiative.id, name: phaseFixture.name, status: 'pending', }); phasesMap.set(phaseFixture.name, phase.id); // Create task groups as parent detail tasks let taskOrder = 0; for (const groupFixture of phaseFixture.taskGroups) { // Create parent detail task const parentTask = await taskRepo.create({ phaseId: phase.id, initiativeId: initiative.id, name: groupFixture.name, description: `Test task group: ${groupFixture.name}`, category: 'detail', type: 'auto', priority: 'medium', status: 'completed', // Detail tasks are completed once child tasks are created order: taskOrder++, }); taskGroupsMap.set(groupFixture.name, parentTask.id); // Create child tasks linked to parent let childOrder = 0; for (const taskFixture of groupFixture.tasks) { const task = await taskRepo.create({ parentTaskId: parentTask.id, phaseId: phase.id, initiativeId: initiative.id, name: taskFixture.name, description: `Test task: ${taskFixture.name}`, category: taskFixture.category ?? 'execute', type: 'auto', priority: taskFixture.priority ?? 'medium', status: 'pending', order: childOrder++, }); tasksMap.set(taskFixture.id, task.id); // Collect dependencies to resolve later if (taskFixture.dependsOn && taskFixture.dependsOn.length > 0) { pendingDependencies.push({ taskId: task.id, dependsOnNames: taskFixture.dependsOn, }); } } } } // Resolve and insert task dependencies for (const { taskId, dependsOnNames } of pendingDependencies) { for (const depName of dependsOnNames) { const dependsOnTaskId = tasksMap.get(depName); if (!dependsOnTaskId) { throw new Error( `Dependency resolution failed: task "${depName}" not found in fixture` ); } // Insert into task_dependencies table await db.insert(taskDependencies).values({ id: nanoid(), taskId, dependsOnTaskId, createdAt: new Date(), }); } } return { initiativeId: initiative.id, phases: phasesMap, taskGroups: taskGroupsMap, tasks: tasksMap, }; } // ============================================================================= // Convenience Fixtures // ============================================================================= /** * Simple fixture: 1 initiative -> 1 phase -> 1 task group -> 3 tasks. * * Task dependency structure: * - Task A: no dependencies * - Task B: depends on Task A * - Task C: depends on Task A */ export const SIMPLE_FIXTURE: InitiativeFixture = { name: 'Simple Test Initiative', phases: [ { name: 'Phase 1', taskGroups: [ { name: 'Task Group 1', tasks: [ { id: 'Task A', name: 'Task A', priority: 'high' }, { id: 'Task B', name: 'Task B', priority: 'medium', dependsOn: ['Task A'] }, { id: 'Task C', name: 'Task C', priority: 'medium', dependsOn: ['Task A'] }, ], }, ], }, ], }; /** * Parallel fixture: 1 initiative -> 1 phase -> 2 task groups (each with 2 independent tasks). * * Task structure: * - Group A: Task X, Task Y (independent) * - Group B: Task P, Task Q (independent) */ export const PARALLEL_FIXTURE: InitiativeFixture = { name: 'Parallel Test Initiative', phases: [ { name: 'Parallel Phase', taskGroups: [ { name: 'Group A', tasks: [ { id: 'Task X', name: 'Task X', priority: 'high' }, { id: 'Task Y', name: 'Task Y', priority: 'medium' }, ], }, { name: 'Group B', tasks: [ { id: 'Task P', name: 'Task P', priority: 'high' }, { id: 'Task Q', name: 'Task Q', priority: 'low' }, ], }, ], }, ], }; /** * Complex fixture: 1 initiative -> 2 phases -> 4 task groups with cross-group dependencies. * * Structure: * - Phase 1: Group 1 (Task 1A, 1B), Group 2 (Task 2A depends on 1A) * - Phase 2: Group 3 (Task 3A depends on 1B), Group 4 (Task 4A depends on 2A and 3A) */ export const COMPLEX_FIXTURE: InitiativeFixture = { name: 'Complex Test Initiative', phases: [ { name: 'Phase 1', taskGroups: [ { name: 'Group 1', tasks: [ { id: 'Task 1A', name: 'Task 1A', priority: 'high' }, { id: 'Task 1B', name: 'Task 1B', priority: 'medium' }, ], }, { name: 'Group 2', tasks: [ { id: 'Task 2A', name: 'Task 2A', priority: 'high', dependsOn: ['Task 1A'] }, ], }, ], }, { name: 'Phase 2', taskGroups: [ { name: 'Group 3', tasks: [ { id: 'Task 3A', name: 'Task 3A', priority: 'high', dependsOn: ['Task 1B'] }, ], }, { name: 'Group 4', tasks: [ { id: 'Task 4A', name: 'Task 4A', priority: 'high', dependsOn: ['Task 2A', 'Task 3A'], }, ], }, ], }, ], };