diff --git a/src/server/routers/mentor.ts b/src/server/routers/mentor.ts index 35cf2fe..a0f757b 100644 --- a/src/server/routers/mentor.ts +++ b/src/server/routers/mentor.ts @@ -717,6 +717,10 @@ export const mentorRouter = router({ roundId: input.roundId, project: { mentorAssignment: null, + // Only assign mentors to projects whose team has confirmed they will + // attend the grand finale. This skips PENDING/DECLINED/EXPIRED/SUPERSEDED + // confirmations and any project without a confirmation row at all. + finalistConfirmation: { status: 'CONFIRMED' }, ...(eligibility === 'requested_only' ? { wantsMentorship: true } : {}), }, }, diff --git a/tests/unit/mentor-assignment-ux.test.ts b/tests/unit/mentor-assignment-ux.test.ts index 3f91c42..d4fea22 100644 --- a/tests/unit/mentor-assignment-ux.test.ts +++ b/tests/unit/mentor-assignment-ux.test.ts @@ -182,6 +182,29 @@ describe('mentor.autoAssignBulkForRound', () => { data: { projectId: projWithoutRequest.id, roundId: round.id, state: 'PENDING' }, }) + // Both projects must have CONFIRMED FinalistConfirmation to qualify for + // mentor assignment — the wantsMentorship gate is what we're isolating here. + await prisma.finalistConfirmation.createMany({ + data: [ + { + projectId: projWithRequest.id, + category: 'STARTUP', + status: 'CONFIRMED', + deadline: new Date(Date.now() + 86_400_000), + token: `tok_${uid()}`, + confirmedAt: new Date(), + }, + { + projectId: projWithoutRequest.id, + category: 'STARTUP', + status: 'CONFIRMED', + deadline: new Date(Date.now() + 86_400_000), + token: `tok_${uid()}`, + confirmedAt: new Date(), + }, + ], + }) + const mentor = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['x'] }) userIds.push(mentor.id) @@ -230,6 +253,29 @@ describe('mentor.autoAssignBulkForRound', () => { data: { projectId: projUnassigned.id, roundId: round.id, state: 'PENDING' }, }) + // Both projects must have CONFIRMED FinalistConfirmation to qualify for + // mentor assignment — the existing-assignment gate is what we're isolating here. + await prisma.finalistConfirmation.createMany({ + data: [ + { + projectId: projAlreadyAssigned.id, + category: 'STARTUP', + status: 'CONFIRMED', + deadline: new Date(Date.now() + 86_400_000), + token: `tok_${uid()}`, + confirmedAt: new Date(), + }, + { + projectId: projUnassigned.id, + category: 'STARTUP', + status: 'CONFIRMED', + deadline: new Date(Date.now() + 86_400_000), + token: `tok_${uid()}`, + confirmedAt: new Date(), + }, + ], + }) + await prisma.mentorAssignment.create({ data: { projectId: projAlreadyAssigned.id, @@ -251,6 +297,102 @@ describe('mentor.autoAssignBulkForRound', () => { expect(stillExisting?.mentorId).toBe(existingMentor.id) // unchanged }) + it('only assigns mentors to projects with CONFIRMED FinalistConfirmation', async () => { + const admin = await createTestUser('SUPER_ADMIN') + userIds.push(admin.id) + + const program = await createTestProgram({ name: `bulk-finalist-${uid()}` }) + programIds.push(program.id) + const competition = await createTestCompetition(program.id, { status: 'ACTIVE' }) + const round = await createTestRound(competition.id, { + roundType: 'MENTORING', + configJson: { eligibility: 'all_advancing' }, + }) + + const projConfirmed = await prisma.project.create({ + data: { + id: uid('proj'), + title: 'Confirmed Finalist', + programId: program.id, + tags: ['x'], + wantsMentorship: true, + }, + }) + const projPending = await prisma.project.create({ + data: { + id: uid('proj'), + title: 'Pending Finalist', + programId: program.id, + tags: ['x'], + wantsMentorship: true, + }, + }) + const projNoConfirmation = await prisma.project.create({ + data: { + id: uid('proj'), + title: 'No Confirmation', + programId: program.id, + tags: ['x'], + wantsMentorship: true, + }, + }) + + await prisma.projectRoundState.createMany({ + data: [ + { projectId: projConfirmed.id, roundId: round.id, state: 'PENDING' }, + { projectId: projPending.id, roundId: round.id, state: 'PENDING' }, + { projectId: projNoConfirmation.id, roundId: round.id, state: 'PENDING' }, + ], + }) + + await prisma.finalistConfirmation.create({ + data: { + projectId: projConfirmed.id, + category: 'STARTUP', + status: 'CONFIRMED', + deadline: new Date(Date.now() + 86_400_000), + token: `tok_${uid()}`, + confirmedAt: new Date(), + }, + }) + await prisma.finalistConfirmation.create({ + data: { + projectId: projPending.id, + category: 'STARTUP', + status: 'PENDING', + deadline: new Date(Date.now() + 86_400_000), + token: `tok_${uid()}`, + }, + }) + + const mentor = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['x'] }) + userIds.push(mentor.id) + + const caller = createCaller(mentorRouter, { + id: admin.id, + email: admin.email, + role: 'SUPER_ADMIN', + }) + const result = await caller.autoAssignBulkForRound({ roundId: round.id, useAI: false }) + + expect(result.assigned).toBe(1) + + const confirmedAssigned = await prisma.mentorAssignment.findUnique({ + where: { projectId: projConfirmed.id }, + }) + expect(confirmedAssigned).not.toBeNull() + + const pendingAssigned = await prisma.mentorAssignment.findUnique({ + where: { projectId: projPending.id }, + }) + expect(pendingAssigned).toBeNull() + + const noConfAssigned = await prisma.mentorAssignment.findUnique({ + where: { projectId: projNoConfirmation.id }, + }) + expect(noConfAssigned).toBeNull() + }) + it('refuses with admin_selected eligibility (admin must pick manually)', async () => { const admin = await createTestUser('SUPER_ADMIN') userIds.push(admin.id)