import { afterAll, describe, expect, it } from 'vitest' import { prisma, createCaller } from '../setup' import { createTestUser, createTestProgram, createTestProject, createTestCompetition, createTestRound, cleanupTestData, uid, } from '../helpers' import { finalistRouter } from '../../src/server/routers/finalist' async function createApplicant(role: 'LEAD' | 'MEMBER' = 'MEMBER') { const id = uid('user') return prisma.user.create({ data: { id, email: `${id}@test.local`, name: `Test ${role}`, role: 'APPLICANT', roles: ['APPLICANT'], status: 'ACTIVE', }, }) } async function setup(opts: { programName: string status?: 'PENDING' | 'CONFIRMED' | 'DECLINED' }) { const program = await createTestProgram({ name: opts.programName, defaultAttendeeCap: 3 }) const project = await createTestProject(program.id, { title: 'P', competitionCategory: 'STARTUP', }) const lead = await createApplicant('LEAD') const member = await createApplicant('MEMBER') await prisma.teamMember.createMany({ data: [ { projectId: project.id, userId: lead.id, role: 'LEAD' }, { projectId: project.id, userId: member.id, role: 'MEMBER' }, ], }) const competition = await createTestCompetition(program.id) await createTestRound(competition.id, { roundType: 'LIVE_FINAL', sortOrder: 99, configJson: { confirmationWindowHours: 24 }, }) const confirmation = await prisma.finalistConfirmation.create({ data: { projectId: project.id, category: 'STARTUP', status: opts.status ?? 'PENDING', deadline: new Date(Date.now() + 86_400_000), token: `tok_${uid()}`, confirmedAt: opts.status === 'CONFIRMED' ? new Date() : null, }, }) return { program, project, lead, member, confirmation } } describe('finalist.adminConfirm', () => { const programIds: string[] = [] const userIds: string[] = [] afterAll(async () => { for (const programId of programIds) { await prisma.attendingMember.deleteMany({ where: { confirmation: { project: { programId } } }, }) await prisma.finalistConfirmation.deleteMany({ where: { project: { programId } } }) await cleanupTestData(programId, []) } if (userIds.length > 0) { await prisma.user.deleteMany({ where: { id: { in: userIds } } }) } }) it('flips PENDING → CONFIRMED with attendee rows + audit log', async () => { const admin = await createTestUser('SUPER_ADMIN') userIds.push(admin.id) const { program, lead, member, confirmation } = await setup({ programName: `admin-confirm-${uid()}`, }) programIds.push(program.id) userIds.push(lead.id, member.id) const caller = createCaller(finalistRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN', }) await caller.adminConfirm({ confirmationId: confirmation.id, attendingUserIds: [lead.id, member.id], visaFlags: { [member.id]: true }, }) const updated = await prisma.finalistConfirmation.findUniqueOrThrow({ where: { id: confirmation.id }, }) expect(updated.status).toBe('CONFIRMED') expect(updated.confirmedAt).not.toBeNull() const attendees = await prisma.attendingMember.findMany({ where: { confirmationId: confirmation.id }, }) expect(attendees).toHaveLength(2) expect(attendees.find((a) => a.userId === member.id)?.needsVisa).toBe(true) const audit = await prisma.auditLog.findFirst({ where: { action: 'FINALIST_ADMIN_CONFIRM', entityId: confirmation.id }, }) expect(audit).not.toBeNull() }) it('rejects when confirmation is not PENDING', async () => { const admin = await createTestUser('SUPER_ADMIN') userIds.push(admin.id) const { program, lead, member, confirmation } = await setup({ programName: `admin-confirm-already-${uid()}`, status: 'CONFIRMED', }) programIds.push(program.id) userIds.push(lead.id, member.id) const caller = createCaller(finalistRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN', }) await expect( caller.adminConfirm({ confirmationId: confirmation.id, attendingUserIds: [lead.id], }), ).rejects.toThrow(/PENDING/i) }) it('rejects when attendee is not a team member', async () => { const admin = await createTestUser('SUPER_ADMIN') userIds.push(admin.id) const { program, lead, member, confirmation } = await setup({ programName: `admin-confirm-foreign-${uid()}`, }) programIds.push(program.id) userIds.push(lead.id, member.id) const outsider = await createApplicant('MEMBER') userIds.push(outsider.id) const caller = createCaller(finalistRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN', }) await expect( caller.adminConfirm({ confirmationId: confirmation.id, attendingUserIds: [lead.id, outsider.id], }), ).rejects.toThrow(/not a team member/i) }) }) describe('finalist.adminDecline', () => { const programIds: string[] = [] const userIds: string[] = [] afterAll(async () => { for (const programId of programIds) { await prisma.attendingMember.deleteMany({ where: { confirmation: { project: { programId } } }, }) await prisma.waitlistEntry.deleteMany({ where: { programId } }) await prisma.finalistConfirmation.deleteMany({ where: { project: { programId } } }) await cleanupTestData(programId, []) } if (userIds.length > 0) { await prisma.user.deleteMany({ where: { id: { in: userIds } } }) } }) it('flips PENDING → DECLINED with reason + audit log', async () => { const admin = await createTestUser('SUPER_ADMIN') userIds.push(admin.id) const { program, lead, member, confirmation } = await setup({ programName: `admin-decline-${uid()}`, }) programIds.push(program.id) userIds.push(lead.id, member.id) const caller = createCaller(finalistRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN', }) await caller.adminDecline({ confirmationId: confirmation.id, reason: 'Team replied by email — schedule conflict', }) const updated = await prisma.finalistConfirmation.findUniqueOrThrow({ where: { id: confirmation.id }, }) expect(updated.status).toBe('DECLINED') expect(updated.declineReason).toContain('schedule conflict') const audit = await prisma.auditLog.findFirst({ where: { action: 'FINALIST_ADMIN_DECLINE', entityId: confirmation.id }, }) expect(audit).not.toBeNull() }) it('rejects when confirmation is not PENDING', async () => { const admin = await createTestUser('SUPER_ADMIN') userIds.push(admin.id) const { program, lead, member, confirmation } = await setup({ programName: `admin-decline-already-${uid()}`, status: 'CONFIRMED', }) programIds.push(program.id) userIds.push(lead.id, member.id) const caller = createCaller(finalistRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN', }) await expect( caller.adminDecline({ confirmationId: confirmation.id }), ).rejects.toThrow(/PENDING/i) }) })