/** * 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, RoundType, RoundStatus, CompetitionStatus, ProjectRoundStateValue, 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', }, }) } // ─── Competition Factory ─────────────────────────────────────────────────── export async function createTestCompetition( programId: string, overrides: Partial<{ name: string slug: string status: CompetitionStatus }> = {}, ) { const id = uid('comp') return prisma.competition.create({ data: { id, programId, name: overrides.name ?? `Competition ${id}`, slug: overrides.slug ?? id, status: overrides.status ?? 'DRAFT', }, }) } // ─── Round Factory ───────────────────────────────────────────────────────── export async function createTestRound( competitionId: string, overrides: Partial<{ name: string slug: string roundType: RoundType status: RoundStatus sortOrder: number configJson: Record windowOpenAt: Date windowCloseAt: Date }> = {}, ) { const id = uid('round') return prisma.round.create({ data: { id, competitionId, name: overrides.name ?? `Round ${id}`, slug: overrides.slug ?? id, roundType: overrides.roundType ?? 'EVALUATION', status: overrides.status ?? 'ROUND_ACTIVE', sortOrder: overrides.sortOrder ?? 0, configJson: (overrides.configJson as any) ?? undefined, windowOpenAt: overrides.windowOpenAt ?? null, windowCloseAt: overrides.windowCloseAt ?? null, }, }) } // ─── 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, }, }) } // ─── ProjectRoundState Factory ───────────────────────────────────────────── export async function createTestProjectRoundState( projectId: string, roundId: string, overrides: Partial<{ state: ProjectRoundStateValue exitedAt: Date | null }> = {}, ) { return prisma.projectRoundState.create({ data: { projectId, roundId, state: overrides.state ?? 'PENDING', exitedAt: overrides.exitedAt ?? null, }, }) } // ─── Assignment Factory ──────────────────────────────────────────────────── export async function createTestAssignment( userId: string, projectId: string, roundId: string, overrides: Partial<{ method: AssignmentMethod isCompleted: boolean }> = {}, ) { return prisma.assignment.create({ data: { userId, projectId, roundId, method: overrides.method ?? 'MANUAL', isCompleted: overrides.isCompleted ?? false, }, }) } // ─── Evaluation Form Factory ─────────────────────────────────────────────── export async function createTestEvaluationForm( roundId: string, criteria: Array<{ id: string label: string scale: string weight: number }> = [], ) { return prisma.evaluationForm.create({ data: { roundId, 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( roundId: string, overrides: Partial<{ name: string ruleType: string configJson: Record priority: number }> = {}, ) { return prisma.filteringRule.create({ data: { roundId, 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( roundId: string, overrides: Partial<{ name: string isOpen: boolean votingMode: string }> = {}, ) { const id = uid('cohort') return prisma.cohort.create({ data: { id, roundId, 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 } } }) } // Competition/Round cascade cleanup await prisma.cohortProject.deleteMany({ where: { cohort: { round: { competition: { programId } } } } }) await prisma.cohort.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.liveProgressCursor.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.filteringResult.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.filteringRule.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.filteringJob.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.assignmentJob.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.conflictOfInterest.deleteMany({ where: { assignment: { round: { competition: { programId } } } } }) await prisma.evaluation.deleteMany({ where: { assignment: { round: { competition: { programId } } } } }) await prisma.assignment.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.evaluationForm.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.fileRequirement.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.gracePeriod.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.reminderLog.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.evaluationSummary.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.evaluationDiscussion.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.projectRoundState.deleteMany({ where: { round: { competition: { programId } } } }) await prisma.advancementRule.deleteMany({ where: { round: { competition: { 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.round.deleteMany({ where: { competition: { programId } } }) await prisma.competition.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 } } }) } }