2026-04-28 15:24:07 +02:00
|
|
|
import { afterAll, describe, expect, it } from 'vitest'
|
|
|
|
|
import { prisma, createCaller } from '../setup'
|
|
|
|
|
import {
|
|
|
|
|
createTestUser,
|
|
|
|
|
createTestProgram,
|
|
|
|
|
createTestProject,
|
|
|
|
|
createTestCompetition,
|
|
|
|
|
createTestRound,
|
|
|
|
|
cleanupTestData,
|
|
|
|
|
uid,
|
|
|
|
|
} from '../helpers'
|
|
|
|
|
import { mentorRouter } from '../../src/server/routers/mentor'
|
|
|
|
|
import type { UserRole } from '@prisma/client'
|
|
|
|
|
|
|
|
|
|
async function createUserWithRoles(
|
|
|
|
|
primaryRole: UserRole,
|
|
|
|
|
rolesArray: UserRole[],
|
|
|
|
|
overrides: { expertiseTags?: string[]; maxAssignments?: number | null; country?: string | null } = {},
|
|
|
|
|
) {
|
|
|
|
|
const id = uid('user')
|
|
|
|
|
return prisma.user.create({
|
|
|
|
|
data: {
|
|
|
|
|
id,
|
|
|
|
|
email: `${id}@test.local`,
|
|
|
|
|
name: `Test ${primaryRole}`,
|
|
|
|
|
role: primaryRole,
|
|
|
|
|
roles: rolesArray,
|
|
|
|
|
status: 'ACTIVE',
|
|
|
|
|
expertiseTags: overrides.expertiseTags ?? [],
|
|
|
|
|
maxAssignments: overrides.maxAssignments ?? null,
|
|
|
|
|
country: overrides.country ?? null,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('mentor.getRoundStats', () => {
|
|
|
|
|
const programIds: string[] = []
|
|
|
|
|
const userIds: string[] = []
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
for (const programId of programIds) {
|
|
|
|
|
await prisma.mentorAssignment.deleteMany({ where: { project: { programId } } })
|
|
|
|
|
await cleanupTestData(programId, [])
|
|
|
|
|
}
|
|
|
|
|
if (userIds.length > 0) {
|
|
|
|
|
await prisma.user.deleteMany({ where: { id: { in: userIds } } })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns counts of total / requested / assigned / awaiting + activity totals', async () => {
|
|
|
|
|
const admin = await createTestUser('SUPER_ADMIN')
|
|
|
|
|
userIds.push(admin.id)
|
|
|
|
|
const program = await createTestProgram({ name: `round-stats-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const competition = await createTestCompetition(program.id, { status: 'ACTIVE' })
|
|
|
|
|
const round = await createTestRound(competition.id, {
|
|
|
|
|
roundType: 'MENTORING',
|
|
|
|
|
configJson: { eligibility: 'requested_only', mentoringRequestDeadlineDays: 14 },
|
|
|
|
|
windowOpenAt: new Date('2026-04-01T00:00:00Z'),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const projReqAssigned = await createTestProject(program.id, { title: 'Req+Assigned', tags: [] })
|
|
|
|
|
const projReqAwaiting = await createTestProject(program.id, { title: 'Req+Awaiting', tags: [] })
|
|
|
|
|
const projNotReqAssigned = await createTestProject(program.id, { title: 'NotReq+Assigned', tags: [] })
|
|
|
|
|
const projNotReq = await createTestProject(program.id, { title: 'NotReq', tags: [] })
|
|
|
|
|
await prisma.project.update({ where: { id: projReqAssigned.id }, data: { wantsMentorship: true } })
|
|
|
|
|
await prisma.project.update({ where: { id: projReqAwaiting.id }, data: { wantsMentorship: true } })
|
|
|
|
|
|
|
|
|
|
for (const p of [projReqAssigned, projReqAwaiting, projNotReqAssigned, projNotReq]) {
|
|
|
|
|
await prisma.projectRoundState.create({
|
|
|
|
|
data: { projectId: p.id, roundId: round.id, state: 'PENDING' },
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mentor = await createUserWithRoles('MENTOR', ['MENTOR'])
|
|
|
|
|
userIds.push(mentor.id)
|
|
|
|
|
|
|
|
|
|
const a1 = await prisma.mentorAssignment.create({
|
|
|
|
|
data: { projectId: projReqAssigned.id, mentorId: mentor.id, method: 'MANUAL', assignedBy: admin.id, workspaceEnabled: true },
|
|
|
|
|
})
|
|
|
|
|
await prisma.mentorAssignment.create({
|
|
|
|
|
data: { projectId: projNotReqAssigned.id, mentorId: mentor.id, method: 'MANUAL', assignedBy: admin.id, workspaceEnabled: true },
|
|
|
|
|
})
|
|
|
|
|
await prisma.mentorMessage.create({
|
|
|
|
|
data: { projectId: projReqAssigned.id, senderId: mentor.id, message: 'hello', workspaceId: a1.id },
|
|
|
|
|
})
|
|
|
|
|
await prisma.mentorMessage.create({
|
|
|
|
|
data: { projectId: projReqAssigned.id, senderId: mentor.id, message: 'still here', workspaceId: a1.id },
|
|
|
|
|
})
|
|
|
|
|
await prisma.mentorMessage.create({
|
|
|
|
|
data: { projectId: projNotReqAssigned.id, senderId: mentor.id, message: 'on it' },
|
|
|
|
|
})
|
|
|
|
|
await prisma.mentorFile.create({
|
|
|
|
|
data: {
|
2026-05-22 16:37:37 +02:00
|
|
|
projectId: projReqAssigned.id,
|
2026-04-28 15:24:07 +02:00
|
|
|
mentorAssignmentId: a1.id,
|
|
|
|
|
uploadedByUserId: mentor.id,
|
|
|
|
|
fileName: 'plan.pdf',
|
|
|
|
|
objectKey: 'k',
|
|
|
|
|
bucket: 'b',
|
|
|
|
|
mimeType: 'application/pdf',
|
|
|
|
|
size: 1,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
|
|
|
|
|
const stats = await caller.getRoundStats({ roundId: round.id })
|
|
|
|
|
|
|
|
|
|
expect(stats.totalProjects).toBe(4)
|
|
|
|
|
expect(stats.requestedCount).toBe(2)
|
|
|
|
|
expect(stats.assignedCount).toBe(2)
|
|
|
|
|
expect(stats.awaitingAssignment).toBe(1)
|
|
|
|
|
expect(stats.workspaceActivity.messageCount).toBe(3)
|
|
|
|
|
expect(stats.workspaceActivity.fileCount).toBe(1)
|
|
|
|
|
expect(stats.workspaceActivity.lastActivityAt).not.toBeNull()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('exposes request-window deadline computed from windowOpenAt + deadlineDays', async () => {
|
|
|
|
|
const admin = await createTestUser('SUPER_ADMIN')
|
|
|
|
|
userIds.push(admin.id)
|
|
|
|
|
const program = await createTestProgram({ name: `round-stats-window-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const competition = await createTestCompetition(program.id, { status: 'ACTIVE' })
|
|
|
|
|
const round = await createTestRound(competition.id, {
|
|
|
|
|
roundType: 'MENTORING',
|
|
|
|
|
configJson: { eligibility: 'requested_only', mentoringRequestDeadlineDays: 7 },
|
|
|
|
|
windowOpenAt: new Date('2026-05-01T00:00:00Z'),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
|
|
|
|
|
const stats = await caller.getRoundStats({ roundId: round.id })
|
|
|
|
|
|
|
|
|
|
expect(stats.requestWindow.deadline).not.toBeNull()
|
|
|
|
|
expect(new Date(stats.requestWindow.deadline!).toISOString()).toBe('2026-05-08T00:00:00.000Z')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns zeros for an empty round', async () => {
|
|
|
|
|
const admin = await createTestUser('SUPER_ADMIN')
|
|
|
|
|
userIds.push(admin.id)
|
|
|
|
|
const program = await createTestProgram({ name: `round-stats-empty-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const competition = await createTestCompetition(program.id, { status: 'ACTIVE' })
|
|
|
|
|
const round = await createTestRound(competition.id, {
|
|
|
|
|
roundType: 'MENTORING',
|
|
|
|
|
configJson: { eligibility: 'requested_only' },
|
|
|
|
|
})
|
|
|
|
|
const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
|
|
|
|
|
const stats = await caller.getRoundStats({ roundId: round.id })
|
|
|
|
|
expect(stats.totalProjects).toBe(0)
|
|
|
|
|
expect(stats.requestedCount).toBe(0)
|
|
|
|
|
expect(stats.assignedCount).toBe(0)
|
|
|
|
|
expect(stats.awaitingAssignment).toBe(0)
|
|
|
|
|
expect(stats.workspaceActivity.messageCount).toBe(0)
|
|
|
|
|
expect(stats.workspaceActivity.fileCount).toBe(0)
|
|
|
|
|
expect(stats.workspaceActivity.lastActivityAt).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('mentor.getMentorPool', () => {
|
|
|
|
|
const programIds: string[] = []
|
|
|
|
|
const userIds: string[] = []
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
for (const programId of programIds) {
|
|
|
|
|
await prisma.mentorAssignment.deleteMany({ where: { project: { programId } } })
|
|
|
|
|
await cleanupTestData(programId, [])
|
|
|
|
|
}
|
|
|
|
|
if (userIds.length > 0) {
|
|
|
|
|
await prisma.user.deleteMany({ where: { id: { in: userIds } } })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns all MENTOR-role users with current/completed counts and capacity', async () => {
|
|
|
|
|
const admin = await createTestUser('SUPER_ADMIN')
|
|
|
|
|
userIds.push(admin.id)
|
|
|
|
|
const program = await createTestProgram({ name: `pool-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
|
|
|
|
|
const m1 = await createUserWithRoles('MENTOR', ['MENTOR'], {
|
|
|
|
|
expertiseTags: ['biology'],
|
|
|
|
|
maxAssignments: 5,
|
|
|
|
|
country: 'France',
|
|
|
|
|
})
|
|
|
|
|
const m2 = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['policy'] })
|
|
|
|
|
userIds.push(m1.id, m2.id)
|
|
|
|
|
|
|
|
|
|
const project = await createTestProject(program.id, { title: 'P1', tags: [] })
|
|
|
|
|
await prisma.mentorAssignment.create({
|
|
|
|
|
data: {
|
|
|
|
|
projectId: project.id, mentorId: m1.id, method: 'MANUAL',
|
|
|
|
|
assignedBy: admin.id, completionStatus: 'in_progress',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
|
|
|
|
|
const result = await caller.getMentorPool({})
|
|
|
|
|
|
|
|
|
|
const mentor1 = result.mentors.find((m: { id: string }) => m.id === m1.id)!
|
|
|
|
|
const mentor2 = result.mentors.find((m: { id: string }) => m.id === m2.id)!
|
|
|
|
|
expect(mentor1).toBeDefined()
|
|
|
|
|
expect(mentor2).toBeDefined()
|
|
|
|
|
expect(mentor1.currentAssignments).toBe(1)
|
|
|
|
|
expect(mentor1.completedAssignments).toBe(0)
|
|
|
|
|
expect(mentor1.maxAssignments).toBe(5)
|
|
|
|
|
expect(mentor1.capacityRemaining).toBe(4)
|
|
|
|
|
expect(mentor1.country).toBe('France')
|
|
|
|
|
expect(mentor2.currentAssignments).toBe(0)
|
|
|
|
|
expect(mentor2.maxAssignments).toBeNull()
|
|
|
|
|
expect(mentor2.capacityRemaining).toBeNull()
|
|
|
|
|
|
|
|
|
|
expect(result.poolSize).toBeGreaterThanOrEqual(2)
|
|
|
|
|
expect(result.totalCurrentAssignments).toBeGreaterThanOrEqual(1)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('counts completed assignments separately from in-progress', async () => {
|
|
|
|
|
const admin = await createTestUser('SUPER_ADMIN')
|
|
|
|
|
userIds.push(admin.id)
|
|
|
|
|
const program = await createTestProgram({ name: `pool-completed-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
|
|
|
|
|
const m = await createUserWithRoles('MENTOR', ['MENTOR'])
|
|
|
|
|
userIds.push(m.id)
|
|
|
|
|
|
|
|
|
|
const projInProgress = await createTestProject(program.id, { title: 'A', tags: [] })
|
|
|
|
|
const projCompleted = await createTestProject(program.id, { title: 'B', tags: [] })
|
|
|
|
|
await prisma.mentorAssignment.create({
|
|
|
|
|
data: {
|
|
|
|
|
projectId: projInProgress.id, mentorId: m.id, method: 'MANUAL',
|
|
|
|
|
assignedBy: admin.id, completionStatus: 'in_progress',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
await prisma.mentorAssignment.create({
|
|
|
|
|
data: {
|
|
|
|
|
projectId: projCompleted.id, mentorId: m.id, method: 'MANUAL',
|
|
|
|
|
assignedBy: admin.id, completionStatus: 'completed',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
|
|
|
|
|
const result = await caller.getMentorPool({})
|
|
|
|
|
const found = result.mentors.find((x: { id: string }) => x.id === m.id)!
|
|
|
|
|
expect(found.currentAssignments).toBe(1)
|
|
|
|
|
expect(found.completedAssignments).toBe(1)
|
|
|
|
|
})
|
|
|
|
|
})
|