New GET /events/{token}/attendees endpoint returns attendee names when
a valid organizer token is provided (403 otherwise). The frontend
conditionally renders the list below the attendee count for organizers,
silently degrading for visitors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
import { http, HttpResponse } from 'msw'
|
|
import { test, expect } from './msw-setup'
|
|
|
|
const eventToken = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
|
const organizerToken = 'f9e8d7c6-b5a4-3210-fedc-ba9876543210'
|
|
|
|
const fullEvent = {
|
|
eventToken,
|
|
title: 'Summer BBQ',
|
|
description: 'Bring your own drinks!',
|
|
dateTime: '2026-03-15T20:00:00+01:00',
|
|
timezone: 'Europe/Berlin',
|
|
location: 'Central Park, NYC',
|
|
attendeeCount: 3,
|
|
expired: false,
|
|
}
|
|
|
|
const attendeesResponse = {
|
|
attendees: [
|
|
{ name: 'Alice' },
|
|
{ name: 'Bob' },
|
|
{ name: 'Charlie' },
|
|
],
|
|
}
|
|
|
|
test.describe('US-1: View attendee list as organizer', () => {
|
|
test('organizer sees attendee names', async ({ page, network }) => {
|
|
network.use(
|
|
http.get('*/api/events/:token', () => {
|
|
return HttpResponse.json(fullEvent)
|
|
}),
|
|
http.get('*/api/events/:token/attendees', () => {
|
|
return HttpResponse.json(attendeesResponse)
|
|
}),
|
|
)
|
|
|
|
// Set organizer token in localStorage before navigating
|
|
await page.goto('/')
|
|
await page.evaluate(
|
|
([et, ot]) => {
|
|
localStorage.setItem(
|
|
'fete:events',
|
|
JSON.stringify([{ eventToken: et, organizerToken: ot, title: 'Summer BBQ', dateTime: '2026-03-15T20:00:00+01:00', expiryDate: '' }]),
|
|
)
|
|
},
|
|
[eventToken, organizerToken],
|
|
)
|
|
|
|
await page.goto(`/events/${eventToken}`)
|
|
|
|
await expect(page.getByRole('heading', { name: 'Summer BBQ' })).toBeVisible()
|
|
await expect(page.getByText('3 Attendees')).toBeVisible()
|
|
await expect(page.getByText('Alice')).toBeVisible()
|
|
await expect(page.getByText('Bob')).toBeVisible()
|
|
await expect(page.getByText('Charlie')).toBeVisible()
|
|
})
|
|
|
|
test('visitor does not see attendee list', async ({ page, network }) => {
|
|
network.use(
|
|
http.get('*/api/events/:token', () => {
|
|
return HttpResponse.json(fullEvent)
|
|
}),
|
|
)
|
|
|
|
await page.goto(`/events/${eventToken}`)
|
|
|
|
await expect(page.getByRole('heading', { name: 'Summer BBQ' })).toBeVisible()
|
|
await expect(page.getByText('3 going')).toBeVisible()
|
|
await expect(page.locator('.attendee-list')).not.toBeVisible()
|
|
})
|
|
|
|
test('organizer sees empty state when no attendees', async ({ page, network }) => {
|
|
network.use(
|
|
http.get('*/api/events/:token', () => {
|
|
return HttpResponse.json({ ...fullEvent, attendeeCount: 0 })
|
|
}),
|
|
http.get('*/api/events/:token/attendees', () => {
|
|
return HttpResponse.json({ attendees: [] })
|
|
}),
|
|
)
|
|
|
|
await page.goto('/')
|
|
await page.evaluate(
|
|
([et, ot]) => {
|
|
localStorage.setItem(
|
|
'fete:events',
|
|
JSON.stringify([{ eventToken: et, organizerToken: ot, title: 'Summer BBQ', dateTime: '2026-03-15T20:00:00+01:00', expiryDate: '' }]),
|
|
)
|
|
},
|
|
[eventToken, organizerToken],
|
|
)
|
|
|
|
await page.goto(`/events/${eventToken}`)
|
|
|
|
await expect(page.getByRole('heading', { name: 'Summer BBQ' })).toBeVisible()
|
|
await expect(page.getByText('0 Attendees')).toBeVisible()
|
|
await expect(page.getByText('No attendees yet.')).toBeVisible()
|
|
})
|
|
})
|