fix(users): populate roles[] with primary role on user creation
All user-creation paths (admin create, bulk invite import, public application contact + team members, project team members, jury-group + special-award invites) now set roles=[role] so the invariant role in roles[] holds for new users, matching seed.ts and the role-change mutations. Prevents the empty roles[] inconsistency that hid primary-role mentors from the mentor picker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -402,6 +402,7 @@ export const applicationRouter = router({
|
|||||||
email: data.contactEmail,
|
email: data.contactEmail,
|
||||||
name: data.contactName,
|
name: data.contactName,
|
||||||
role: 'APPLICANT',
|
role: 'APPLICANT',
|
||||||
|
roles: ['APPLICANT'],
|
||||||
status: 'ACTIVE',
|
status: 'ACTIVE',
|
||||||
phoneNumber: data.contactPhone,
|
phoneNumber: data.contactPhone,
|
||||||
},
|
},
|
||||||
@@ -474,6 +475,7 @@ export const applicationRouter = router({
|
|||||||
email: member.email,
|
email: member.email,
|
||||||
name: member.name,
|
name: member.name,
|
||||||
role: 'APPLICANT',
|
role: 'APPLICANT',
|
||||||
|
roles: ['APPLICANT'],
|
||||||
status: 'NONE',
|
status: 'NONE',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -790,6 +792,7 @@ export const applicationRouter = router({
|
|||||||
email: data.contactEmail,
|
email: data.contactEmail,
|
||||||
name: data.contactName,
|
name: data.contactName,
|
||||||
role: 'APPLICANT',
|
role: 'APPLICANT',
|
||||||
|
roles: ['APPLICANT'],
|
||||||
status: 'ACTIVE',
|
status: 'ACTIVE',
|
||||||
phoneNumber: data.contactPhone,
|
phoneNumber: data.contactPhone,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -440,6 +440,7 @@ export const juryGroupRouter = router({
|
|||||||
email: invitee.email,
|
email: invitee.email,
|
||||||
name: invitee.name || null,
|
name: invitee.name || null,
|
||||||
role: 'JURY_MEMBER',
|
role: 'JURY_MEMBER',
|
||||||
|
roles: ['JURY_MEMBER'],
|
||||||
status: 'INVITED',
|
status: 'INVITED',
|
||||||
inviteToken,
|
inviteToken,
|
||||||
inviteTokenExpiresAt: new Date(Date.now() + expiryMs),
|
inviteTokenExpiresAt: new Date(Date.now() + expiryMs),
|
||||||
|
|||||||
@@ -714,6 +714,7 @@ export const projectRouter = router({
|
|||||||
email: member.email.toLowerCase(),
|
email: member.email.toLowerCase(),
|
||||||
name: member.name,
|
name: member.name,
|
||||||
role: 'APPLICANT',
|
role: 'APPLICANT',
|
||||||
|
roles: ['APPLICANT'],
|
||||||
status: 'NONE',
|
status: 'NONE',
|
||||||
phoneNumber: member.phone || null,
|
phoneNumber: member.phone || null,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -697,6 +697,7 @@ export const specialAwardRouter = router({
|
|||||||
email: invitee.email,
|
email: invitee.email,
|
||||||
name: invitee.name || null,
|
name: invitee.name || null,
|
||||||
role: 'JURY_MEMBER',
|
role: 'JURY_MEMBER',
|
||||||
|
roles: ['JURY_MEMBER'],
|
||||||
status: 'INVITED',
|
status: 'INVITED',
|
||||||
inviteToken,
|
inviteToken,
|
||||||
inviteTokenExpiresAt: new Date(Date.now() + expiryMs),
|
inviteTokenExpiresAt: new Date(Date.now() + expiryMs),
|
||||||
|
|||||||
@@ -510,6 +510,7 @@ export const userRouter = router({
|
|||||||
const user = await ctx.prisma.user.create({
|
const user = await ctx.prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
...input,
|
...input,
|
||||||
|
roles: [input.role],
|
||||||
status: 'INVITED',
|
status: 'INVITED',
|
||||||
inviteToken,
|
inviteToken,
|
||||||
inviteTokenExpiresAt: new Date(Date.now() + expiryHours * 60 * 60 * 1000),
|
inviteTokenExpiresAt: new Date(Date.now() + expiryHours * 60 * 60 * 1000),
|
||||||
@@ -811,6 +812,7 @@ export const userRouter = router({
|
|||||||
email: u.email.toLowerCase(),
|
email: u.email.toLowerCase(),
|
||||||
name: u.name,
|
name: u.name,
|
||||||
role: u.role,
|
role: u.role,
|
||||||
|
roles: [u.role],
|
||||||
expertiseTags: u.expertiseTags,
|
expertiseTags: u.expertiseTags,
|
||||||
status: input.sendInvitation ? 'INVITED' : 'NONE',
|
status: input.sendInvitation ? 'INVITED' : 'NONE',
|
||||||
})),
|
})),
|
||||||
|
|||||||
43
tests/unit/user-create-roles-populated.test.ts
Normal file
43
tests/unit/user-create-roles-populated.test.ts
Normal file
@@ -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'])
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user