import { describe, it, expect } from 'vitest' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' const eventsDir = resolve(import.meta.dirname, '../../../server/api/events/[id]') describe('register.post.js', () => { const source = readFileSync(resolve(eventsDir, 'register.post.js'), 'utf-8') it('uses validateBody for input validation', () => { expect(source).toContain('validateBody(event') }) it('checks for duplicate registration with case-insensitive email', () => { expect(source).toContain('email.toLowerCase()') }) it('uses validateTicketPurchase for gating (covers membersOnly, deadline, sold-out, etc.)', () => { expect(source).toContain('validateTicketPurchase(') }) it('enforces series-pass gate via checkUserSeriesPass', () => { expect(source).toContain('checkUserSeriesPass(') expect(source).toContain('requiresSeriesTicket') expect(source).toContain('seriesTicketReference') }) it('does not let email failure block registration', () => { // The await call (not the import) must be wrapped in try/catch const emailCallIndex = source.indexOf('await sendEventRegistrationEmail') expect(emailCallIndex).toBeGreaterThan(-1) // A try block immediately precedes the email call const precedingSource = source.slice(0, emailCallIndex) const lastTryIndex = precedingSource.lastIndexOf('try') expect(lastTryIndex).toBeGreaterThan(-1) // The catch after the email call should log but not re-throw const afterEmail = source.slice(emailCallIndex) const catchBlock = afterEmail.match(/catch\s*\(\w+\)\s*\{[^}]*\}/s) expect(catchBlock).not.toBeNull() expect(catchBlock[0]).toContain('console.error') }) }) describe('cancel-registration.post.js', () => { const source = readFileSync(resolve(eventsDir, 'cancel-registration.post.js'), 'utf-8') it('uses validateBody for input validation', () => { expect(source).toContain('validateBody(event') }) it('finds registration by email', () => { expect(source).toContain('email.toLowerCase()') }) it('notifies waitlist after cancellation', () => { expect(source).toContain('waitlist') expect(source).toContain('sendWaitlistNotificationEmail') }) it('does not require auth', () => { expect(source).not.toContain('requireAuth') }) }) describe('tickets/purchase.post.js', () => { const source = readFileSync(resolve(eventsDir, 'tickets/purchase.post.js'), 'utf-8') it('uses validateBody for input validation', () => { expect(source).toContain('validateBody(event, ticketPurchaseSchema)') }) it('upserts a guest Member when consent given and no existing Member', () => { expect(source).toContain('body.createAccount') expect(source).toContain('findOneAndUpdate') expect(source).toContain('$setOnInsert') expect(source).toContain('status: "guest"') expect(source).toContain('upsert: true') }) it('treats inactive Members (guest, suspended, cancelled) as non-members for pricing and validation', () => { // Only members with access (active or pending_payment) get member pricing. // hasMemberAccess is the shared gate in server/utils/tickets.js. expect(source).toContain('hasMemberAccess(member)') }) it('sets an auth cookie for new guests and returning guests', () => { expect(source).toContain('setAuthCookie(event, member)') expect(source).toContain('accountCreated || member.status === "guest"') }) it('does not auto-login existing non-guest members (hijack prevention)', () => { // Requires sign-in flag for existing real members — plan §2 expect(source).toContain('requiresSignIn = true') }) it('passes requiresSignIn to confirmation email', () => { expect(source).toContain('sendEventRegistrationEmail(registration, eventData, { requiresSignIn })') }) it('includes accountCreated and signedIn in response', () => { expect(source).toContain('accountCreated,') expect(source).toContain('signedIn,') }) it('does not block registration when email fails', () => { const emailCallIndex = source.indexOf('await sendEventRegistrationEmail') expect(emailCallIndex).toBeGreaterThan(-1) const afterEmail = source.slice(emailCallIndex) const catchBlock = afterEmail.match(/catch\s*\(\w+\)\s*\{[^}]*\}/s) expect(catchBlock).not.toBeNull() expect(catchBlock[0]).toContain('console.error') }) })