2026-04-29 02:30:06 +02:00
|
|
|
import { afterAll, describe, expect, it } from 'vitest'
|
|
|
|
|
import { prisma, createCaller } from '../setup'
|
|
|
|
|
import {
|
|
|
|
|
createTestUser,
|
|
|
|
|
createTestProgram,
|
|
|
|
|
createTestProject,
|
|
|
|
|
cleanupTestData,
|
|
|
|
|
uid,
|
|
|
|
|
} from '../helpers'
|
|
|
|
|
import { lunchRouter } from '@/server/routers/lunch'
|
|
|
|
|
|
|
|
|
|
const programIds: string[] = []
|
|
|
|
|
const userIds: string[] = []
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
for (const programId of programIds) {
|
|
|
|
|
await prisma.memberLunchPick.deleteMany({
|
|
|
|
|
where: { attendingMember: { confirmation: { project: { programId } } } },
|
|
|
|
|
})
|
|
|
|
|
await prisma.attendingMember.deleteMany({
|
|
|
|
|
where: { confirmation: { project: { programId } } },
|
|
|
|
|
})
|
|
|
|
|
await prisma.finalistConfirmation.deleteMany({ where: { project: { programId } } })
|
|
|
|
|
await prisma.externalAttendee.deleteMany({ where: { lunchEvent: { programId } } })
|
|
|
|
|
await prisma.dish.deleteMany({ where: { lunchEvent: { programId } } })
|
|
|
|
|
await prisma.lunchEvent.deleteMany({ where: { programId } })
|
|
|
|
|
await cleanupTestData(programId, [])
|
|
|
|
|
}
|
|
|
|
|
if (userIds.length > 0) {
|
|
|
|
|
await prisma.auditLog.deleteMany({ where: { userId: { in: userIds } } })
|
|
|
|
|
await prisma.user.deleteMany({ where: { id: { in: userIds } } })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
async function newAdminCaller() {
|
|
|
|
|
const admin = await createTestUser('SUPER_ADMIN')
|
|
|
|
|
userIds.push(admin.id)
|
|
|
|
|
const caller = createCaller(lunchRouter, {
|
|
|
|
|
id: admin.id,
|
|
|
|
|
email: admin.email,
|
|
|
|
|
role: 'SUPER_ADMIN',
|
|
|
|
|
})
|
|
|
|
|
return { admin, caller }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('lunch.getEvent', () => {
|
|
|
|
|
it('lazily creates a LunchEvent on first call', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `lunch-get-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const result = await caller.getEvent({ programId: program.id })
|
|
|
|
|
expect(result.programId).toBe(program.id)
|
|
|
|
|
expect(result.enabled).toBe(false)
|
|
|
|
|
expect(result.changeCutoffHours).toBe(48)
|
|
|
|
|
const row = await prisma.lunchEvent.findUnique({ where: { programId: program.id } })
|
|
|
|
|
expect(row).not.toBeNull()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns the same row on subsequent calls', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `lunch-get-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const a = await caller.getEvent({ programId: program.id })
|
|
|
|
|
const b = await caller.getEvent({ programId: program.id })
|
|
|
|
|
expect(a.id).toBe(b.id)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-29 02:30:49 +02:00
|
|
|
describe('dish CRUD', () => {
|
|
|
|
|
it('createDish + listDishes returns dishes ordered by sortOrder', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `dish-list-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const event = await caller.getEvent({ programId: program.id })
|
|
|
|
|
await caller.createDish({
|
|
|
|
|
lunchEventId: event.id, name: 'Sea bass', dietaryTags: ['PESCATARIAN'], sortOrder: 1,
|
|
|
|
|
})
|
|
|
|
|
await caller.createDish({
|
|
|
|
|
lunchEventId: event.id, name: 'Risotto', dietaryTags: ['VEGETARIAN'], sortOrder: 0,
|
|
|
|
|
})
|
|
|
|
|
const dishes = await caller.listDishes({ lunchEventId: event.id })
|
2026-04-29 02:37:17 +02:00
|
|
|
expect(dishes.map((d: { name: string }) => d.name)).toEqual(['Risotto', 'Sea bass'])
|
2026-04-29 02:30:49 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('updateDish patches name + tags', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `dish-upd-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const event = await caller.getEvent({ programId: program.id })
|
|
|
|
|
const dish = await caller.createDish({ lunchEventId: event.id, name: 'A', dietaryTags: [] })
|
|
|
|
|
const updated = await caller.updateDish({
|
|
|
|
|
dishId: dish.id, name: 'B', dietaryTags: ['VEGAN'],
|
|
|
|
|
})
|
|
|
|
|
expect(updated.name).toBe('B')
|
|
|
|
|
expect(updated.dietaryTags).toEqual(['VEGAN'])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('deleteDish sets dishId=null on existing picks', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `dish-del-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const event = await caller.getEvent({ programId: program.id })
|
|
|
|
|
const dish = await caller.createDish({ lunchEventId: event.id, name: 'X', dietaryTags: [] })
|
|
|
|
|
const user = await createTestUser('APPLICANT')
|
|
|
|
|
userIds.push(user.id)
|
|
|
|
|
const project = await createTestProject(program.id, {
|
|
|
|
|
title: `dish-del-proj-${uid()}`,
|
|
|
|
|
competitionCategory: 'STARTUP',
|
|
|
|
|
})
|
|
|
|
|
const conf = await prisma.finalistConfirmation.create({
|
|
|
|
|
data: {
|
|
|
|
|
projectId: project.id, category: 'STARTUP', status: 'CONFIRMED',
|
|
|
|
|
deadline: new Date(Date.now() + 86_400_000), token: `tok-${uid()}`,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
const member = await prisma.attendingMember.create({
|
|
|
|
|
data: { confirmationId: conf.id, userId: user.id },
|
|
|
|
|
})
|
|
|
|
|
await prisma.memberLunchPick.create({
|
|
|
|
|
data: { attendingMemberId: member.id, dishId: dish.id, pickedAt: new Date() },
|
|
|
|
|
})
|
|
|
|
|
await caller.deleteDish({ dishId: dish.id })
|
|
|
|
|
const pick = await prisma.memberLunchPick.findUnique({
|
|
|
|
|
where: { attendingMemberId: member.id },
|
|
|
|
|
})
|
|
|
|
|
expect(pick?.dishId).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('reorderDishes commits new sortOrder values', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `dish-reorder-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const event = await caller.getEvent({ programId: program.id })
|
|
|
|
|
const a = await caller.createDish({
|
|
|
|
|
lunchEventId: event.id, name: 'a', dietaryTags: [], sortOrder: 0,
|
|
|
|
|
})
|
|
|
|
|
const b = await caller.createDish({
|
|
|
|
|
lunchEventId: event.id, name: 'b', dietaryTags: [], sortOrder: 1,
|
|
|
|
|
})
|
|
|
|
|
await caller.reorderDishes({
|
|
|
|
|
ordered: [
|
|
|
|
|
{ dishId: b.id, sortOrder: 0 },
|
|
|
|
|
{ dishId: a.id, sortOrder: 1 },
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
const dishes = await caller.listDishes({ lunchEventId: event.id })
|
2026-04-29 02:37:17 +02:00
|
|
|
expect(dishes.map((d: { name: string }) => d.name)).toEqual(['b', 'a'])
|
2026-04-29 02:30:49 +02:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-29 02:31:28 +02:00
|
|
|
describe('external attendees CRUD', () => {
|
|
|
|
|
it('listExternals returns standalone + project-attached entries', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `ext-list-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const event = await caller.getEvent({ programId: program.id })
|
|
|
|
|
const project = await createTestProject(program.id, {
|
|
|
|
|
title: `ext-${uid()}`,
|
|
|
|
|
competitionCategory: 'STARTUP',
|
|
|
|
|
})
|
|
|
|
|
await caller.createExternal({
|
|
|
|
|
lunchEventId: event.id, name: 'Princess Albert',
|
|
|
|
|
roleNote: 'Foundation rep',
|
|
|
|
|
})
|
|
|
|
|
await caller.createExternal({
|
|
|
|
|
lunchEventId: event.id, name: 'Speaker Smith',
|
|
|
|
|
projectId: project.id, email: 's@example.com',
|
|
|
|
|
})
|
|
|
|
|
const list = await caller.listExternals({ lunchEventId: event.id })
|
|
|
|
|
expect(list).toHaveLength(2)
|
2026-04-29 02:37:17 +02:00
|
|
|
expect(list.find((e: { name: string }) => e.name === 'Princess Albert')?.projectId).toBeNull()
|
|
|
|
|
expect(list.find((e: { name: string }) => e.name === 'Speaker Smith')?.projectId).toBe(project.id)
|
2026-04-29 02:31:28 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('updateExternal patches fields including dishId + allergens', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `ext-upd-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const event = await caller.getEvent({ programId: program.id })
|
|
|
|
|
const dish = await caller.createDish({
|
|
|
|
|
lunchEventId: event.id, name: 'Steak', dietaryTags: [],
|
|
|
|
|
})
|
|
|
|
|
const ext = await caller.createExternal({ lunchEventId: event.id, name: 'X' })
|
|
|
|
|
const updated = await caller.updateExternal({
|
|
|
|
|
externalId: ext.id, dishId: dish.id, allergens: ['GLUTEN', 'TREE_NUTS'],
|
|
|
|
|
allergenOther: 'sulphites in red wine',
|
|
|
|
|
})
|
|
|
|
|
expect(updated.dishId).toBe(dish.id)
|
|
|
|
|
expect(updated.allergens).toEqual(['GLUTEN', 'TREE_NUTS'])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('deleteExternal removes the row', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `ext-del-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const event = await caller.getEvent({ programId: program.id })
|
|
|
|
|
const ext = await caller.createExternal({ lunchEventId: event.id, name: 'tmp' })
|
|
|
|
|
await caller.deleteExternal({ externalId: ext.id })
|
|
|
|
|
const list = await caller.listExternals({ lunchEventId: event.id })
|
2026-04-29 02:37:17 +02:00
|
|
|
expect(list.find((e: { id: string }) => e.id === ext.id)).toBeUndefined()
|
2026-04-29 02:31:28 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('rejects non-admin createExternal', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `ext-rej-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const event = await caller.getEvent({ programId: program.id })
|
|
|
|
|
const member = await createTestUser('APPLICANT')
|
|
|
|
|
userIds.push(member.id)
|
|
|
|
|
const memberCaller = createCaller(lunchRouter, {
|
|
|
|
|
id: member.id,
|
|
|
|
|
email: member.email,
|
|
|
|
|
role: 'APPLICANT',
|
|
|
|
|
})
|
|
|
|
|
await expect(
|
|
|
|
|
memberCaller.createExternal({ lunchEventId: event.id, name: 'nope' }),
|
|
|
|
|
).rejects.toThrow()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-29 02:33:24 +02:00
|
|
|
describe('lunch.getEventForMember', () => {
|
|
|
|
|
it('returns event details when enabled', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `efm-en-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const member = await createTestUser('APPLICANT')
|
|
|
|
|
userIds.push(member.id)
|
|
|
|
|
await prisma.lunchEvent.create({
|
|
|
|
|
data: {
|
|
|
|
|
programId: program.id,
|
|
|
|
|
enabled: true,
|
|
|
|
|
eventAt: new Date('2026-06-28T12:30:00Z'),
|
|
|
|
|
venue: 'Hôtel',
|
|
|
|
|
changeCutoffHours: 48,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
const caller = createCaller(lunchRouter, {
|
|
|
|
|
id: member.id,
|
|
|
|
|
email: member.email,
|
|
|
|
|
role: 'APPLICANT',
|
|
|
|
|
})
|
|
|
|
|
const result = await caller.getEventForMember({ programId: program.id })
|
|
|
|
|
expect(result?.venue).toBe('Hôtel')
|
|
|
|
|
expect(result?.changeDeadline).toBeInstanceOf(Date)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('returns null when event is disabled', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `efm-dis-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const member = await createTestUser('APPLICANT')
|
|
|
|
|
userIds.push(member.id)
|
|
|
|
|
await prisma.lunchEvent.create({
|
|
|
|
|
data: { programId: program.id, enabled: false },
|
|
|
|
|
})
|
|
|
|
|
const caller = createCaller(lunchRouter, {
|
|
|
|
|
id: member.id,
|
|
|
|
|
email: member.email,
|
|
|
|
|
role: 'APPLICANT',
|
|
|
|
|
})
|
|
|
|
|
const result = await caller.getEventForMember({ programId: program.id })
|
|
|
|
|
expect(result).toBeNull()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('lunch.getTeamPicks', () => {
|
|
|
|
|
it('returns picks for all attending members of the caller team', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `tp-ok-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const lead = await createTestUser('APPLICANT')
|
|
|
|
|
const m1 = await createTestUser('APPLICANT')
|
|
|
|
|
const m2 = await createTestUser('APPLICANT')
|
|
|
|
|
userIds.push(lead.id, m1.id, m2.id)
|
|
|
|
|
const project = await createTestProject(program.id, {
|
|
|
|
|
title: `tp-${uid()}`,
|
|
|
|
|
competitionCategory: 'STARTUP',
|
|
|
|
|
})
|
|
|
|
|
await prisma.teamMember.createMany({
|
|
|
|
|
data: [
|
|
|
|
|
{ projectId: project.id, userId: lead.id, role: 'LEAD' },
|
|
|
|
|
{ projectId: project.id, userId: m1.id, role: 'MEMBER' },
|
|
|
|
|
{ projectId: project.id, userId: m2.id, role: 'MEMBER' },
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
const conf = await prisma.finalistConfirmation.create({
|
|
|
|
|
data: {
|
|
|
|
|
projectId: project.id, category: 'STARTUP', status: 'CONFIRMED',
|
|
|
|
|
deadline: new Date(Date.now() + 86_400_000), token: `tok-${uid()}`,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
const am1 = await prisma.attendingMember.create({
|
|
|
|
|
data: { confirmationId: conf.id, userId: m1.id },
|
|
|
|
|
})
|
|
|
|
|
const am2 = await prisma.attendingMember.create({
|
|
|
|
|
data: { confirmationId: conf.id, userId: m2.id },
|
|
|
|
|
})
|
|
|
|
|
const event = await prisma.lunchEvent.create({
|
|
|
|
|
data: { programId: program.id, enabled: true },
|
|
|
|
|
})
|
|
|
|
|
const dish = await prisma.dish.create({
|
|
|
|
|
data: { lunchEventId: event.id, name: 'X', dietaryTags: [] },
|
|
|
|
|
})
|
|
|
|
|
await prisma.memberLunchPick.create({
|
|
|
|
|
data: { attendingMemberId: am1.id, dishId: dish.id, pickedAt: new Date() },
|
|
|
|
|
})
|
|
|
|
|
await prisma.memberLunchPick.create({ data: { attendingMemberId: am2.id } })
|
|
|
|
|
|
|
|
|
|
const caller = createCaller(lunchRouter, {
|
|
|
|
|
id: lead.id, email: lead.email, role: 'APPLICANT',
|
|
|
|
|
})
|
|
|
|
|
const picks = await caller.getTeamPicks({ projectId: project.id })
|
|
|
|
|
expect(picks).toHaveLength(2)
|
2026-04-29 02:37:17 +02:00
|
|
|
expect(picks.find((p: { userId: string }) => p.userId === m1.id)?.hasPicked).toBe(true)
|
|
|
|
|
expect(picks.find((p: { userId: string }) => p.userId === m2.id)?.hasPicked).toBe(false)
|
2026-04-29 02:33:24 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('rejects non-team-member callers', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `tp-rej-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const stranger = await createTestUser('APPLICANT')
|
|
|
|
|
userIds.push(stranger.id)
|
|
|
|
|
const project = await createTestProject(program.id, {
|
|
|
|
|
title: `tp-rej-${uid()}`,
|
|
|
|
|
competitionCategory: 'STARTUP',
|
|
|
|
|
})
|
|
|
|
|
const caller = createCaller(lunchRouter, {
|
|
|
|
|
id: stranger.id, email: stranger.email, role: 'APPLICANT',
|
|
|
|
|
})
|
|
|
|
|
await expect(
|
|
|
|
|
caller.getTeamPicks({ projectId: project.id }),
|
|
|
|
|
).rejects.toThrow()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-29 02:34:24 +02:00
|
|
|
describe('lunch.getManifest', () => {
|
|
|
|
|
it('returns confirmed attending members + externals with merged shape', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `mfst-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const m = await createTestUser('APPLICANT')
|
|
|
|
|
userIds.push(m.id)
|
|
|
|
|
const project = await createTestProject(program.id, {
|
|
|
|
|
title: `mfst-${uid()}`, competitionCategory: 'STARTUP',
|
|
|
|
|
})
|
|
|
|
|
const conf = await prisma.finalistConfirmation.create({
|
|
|
|
|
data: {
|
|
|
|
|
projectId: project.id, category: 'STARTUP', status: 'CONFIRMED',
|
|
|
|
|
deadline: new Date(Date.now() + 86_400_000), token: `tok-${uid()}`,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
const am = await prisma.attendingMember.create({
|
|
|
|
|
data: { confirmationId: conf.id, userId: m.id },
|
|
|
|
|
})
|
|
|
|
|
const event = await prisma.lunchEvent.create({
|
|
|
|
|
data: { programId: program.id, enabled: true },
|
|
|
|
|
})
|
|
|
|
|
const dish = await prisma.dish.create({
|
|
|
|
|
data: { lunchEventId: event.id, name: 'Risotto', dietaryTags: ['VEGETARIAN'] },
|
|
|
|
|
})
|
|
|
|
|
await prisma.memberLunchPick.create({
|
|
|
|
|
data: { attendingMemberId: am.id, dishId: dish.id, pickedAt: new Date() },
|
|
|
|
|
})
|
|
|
|
|
await prisma.externalAttendee.create({
|
|
|
|
|
data: { lunchEventId: event.id, name: 'External Bob', dishId: dish.id },
|
|
|
|
|
})
|
|
|
|
|
const manifest = await caller.getManifest({ programId: program.id })
|
|
|
|
|
expect(manifest.members).toHaveLength(1)
|
|
|
|
|
expect(manifest.externals).toHaveLength(1)
|
|
|
|
|
expect(manifest.summary.picked).toBe(2)
|
|
|
|
|
expect(manifest.summary.missing).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('excludes non-CONFIRMED confirmations', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `mfst-x-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const u = await createTestUser('APPLICANT')
|
|
|
|
|
userIds.push(u.id)
|
|
|
|
|
const project = await createTestProject(program.id, {
|
|
|
|
|
title: `mfst-x-${uid()}`, competitionCategory: 'STARTUP',
|
|
|
|
|
})
|
|
|
|
|
const conf = await prisma.finalistConfirmation.create({
|
|
|
|
|
data: {
|
|
|
|
|
projectId: project.id, category: 'STARTUP', status: 'PENDING',
|
|
|
|
|
deadline: new Date(Date.now() + 86_400_000), token: `tok-${uid()}`,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
await prisma.attendingMember.create({
|
|
|
|
|
data: { confirmationId: conf.id, userId: u.id },
|
|
|
|
|
})
|
|
|
|
|
await prisma.lunchEvent.create({ data: { programId: program.id, enabled: true } })
|
|
|
|
|
const manifest = await caller.getManifest({ programId: program.id })
|
|
|
|
|
expect(manifest.members).toHaveLength(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('lunch.exportManifestCsv', () => {
|
|
|
|
|
it('returns a CSV string with header + one row per attendee', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `csv-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const event = await prisma.lunchEvent.create({
|
|
|
|
|
data: { programId: program.id, enabled: true },
|
|
|
|
|
})
|
|
|
|
|
const dish = await prisma.dish.create({
|
|
|
|
|
data: { lunchEventId: event.id, name: 'Risotto', dietaryTags: [] },
|
|
|
|
|
})
|
|
|
|
|
await prisma.externalAttendee.create({
|
|
|
|
|
data: {
|
|
|
|
|
lunchEventId: event.id, name: 'X Y', dishId: dish.id, allergens: ['GLUTEN'],
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
const csv = await caller.exportManifestCsv({ programId: program.id })
|
|
|
|
|
expect(csv.split('\n')[0]).toBe('Type,Team,Name,Email,Dish,Allergens,Allergen notes')
|
|
|
|
|
expect(csv).toContain('External,,X Y,,Risotto,GLUTEN,')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-29 02:30:06 +02:00
|
|
|
describe('lunch.updateEvent', () => {
|
|
|
|
|
it('patches an arbitrary subset of fields', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `lunch-upd-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
await caller.getEvent({ programId: program.id })
|
|
|
|
|
const updated = await caller.updateEvent({
|
|
|
|
|
programId: program.id,
|
|
|
|
|
enabled: true,
|
|
|
|
|
eventAt: new Date('2026-06-28T12:30:00Z'),
|
|
|
|
|
venue: 'Hôtel Hermitage',
|
|
|
|
|
changeCutoffHours: 24,
|
|
|
|
|
extraRecipients: ['caterer@example.com'],
|
|
|
|
|
})
|
|
|
|
|
expect(updated.enabled).toBe(true)
|
|
|
|
|
expect(updated.venue).toBe('Hôtel Hermitage')
|
|
|
|
|
expect(updated.changeCutoffHours).toBe(24)
|
|
|
|
|
expect(updated.extraRecipients).toEqual(['caterer@example.com'])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('lazy-creates the event on first updateEvent', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `lunch-lazy-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const { caller } = await newAdminCaller()
|
|
|
|
|
const updated = await caller.updateEvent({
|
|
|
|
|
programId: program.id,
|
|
|
|
|
enabled: true,
|
|
|
|
|
})
|
|
|
|
|
expect(updated.enabled).toBe(true)
|
|
|
|
|
expect(updated.programId).toBe(program.id)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('rejects non-admin callers', async () => {
|
|
|
|
|
const program = await createTestProgram({ name: `lunch-rej-${uid()}` })
|
|
|
|
|
programIds.push(program.id)
|
|
|
|
|
const member = await createTestUser('APPLICANT')
|
|
|
|
|
userIds.push(member.id)
|
|
|
|
|
const memberCaller = createCaller(lunchRouter, {
|
|
|
|
|
id: member.id,
|
|
|
|
|
email: member.email,
|
|
|
|
|
role: 'APPLICANT',
|
|
|
|
|
})
|
|
|
|
|
await expect(
|
|
|
|
|
memberCaller.updateEvent({ programId: program.id, enabled: true }),
|
|
|
|
|
).rejects.toThrow()
|
|
|
|
|
})
|
|
|
|
|
})
|