/** * U-001: Stage Transition — Legal Transition * U-002: Stage Transition — Illegal Transition */ import { describe, it, expect, beforeAll, afterAll } from 'vitest' import { prisma } from '../setup' import { createTestUser, createTestProgram, createTestPipeline, createTestTrack, createTestStage, createTestTransition, createTestProject, createTestPSS, cleanupTestData, } from '../helpers' import { validateTransition, executeTransition } from '@/server/services/stage-engine' let programId: string let userIds: string[] = [] beforeAll(async () => { const program = await createTestProgram({ name: 'StageEngine Test' }) programId = program.id }) afterAll(async () => { await cleanupTestData(programId, userIds) }) describe('U-001: Legal Transition', () => { it('validates and executes a legal transition between two stages', async () => { const admin = await createTestUser('SUPER_ADMIN') userIds.push(admin.id) const pipeline = await createTestPipeline(programId) const track = await createTestTrack(pipeline.id) const stageA = await createTestStage(track.id, { name: 'Stage A', stageType: 'FILTER', status: 'STAGE_ACTIVE', sortOrder: 0, }) const stageB = await createTestStage(track.id, { name: 'Stage B', stageType: 'EVALUATION', status: 'STAGE_ACTIVE', sortOrder: 1, }) // Create a legal transition path A → B await createTestTransition(stageA.id, stageB.id) // Create project with PSS in stage A const project = await createTestProject(programId) await createTestPSS(project.id, track.id, stageA.id, { state: 'PENDING' }) // Validate const validation = await validateTransition(project.id, stageA.id, stageB.id, prisma) expect(validation.valid).toBe(true) expect(validation.errors).toHaveLength(0) // Execute const result = await executeTransition( project.id, track.id, stageA.id, stageB.id, 'PENDING', admin.id, prisma ) expect(result.success).toBe(true) expect(result.projectStageState).not.toBeNull() expect(result.projectStageState!.stageId).toBe(stageB.id) expect(result.projectStageState!.state).toBe('PENDING') // Verify source PSS was exited const sourcePSS = await prisma.projectStageState.findFirst({ where: { projectId: project.id, stageId: stageA.id }, }) expect(sourcePSS!.exitedAt).not.toBeNull() // Verify dest PSS was created const destPSS = await prisma.projectStageState.findFirst({ where: { projectId: project.id, stageId: stageB.id, exitedAt: null }, }) expect(destPSS).not.toBeNull() expect(destPSS!.state).toBe('PENDING') // Verify audit log entry was created const auditLog = await prisma.decisionAuditLog.findFirst({ where: { entityType: 'ProjectStageState', eventType: 'stage.transitioned', entityId: destPSS!.id, }, }) expect(auditLog).not.toBeNull() expect(auditLog!.actorId).toBe(admin.id) }) }) describe('U-002: Illegal Transition', () => { it('rejects transition when no StageTransition record exists', async () => { const pipeline = await createTestPipeline(programId) const track = await createTestTrack(pipeline.id) const stageA = await createTestStage(track.id, { name: 'Stage A (no path)', stageType: 'FILTER', status: 'STAGE_ACTIVE', sortOrder: 0, }) const stageB = await createTestStage(track.id, { name: 'Stage B (no path)', stageType: 'EVALUATION', status: 'STAGE_ACTIVE', sortOrder: 1, }) // No StageTransition created between A and B const project = await createTestProject(programId) await createTestPSS(project.id, track.id, stageA.id, { state: 'PENDING' }) const validation = await validateTransition(project.id, stageA.id, stageB.id, prisma) expect(validation.valid).toBe(false) expect(validation.errors.length).toBeGreaterThan(0) expect(validation.errors.some(e => e.includes('No transition defined'))).toBe(true) }) it('rejects transition when destination stage is archived', async () => { const pipeline = await createTestPipeline(programId) const track = await createTestTrack(pipeline.id) const stageA = await createTestStage(track.id, { name: 'Active Stage', status: 'STAGE_ACTIVE', sortOrder: 0, }) const stageB = await createTestStage(track.id, { name: 'Archived Stage', status: 'STAGE_ARCHIVED', sortOrder: 1, }) await createTestTransition(stageA.id, stageB.id) const project = await createTestProject(programId) await createTestPSS(project.id, track.id, stageA.id, { state: 'PENDING' }) const validation = await validateTransition(project.id, stageA.id, stageB.id, prisma) expect(validation.valid).toBe(false) expect(validation.errors.some(e => e.includes('archived'))).toBe(true) }) it('rejects transition when project has no active PSS in source stage', async () => { const pipeline = await createTestPipeline(programId) const track = await createTestTrack(pipeline.id) const stageA = await createTestStage(track.id, { name: 'Source', status: 'STAGE_ACTIVE', sortOrder: 0, }) const stageB = await createTestStage(track.id, { name: 'Dest', status: 'STAGE_ACTIVE', sortOrder: 1, }) await createTestTransition(stageA.id, stageB.id) const project = await createTestProject(programId) // No PSS created for this project const validation = await validateTransition(project.id, stageA.id, stageB.id, prisma) expect(validation.valid).toBe(false) expect(validation.errors.some(e => e.includes('no active state'))).toBe(true) }) })