MOPC-App/tests/helpers.ts

345 lines
12 KiB
TypeScript
Raw Normal View History

/**
* 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<string, unknown>
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<string, unknown>
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 } } })
}
}