diff --git a/src/server/routers/application.ts b/src/server/routers/application.ts index 99e8e13..35f6318 100644 --- a/src/server/routers/application.ts +++ b/src/server/routers/application.ts @@ -402,6 +402,7 @@ export const applicationRouter = router({ email: data.contactEmail, name: data.contactName, role: 'APPLICANT', + roles: ['APPLICANT'], status: 'ACTIVE', phoneNumber: data.contactPhone, }, @@ -474,6 +475,7 @@ export const applicationRouter = router({ email: member.email, name: member.name, role: 'APPLICANT', + roles: ['APPLICANT'], status: 'NONE', }, }) @@ -790,6 +792,7 @@ export const applicationRouter = router({ email: data.contactEmail, name: data.contactName, role: 'APPLICANT', + roles: ['APPLICANT'], status: 'ACTIVE', phoneNumber: data.contactPhone, }, diff --git a/src/server/routers/juryGroup.ts b/src/server/routers/juryGroup.ts index 308e2bf..6df4cc5 100644 --- a/src/server/routers/juryGroup.ts +++ b/src/server/routers/juryGroup.ts @@ -440,6 +440,7 @@ export const juryGroupRouter = router({ email: invitee.email, name: invitee.name || null, role: 'JURY_MEMBER', + roles: ['JURY_MEMBER'], status: 'INVITED', inviteToken, inviteTokenExpiresAt: new Date(Date.now() + expiryMs), diff --git a/src/server/routers/project.ts b/src/server/routers/project.ts index 4d5b859..fc1a997 100644 --- a/src/server/routers/project.ts +++ b/src/server/routers/project.ts @@ -714,6 +714,7 @@ export const projectRouter = router({ email: member.email.toLowerCase(), name: member.name, role: 'APPLICANT', + roles: ['APPLICANT'], status: 'NONE', phoneNumber: member.phone || null, }, diff --git a/src/server/routers/specialAward.ts b/src/server/routers/specialAward.ts index 4782a03..54de207 100644 --- a/src/server/routers/specialAward.ts +++ b/src/server/routers/specialAward.ts @@ -697,6 +697,7 @@ export const specialAwardRouter = router({ email: invitee.email, name: invitee.name || null, role: 'JURY_MEMBER', + roles: ['JURY_MEMBER'], status: 'INVITED', inviteToken, inviteTokenExpiresAt: new Date(Date.now() + expiryMs), diff --git a/src/server/routers/user.ts b/src/server/routers/user.ts index 02cfac9..79af12b 100644 --- a/src/server/routers/user.ts +++ b/src/server/routers/user.ts @@ -510,6 +510,7 @@ export const userRouter = router({ const user = await ctx.prisma.user.create({ data: { ...input, + roles: [input.role], status: 'INVITED', inviteToken, inviteTokenExpiresAt: new Date(Date.now() + expiryHours * 60 * 60 * 1000), @@ -811,6 +812,7 @@ export const userRouter = router({ email: u.email.toLowerCase(), name: u.name, role: u.role, + roles: [u.role], expertiseTags: u.expertiseTags, status: input.sendInvitation ? 'INVITED' : 'NONE', })), diff --git a/tests/unit/user-create-roles-populated.test.ts b/tests/unit/user-create-roles-populated.test.ts new file mode 100644 index 0000000..a27694c --- /dev/null +++ b/tests/unit/user-create-roles-populated.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import { prisma, createCaller } from '../setup' +import { createTestProgram, createTestUser, cleanupTestData, uid } from '../helpers' +import { userRouter } from '../../src/server/routers/user' + +/** + * Regression: user-creation paths must populate roles[] with the primary role, + * so the invariant role ∈ roles holds for new users (prevents the empty-roles[] + * inconsistency that made primary-role mentors un-addable). Covers the admin + * `user.create` path as the representative case. + */ +describe('user.create — populates roles[] with the primary role', () => { + let programId = '' + const userIds: string[] = [] + + beforeAll(async () => { + const program = await createTestProgram() + programId = program.id + }) + + afterAll(async () => { + await cleanupTestData(programId, userIds) + }) + + it('sets roles=[role] on an admin-created user', async () => { + const admin = await createTestUser('SUPER_ADMIN') + userIds.push(admin.id) + const caller = createCaller(userRouter, { + id: admin.id, + email: admin.email, + role: 'SUPER_ADMIN', + }) + + const email = `${uid('created')}@test.local` + await caller.create({ email, name: 'New Member', role: 'JURY_MEMBER' }) + + const created = await prisma.user.findUnique({ where: { email } }) + expect(created).not.toBeNull() + if (created) userIds.push(created.id) + expect(created?.role).toBe('JURY_MEMBER') + expect(created?.roles).toEqual(['JURY_MEMBER']) + }) +})