import { describe, it, expect, vi, beforeEach } from 'vitest' import handler from '../../../../server/api/events/[id]/register.post.js' import { createMockEvent } from '../../helpers/createMockEvent.js' const { mockLoadPublicEvent, mockMemberFindOne, mockSeriesFindById, mockFindByIdAndUpdate, mockCheckUserSeriesPass } = vi.hoisted(() => ({ mockLoadPublicEvent: vi.fn(), mockMemberFindOne: vi.fn(), mockSeriesFindById: vi.fn(), mockFindByIdAndUpdate: vi.fn(), mockCheckUserSeriesPass: vi.fn() })) vi.mock('../../../../server/models/event.js', () => ({ default: { findByIdAndUpdate: mockFindByIdAndUpdate } })) vi.mock('../../../../server/models/member.js', () => ({ default: { findOne: mockMemberFindOne } })) vi.mock('../../../../server/models/series.js', () => ({ default: { findById: mockSeriesFindById } })) vi.mock('../../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() })) vi.mock('../../../../server/utils/loadEvent.js', () => ({ loadPublicEvent: mockLoadPublicEvent })) vi.mock('../../../../server/utils/resend.js', () => ({ sendEventRegistrationEmail: vi.fn().mockResolvedValue(null) })) vi.mock('../../../../server/utils/tickets.js', async () => { const actual = await vi.importActual('../../../../server/utils/tickets.js') return { ...actual, checkUserSeriesPass: mockCheckUserSeriesPass } }) const futureDate = () => new Date(Date.now() + 86400000) const defaultTickets = () => ({ enabled: true, currency: 'CAD', capacity: { total: 100 }, member: { available: true, price: 0, isFree: true, name: 'Member Ticket' }, public: { available: true, price: 20, quantity: 50, sold: 0, reserved: 0, name: 'Public Ticket' }, waitlist: { enabled: false } }) function baseEvent(overrides = {}) { const { tickets: ticketOverrides, ...rest } = overrides return { _id: 'event-1', slug: 'event-slug', title: 'Test Event', startDate: futureDate(), isCancelled: false, registrations: [], tickets: { ...defaultTickets(), ...ticketOverrides }, isVisible: true, ...rest } } function runHandler({ identifier = 'event-slug', body } = {}) { const req = createMockEvent({ method: 'POST', path: `/api/events/${identifier}/register`, body }) req.context = { params: { id: identifier } } return handler(req) } describe('POST /api/events/[id]/register — series-pass enforcement', () => { beforeEach(() => { vi.clearAllMocks() mockMemberFindOne.mockResolvedValue(null) mockFindByIdAndUpdate.mockImplementation((id, update) => { const reg = update.$push.registrations return Promise.resolve({ registrations: [{ ...reg, _id: 'reg-1' }] }) }) }) it('blocks registration when event requires series pass and user has no pass (drop-in disabled)', async () => { const seriesId = 'series-1' mockLoadPublicEvent.mockResolvedValue( baseEvent({ tickets: { enabled: true, requiresSeriesTicket: true, seriesTicketReference: seriesId } }) ) mockSeriesFindById.mockResolvedValue({ _id: seriesId, slug: 'series-slug', tickets: { allowIndividualEventTickets: false }, registrations: [] }) mockCheckUserSeriesPass.mockReturnValue({ hasPass: false, registration: null }) await expect( runHandler({ body: { email: 'guest@example.com', name: 'Guest' } }) ).rejects.toMatchObject({ statusCode: 403, data: { seriesSlug: 'series-slug' } }) expect(mockFindByIdAndUpdate).not.toHaveBeenCalled() }) it('allows registration when user has a valid series pass', async () => { const seriesId = 'series-1' mockLoadPublicEvent.mockResolvedValue( baseEvent({ tickets: { enabled: true, requiresSeriesTicket: true, seriesTicketReference: seriesId } }) ) mockSeriesFindById.mockResolvedValue({ _id: seriesId, slug: 'series-slug', tickets: { allowIndividualEventTickets: false }, registrations: [] }) mockCheckUserSeriesPass.mockReturnValue({ hasPass: true, registration: { email: 'buyer@example.com' } }) const result = await runHandler({ body: { email: 'buyer@example.com', name: 'Buyer' } }) expect(result.success).toBe(true) expect(mockFindByIdAndUpdate).toHaveBeenCalled() }) it('allows drop-in registration when series permits individual event tickets', async () => { const seriesId = 'series-2' mockLoadPublicEvent.mockResolvedValue( baseEvent({ tickets: { enabled: true, requiresSeriesTicket: true, seriesTicketReference: seriesId } }) ) mockSeriesFindById.mockResolvedValue({ _id: seriesId, slug: 'series-dropin', tickets: { allowIndividualEventTickets: true }, registrations: [] }) mockCheckUserSeriesPass.mockReturnValue({ hasPass: false, registration: null }) const result = await runHandler({ body: { email: 'dropin@example.com', name: 'DropIn' } }) expect(result.success).toBe(true) expect(mockFindByIdAndUpdate).toHaveBeenCalled() }) it('throws 500 when event references a series that cannot be found (data integrity)', async () => { mockLoadPublicEvent.mockResolvedValue( baseEvent({ tickets: { enabled: true, requiresSeriesTicket: true, seriesTicketReference: 'missing-series' } }) ) mockSeriesFindById.mockResolvedValue(null) await expect( runHandler({ body: { email: 'guest@example.com', name: 'Guest' } }) ).rejects.toMatchObject({ statusCode: 500 }) }) it('allows normal registration for events with no series linkage', async () => { mockLoadPublicEvent.mockResolvedValue(baseEvent()) const result = await runHandler({ body: { email: 'solo@example.com', name: 'Solo' } }) expect(result.success).toBe(true) expect(mockSeriesFindById).not.toHaveBeenCalled() }) })