Competition/Round architecture: full platform rewrite (Phases 1-9)
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
Replace Pipeline/Stage system with Competition/Round architecture. New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy, ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow. New services: round-engine, round-assignment, deliberation, result-lock, submission-manager, competition-context, ai-prompt-guard. Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with structured prompts, retry logic, and injection detection. All legacy pipeline/stage code removed. 4 new migrations + seed aligned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
163
tests/helpers.ts
163
tests/helpers.ts
@@ -10,10 +10,10 @@ import { randomUUID } from 'crypto'
|
||||
import { prisma } from './setup'
|
||||
import type {
|
||||
UserRole,
|
||||
StageType,
|
||||
StageStatus,
|
||||
TrackKind,
|
||||
ProjectStageStateValue,
|
||||
RoundType,
|
||||
RoundStatus,
|
||||
CompetitionStatus,
|
||||
ProjectRoundStateValue,
|
||||
AssignmentMethod,
|
||||
} from '@prisma/client'
|
||||
|
||||
@@ -65,76 +65,52 @@ export async function createTestProgram(
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Pipeline Factory ──────────────────────────────────────────────────────
|
||||
// ─── Competition Factory ───────────────────────────────────────────────────
|
||||
|
||||
export async function createTestPipeline(
|
||||
export async function createTestCompetition(
|
||||
programId: string,
|
||||
overrides: Partial<{ name: string; slug: string; status: string }> = {},
|
||||
overrides: Partial<{
|
||||
name: string
|
||||
slug: string
|
||||
status: CompetitionStatus
|
||||
}> = {},
|
||||
) {
|
||||
const id = uid('pipe')
|
||||
return prisma.pipeline.create({
|
||||
const id = uid('comp')
|
||||
return prisma.competition.create({
|
||||
data: {
|
||||
id,
|
||||
programId,
|
||||
name: overrides.name ?? `Pipeline ${id}`,
|
||||
name: overrides.name ?? `Competition ${id}`,
|
||||
slug: overrides.slug ?? id,
|
||||
status: overrides.status ?? 'DRAFT',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Track Factory ─────────────────────────────────────────────────────────
|
||||
// ─── Round Factory ─────────────────────────────────────────────────────────
|
||||
|
||||
export async function createTestTrack(
|
||||
pipelineId: string,
|
||||
export async function createTestRound(
|
||||
competitionId: 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
|
||||
roundType: RoundType
|
||||
status: RoundStatus
|
||||
sortOrder: number
|
||||
configJson: Record<string, unknown>
|
||||
windowOpenAt: Date
|
||||
windowCloseAt: Date
|
||||
}> = {},
|
||||
) {
|
||||
const id = uid('stage')
|
||||
return prisma.stage.create({
|
||||
const id = uid('round')
|
||||
return prisma.round.create({
|
||||
data: {
|
||||
id,
|
||||
trackId,
|
||||
name: overrides.name ?? `Stage ${id}`,
|
||||
competitionId,
|
||||
name: overrides.name ?? `Round ${id}`,
|
||||
slug: overrides.slug ?? id,
|
||||
stageType: overrides.stageType ?? 'EVALUATION',
|
||||
status: overrides.status ?? 'STAGE_ACTIVE',
|
||||
roundType: overrides.roundType ?? 'EVALUATION',
|
||||
status: overrides.status ?? 'ROUND_ACTIVE',
|
||||
sortOrder: overrides.sortOrder ?? 0,
|
||||
configJson: (overrides.configJson as any) ?? undefined,
|
||||
windowOpenAt: overrides.windowOpenAt ?? null,
|
||||
@@ -143,23 +119,6 @@ export async function createTestStage(
|
||||
})
|
||||
}
|
||||
|
||||
// ─── 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(
|
||||
@@ -188,22 +147,20 @@ export async function createTestProject(
|
||||
})
|
||||
}
|
||||
|
||||
// ─── ProjectStageState Factory ─────────────────────────────────────────────
|
||||
// ─── ProjectRoundState Factory ─────────────────────────────────────────────
|
||||
|
||||
export async function createTestPSS(
|
||||
export async function createTestProjectRoundState(
|
||||
projectId: string,
|
||||
trackId: string,
|
||||
stageId: string,
|
||||
roundId: string,
|
||||
overrides: Partial<{
|
||||
state: ProjectStageStateValue
|
||||
state: ProjectRoundStateValue
|
||||
exitedAt: Date | null
|
||||
}> = {},
|
||||
) {
|
||||
return prisma.projectStageState.create({
|
||||
return prisma.projectRoundState.create({
|
||||
data: {
|
||||
projectId,
|
||||
trackId,
|
||||
stageId,
|
||||
roundId,
|
||||
state: overrides.state ?? 'PENDING',
|
||||
exitedAt: overrides.exitedAt ?? null,
|
||||
},
|
||||
@@ -215,7 +172,7 @@ export async function createTestPSS(
|
||||
export async function createTestAssignment(
|
||||
userId: string,
|
||||
projectId: string,
|
||||
stageId: string,
|
||||
roundId: string,
|
||||
overrides: Partial<{
|
||||
method: AssignmentMethod
|
||||
isCompleted: boolean
|
||||
@@ -225,7 +182,7 @@ export async function createTestAssignment(
|
||||
data: {
|
||||
userId,
|
||||
projectId,
|
||||
stageId,
|
||||
roundId,
|
||||
method: overrides.method ?? 'MANUAL',
|
||||
isCompleted: overrides.isCompleted ?? false,
|
||||
},
|
||||
@@ -235,7 +192,7 @@ export async function createTestAssignment(
|
||||
// ─── Evaluation Form Factory ───────────────────────────────────────────────
|
||||
|
||||
export async function createTestEvaluationForm(
|
||||
stageId: string,
|
||||
roundId: string,
|
||||
criteria: Array<{
|
||||
id: string
|
||||
label: string
|
||||
@@ -245,7 +202,7 @@ export async function createTestEvaluationForm(
|
||||
) {
|
||||
return prisma.evaluationForm.create({
|
||||
data: {
|
||||
stageId,
|
||||
roundId,
|
||||
criteriaJson: criteria.length > 0
|
||||
? criteria
|
||||
: [
|
||||
@@ -261,7 +218,7 @@ export async function createTestEvaluationForm(
|
||||
// ─── Filtering Rule Factory ────────────────────────────────────────────────
|
||||
|
||||
export async function createTestFilteringRule(
|
||||
stageId: string,
|
||||
roundId: string,
|
||||
overrides: Partial<{
|
||||
name: string
|
||||
ruleType: string
|
||||
@@ -271,7 +228,7 @@ export async function createTestFilteringRule(
|
||||
) {
|
||||
return prisma.filteringRule.create({
|
||||
data: {
|
||||
stageId,
|
||||
roundId,
|
||||
name: overrides.name ?? 'Test Filter Rule',
|
||||
ruleType: (overrides.ruleType as any) ?? 'DOCUMENT_CHECK',
|
||||
configJson: (overrides.configJson ?? { requiredFileTypes: ['EXEC_SUMMARY'], action: 'REJECT' }) as any,
|
||||
@@ -304,7 +261,7 @@ export async function createTestCOI(
|
||||
// ─── Cohort + CohortProject Factory ────────────────────────────────────────
|
||||
|
||||
export async function createTestCohort(
|
||||
stageId: string,
|
||||
roundId: string,
|
||||
overrides: Partial<{
|
||||
name: string
|
||||
isOpen: boolean
|
||||
@@ -315,7 +272,7 @@ export async function createTestCohort(
|
||||
return prisma.cohort.create({
|
||||
data: {
|
||||
id,
|
||||
stageId,
|
||||
roundId,
|
||||
name: overrides.name ?? `Cohort ${id}`,
|
||||
isOpen: overrides.isOpen ?? false,
|
||||
votingMode: overrides.votingMode ?? 'simple',
|
||||
@@ -350,31 +307,31 @@ export async function cleanupTestData(programId: string, userIds: string[] = [])
|
||||
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 } } } } })
|
||||
// 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.stage.deleteMany({ where: { track: { pipeline: { programId } } } })
|
||||
await prisma.track.deleteMany({ where: { pipeline: { programId } } })
|
||||
await prisma.pipeline.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 } } })
|
||||
|
||||
Reference in New Issue
Block a user