Files
Codewalkers/apps/server/test/fixtures.ts
Lukas May 34578d39c6 refactor: Restructure monorepo to apps/server/ and apps/web/ layout
Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt
standard monorepo conventions (apps/ for runnable apps, packages/
for reusable libraries). Update all config files, shared package
imports, test fixtures, and documentation to reflect new paths.

Key fixes:
- Update workspace config to ["apps/*", "packages/*"]
- Update tsconfig.json rootDir/include for apps/server/
- Add apps/web/** to vitest exclude list
- Update drizzle.config.ts schema path
- Fix ensure-schema.ts migration path detection (3 levels up in dev,
  2 levels up in dist)
- Fix tests/integration/cli-server.test.ts import paths
- Update packages/shared imports to apps/server/ paths
- Update all docs/ files with new paths
2026-03-03 11:22:53 +01:00

317 lines
8.7 KiB
TypeScript

/**
* 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<string, string>;
/** Map of task group names to parent task IDs */
taskGroups: Map<string, string>;
/** Map of task names to IDs */
tasks: Map<string, string>;
}
// =============================================================================
// 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<SeededFixture> {
// Create repositories
const initiativeRepo = new DrizzleInitiativeRepository(db);
const phaseRepo = new DrizzlePhaseRepository(db);
const taskRepo = new DrizzleTaskRepository(db);
// Result maps
const phasesMap = new Map<string, string>();
const taskGroupsMap = new Map<string, string>();
const tasksMap = new Map<string, string>();
// 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'],
},
],
},
],
},
],
};