/** * Test Data Factories * * Provides helper functions to create test data in the database. * Each factory returns the created record and uses unique identifiers * to avoid collisions between test files. */ import { randomUUID } from 'crypto' import { prisma } from './setup' import type { UserRole, StageType, StageStatus, TrackKind, ProjectStageStateValue, AssignmentMethod, } from '@prisma/client' export function uid(prefix = 'test'): string { return `${prefix}-${randomUUID().slice(0, 12)}` } // ─── User Factory ────────────────────────────────────────────────────────── export async function createTestUser( role: UserRole = 'JURY_MEMBER', overrides: Partial<{ email: string name: string status: string expertiseTags: string[] maxAssignments: number country: string }> = {}, ) { const id = uid('user') return prisma.user.create({ data: { id, email: overrides.email ?? `${id}@test.local`, name: overrides.name ?? `Test ${role}`, role, status: (overrides.status as any) ?? 'ACTIVE', expertiseTags: overrides.expertiseTags ?? [], maxAssignments: overrides.maxAssignments ?? null, country: overrides.country ?? null, }, }) } // ─── Program Factory ─────────────────────────────────────────────────────── export async function createTestProgram( overrides: Partial<{ name: string; year: number }> = {}, ) { const id = uid('prog') return prisma.program.create({ data: { id, name: overrides.name ?? `Test Program ${id}`, year: overrides.year ?? 2026, status: 'ACTIVE', }, }) } // ─── Pipeline Factory ────────────────────────────────────────────────────── export async function createTestPipeline( programId: string, overrides: Partial<{ name: string; slug: string; status: string }> = {}, ) { const id = uid('pipe') return prisma.pipeline.create({ data: { id, programId, name: overrides.name ?? `Pipeline ${id}`, slug: overrides.slug ?? id, status: overrides.status ?? 'DRAFT', }, }) } // ─── Track Factory ───────────────────────────────────────────────────────── export async function createTestTrack( pipelineId: string, overrides: Partial<{ name: string slug: string kind: TrackKind sortOrder: number routingMode: string decisionMode: string }> = {}, ) { const id = uid('track') return prisma.track.create({ data: { id, pipelineId, name: overrides.name ?? `Track ${id}`, slug: overrides.slug ?? id, kind: overrides.kind ?? 'MAIN', sortOrder: overrides.sortOrder ?? 0, routingMode: (overrides.routingMode as any) ?? null, decisionMode: (overrides.decisionMode as any) ?? null, }, }) } // ─── Stage Factory ───────────────────────────────────────────────────────── export async function createTestStage( trackId: string, overrides: Partial<{ name: string slug: string stageType: StageType status: StageStatus sortOrder: number configJson: Record windowOpenAt: Date windowCloseAt: Date }> = {}, ) { const id = uid('stage') return prisma.stage.create({ data: { id, trackId, name: overrides.name ?? `Stage ${id}`, slug: overrides.slug ?? id, stageType: overrides.stageType ?? 'EVALUATION', status: overrides.status ?? 'STAGE_ACTIVE', sortOrder: overrides.sortOrder ?? 0, configJson: (overrides.configJson as any) ?? undefined, windowOpenAt: overrides.windowOpenAt ?? null, windowCloseAt: overrides.windowCloseAt ?? null, }, }) } // ─── Stage Transition Factory ────────────────────────────────────────────── export async function createTestTransition( fromStageId: string, toStageId: string, overrides: Partial<{ isDefault: boolean; guardJson: Record }> = {}, ) { return prisma.stageTransition.create({ data: { fromStageId, toStageId, isDefault: overrides.isDefault ?? true, guardJson: (overrides.guardJson as any) ?? undefined, }, }) } // ─── Project Factory ─────────────────────────────────────────────────────── export async function createTestProject( programId: string, overrides: Partial<{ title: string description: string country: string tags: string[] competitionCategory: string oceanIssue: string }> = {}, ) { const id = uid('proj') return prisma.project.create({ data: { id, programId, title: overrides.title ?? `Test Project ${id}`, description: overrides.description ?? 'Test project description', country: overrides.country ?? 'France', tags: overrides.tags ?? [], competitionCategory: (overrides.competitionCategory as any) ?? null, oceanIssue: (overrides.oceanIssue as any) ?? null, }, }) } // ─── ProjectStageState Factory ───────────────────────────────────────────── export async function createTestPSS( projectId: string, trackId: string, stageId: string, overrides: Partial<{ state: ProjectStageStateValue exitedAt: Date | null }> = {}, ) { return prisma.projectStageState.create({ data: { projectId, trackId, stageId, state: overrides.state ?? 'PENDING', exitedAt: overrides.exitedAt ?? null, }, }) } // ─── Assignment Factory ──────────────────────────────────────────────────── export async function createTestAssignment( userId: string, projectId: string, stageId: string, overrides: Partial<{ method: AssignmentMethod isCompleted: boolean }> = {}, ) { return prisma.assignment.create({ data: { userId, projectId, stageId, method: overrides.method ?? 'MANUAL', isCompleted: overrides.isCompleted ?? false, }, }) } // ─── Evaluation Form Factory ─────────────────────────────────────────────── export async function createTestEvaluationForm( stageId: string, criteria: Array<{ id: string label: string scale: string weight: number }> = [], ) { return prisma.evaluationForm.create({ data: { stageId, criteriaJson: criteria.length > 0 ? criteria : [ { id: 'c1', label: 'Innovation', scale: '1-10', weight: 1 }, { id: 'c2', label: 'Impact', scale: '1-10', weight: 1 }, ], isActive: true, }, }) } // ─── Filtering Rule Factory ──────────────────────────────────────────────── export async function createTestFilteringRule( stageId: string, overrides: Partial<{ name: string ruleType: string configJson: Record priority: number }> = {}, ) { return prisma.filteringRule.create({ data: { stageId, name: overrides.name ?? 'Test Filter Rule', ruleType: (overrides.ruleType as any) ?? 'DOCUMENT_CHECK', configJson: (overrides.configJson ?? { requiredFileTypes: ['EXEC_SUMMARY'], action: 'REJECT' }) as any, priority: overrides.priority ?? 0, isActive: true, }, }) } // ─── COI Factory ─────────────────────────────────────────────────────────── export async function createTestCOI( assignmentId: string, userId: string, projectId: string, hasConflict = true, ) { return prisma.conflictOfInterest.create({ data: { assignmentId, userId, projectId, hasConflict, conflictType: hasConflict ? 'personal' : null, description: hasConflict ? 'Test conflict' : null, }, }) } // ─── Cohort + CohortProject Factory ──────────────────────────────────────── export async function createTestCohort( stageId: string, overrides: Partial<{ name: string isOpen: boolean votingMode: string }> = {}, ) { const id = uid('cohort') return prisma.cohort.create({ data: { id, stageId, name: overrides.name ?? `Cohort ${id}`, isOpen: overrides.isOpen ?? false, votingMode: overrides.votingMode ?? 'simple', }, }) } export async function createTestCohortProject( cohortId: string, projectId: string, sortOrder = 0, ) { return prisma.cohortProject.create({ data: { cohortId, projectId, sortOrder, }, }) } // ─── Cleanup Utility ─────────────────────────────────────────────────────── /** * Cleanup all test data created by a specific test run. * Pass the programId to cascade-delete most related data. */ export async function cleanupTestData(programId: string, userIds: string[] = []) { // Delete in reverse dependency order — scoped by programId or userIds if (userIds.length > 0) { await prisma.decisionAuditLog.deleteMany({ where: { actorId: { in: userIds } } }) await prisma.overrideAction.deleteMany({ where: { actorId: { in: userIds } } }) await prisma.auditLog.deleteMany({ where: { userId: { in: userIds } } }) } await prisma.cohortProject.deleteMany({ where: { cohort: { stage: { track: { pipeline: { programId } } } } } }) await prisma.cohort.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.liveProgressCursor.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.filteringResult.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.filteringRule.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.filteringJob.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.assignmentJob.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.conflictOfInterest.deleteMany({ where: { assignment: { stage: { track: { pipeline: { programId } } } } } }) await prisma.evaluation.deleteMany({ where: { assignment: { stage: { track: { pipeline: { programId } } } } } }) await prisma.assignment.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.evaluationForm.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.fileRequirement.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.gracePeriod.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.reminderLog.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.evaluationSummary.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.evaluationDiscussion.deleteMany({ where: { stage: { track: { pipeline: { programId } } } } }) await prisma.projectStageState.deleteMany({ where: { track: { pipeline: { programId } } } }) await prisma.stageTransition.deleteMany({ where: { fromStage: { track: { pipeline: { programId } } } } }) await prisma.awardEligibility.deleteMany({ where: { award: { program: { id: programId } } } }) await prisma.awardVote.deleteMany({ where: { award: { program: { id: programId } } } }) await prisma.awardJuror.deleteMany({ where: { award: { program: { id: programId } } } }) await prisma.specialAward.deleteMany({ where: { programId } }) await prisma.stage.deleteMany({ where: { track: { pipeline: { programId } } } }) await prisma.track.deleteMany({ where: { pipeline: { programId } } }) await prisma.pipeline.deleteMany({ where: { programId } }) await prisma.projectStatusHistory.deleteMany({ where: { project: { programId } } }) await prisma.projectFile.deleteMany({ where: { project: { programId } } }) await prisma.projectTag.deleteMany({ where: { project: { programId } } }) await prisma.project.deleteMany({ where: { programId } }) await prisma.program.deleteMany({ where: { id: programId } }) if (userIds.length > 0) { await prisma.user.deleteMany({ where: { id: { in: userIds } } }) } }