418 lines
14 KiB
TypeScript
418 lines
14 KiB
TypeScript
/**
|
|
* 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<string, unknown>
|
|
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<string, unknown> }> = {},
|
|
) {
|
|
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,
|
|
},
|
|
})
|
|
}
|
|
|
|
// ─── Routing Rule Factory ──────────────────────────────────────────────────
|
|
|
|
export async function createTestRoutingRule(
|
|
pipelineId: string,
|
|
destinationTrackId: string,
|
|
overrides: Partial<{
|
|
name: string
|
|
priority: number
|
|
predicateJson: Record<string, unknown>
|
|
sourceTrackId: string
|
|
destinationStageId: string
|
|
isActive: boolean
|
|
}> = {},
|
|
) {
|
|
const id = uid('rule')
|
|
return prisma.routingRule.create({
|
|
data: {
|
|
id,
|
|
pipelineId,
|
|
name: overrides.name ?? `Rule ${id}`,
|
|
destinationTrackId,
|
|
sourceTrackId: overrides.sourceTrackId ?? null,
|
|
destinationStageId: overrides.destinationStageId ?? null,
|
|
predicateJson: (overrides.predicateJson ?? { field: 'project.status', operator: 'eq', value: 'SUBMITTED' }) as any,
|
|
priority: overrides.priority ?? 0,
|
|
isActive: overrides.isActive ?? true,
|
|
},
|
|
})
|
|
}
|
|
|
|
// ─── Filtering Rule Factory ────────────────────────────────────────────────
|
|
|
|
export async function createTestFilteringRule(
|
|
stageId: string,
|
|
overrides: Partial<{
|
|
name: string
|
|
ruleType: string
|
|
configJson: Record<string, unknown>
|
|
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.routingRule.deleteMany({ where: { 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 } } })
|
|
}
|
|
}
|