/** * Deliberation jury wiring — regression for two launch blockers: * 1. submitVote compared the JuryGroupMember id against User.id (FORBIDDEN for * every legitimate juror). The server now resolves the caller's participant * row itself; the client never sends an identity. * 2. getSession had no project list before finalize (the ranking form rendered * empty). It now includes the round's projects filtered by session category. */ import { describe, it, expect, beforeAll, afterAll } from 'vitest' import { prisma, createCaller } from '../setup' import { createTestUser, createTestProgram, createTestCompetition, createTestRound, createTestProject, createTestProjectRoundState, cleanupTestData, uid, } from '../helpers' import { deliberationRouter } from '@/server/routers/deliberation' let program: any let competition: any let delibRound: any let startup1: any let startup2: any let concept1: any let juror: any let outsiderJuror: any let admin: any let sessionId: string let adminCaller: ReturnType let jurorCaller: ReturnType beforeAll(async () => { program = await createTestProgram() competition = await createTestCompetition(program.id) delibRound = await createTestRound(competition.id, { roundType: 'DELIBERATION', status: 'ROUND_ACTIVE', }) startup1 = await createTestProject(program.id, { competitionCategory: 'STARTUP' }) startup2 = await createTestProject(program.id, { competitionCategory: 'STARTUP' }) concept1 = await createTestProject(program.id, { competitionCategory: 'BUSINESS_CONCEPT' }) await createTestProjectRoundState(startup1.id, delibRound.id) await createTestProjectRoundState(startup2.id, delibRound.id) await createTestProjectRoundState(concept1.id, delibRound.id) const juryGroup = await prisma.juryGroup.create({ data: { competitionId: competition.id, name: 'Finals Jury', slug: uid('jg') }, }) juror = await createTestUser('JURY_MEMBER') outsiderJuror = await createTestUser('JURY_MEMBER') admin = await createTestUser('SUPER_ADMIN') const member = await prisma.juryGroupMember.create({ data: { juryGroupId: juryGroup.id, userId: juror.id, role: 'MEMBER' }, }) adminCaller = createCaller(deliberationRouter, admin) jurorCaller = createCaller(deliberationRouter, juror) const session = await adminCaller.createSession({ competitionId: competition.id, roundId: delibRound.id, category: 'STARTUP', mode: 'FULL_RANKING', tieBreakMethod: 'TIE_ADMIN_DECIDES', participantUserIds: [member.id], // JuryGroupMember IDs }) sessionId = session.id await adminCaller.openVoting({ sessionId }) }) afterAll(async () => { await cleanupTestData(program.id, [juror.id, outsiderJuror.id, admin.id]) }) describe('getSession projects', () => { it('exposes the category projects before any results exist', async () => { const session = await jurorCaller.getSession({ sessionId }) const ids = (session.projects ?? []).map((p: any) => p.id).sort() expect(ids).toEqual([startup1.id, startup2.id].sort()) // off-category project excluded expect(ids).not.toContain(concept1.id) }) }) describe('submitVote identity resolution', () => { it('lets a participant juror vote without sending any identity', async () => { await jurorCaller.submitVote({ sessionId, projectId: startup1.id, rank: 1 }) await jurorCaller.submitVote({ sessionId, projectId: startup2.id, rank: 2 }) const session = await jurorCaller.getSession({ sessionId }) const myVotes = (session.votes ?? []).filter( (v: any) => v.juryMember?.user?.id === juror.id ) expect(myVotes).toHaveLength(2) }) it('rejects a juror who is not a participant', async () => { const outsiderCaller = createCaller(deliberationRouter, outsiderJuror) await expect( outsiderCaller.submitVote({ sessionId, projectId: startup1.id, rank: 1 }) ).rejects.toThrow(/participant/i) }) it('aggregates the resolved votes (sanity end-to-end)', async () => { const agg = await adminCaller.aggregate({ sessionId }) expect(agg.rankings.length).toBeGreaterThan(0) expect(agg.rankings[0].projectId).toBe(startup1.id) // rank 1 → top Borda points }) })