import { describe, it, expect } from 'vitest' import { helcimCreatePlanSchema, helcimCustomerSchema, helcimInitializePaymentSchema, helcimSubscriptionSchema, helcimUpdateBillingSchema, ticketPurchaseSchema, ticketReserveSchema, ticketEligibilitySchema, waitlistSchema, waitlistDeleteSchema, cancelRegistrationSchema, checkRegistrationSchema, guestRegisterSchema, eventPaymentSchema, updateContributionSchema, peerSupportUpdateSchema, updatePatchSchema, seriesTicketPurchaseSchema, seriesTicketEligibilitySchema, adminSeriesCreateSchema, adminSeriesUpdateSchema, adminSeriesItemUpdateSchema, adminSeriesTicketsSchema, adminEventUpdateSchema, adminMemberCreateSchema } from '../../../server/utils/schemas.js' // --- Helcim schemas --- describe('helcimCreatePlanSchema', () => { it('accepts valid plan data', () => { const result = helcimCreatePlanSchema.safeParse({ name: 'Monthly Plan', amount: '15.00', frequency: 'monthly' }) expect(result.success).toBe(true) }) it('accepts numeric amount', () => { const result = helcimCreatePlanSchema.safeParse({ name: 'Plan', amount: 15, frequency: 'monthly' }) expect(result.success).toBe(true) }) it('rejects missing name', () => { const result = helcimCreatePlanSchema.safeParse({ amount: 15, frequency: 'monthly' }) expect(result.success).toBe(false) }) it('rejects missing amount', () => { const result = helcimCreatePlanSchema.safeParse({ name: 'Plan', frequency: 'monthly' }) expect(result.success).toBe(false) }) it('strips unknown fields', () => { const result = helcimCreatePlanSchema.safeParse({ name: 'Plan', amount: 15, frequency: 'monthly', malicious: 'data' }) expect(result.success).toBe(true) expect(result.data).not.toHaveProperty('malicious') }) }) describe('helcimCustomerSchema', () => { it('accepts valid customer data', () => { const result = helcimCustomerSchema.safeParse({ name: 'Jane Doe', email: 'jane@example.com' }) expect(result.success).toBe(true) }) it('lowercases email', () => { const result = helcimCustomerSchema.safeParse({ name: 'Jane', email: 'JANE@Example.COM' }) expect(result.success).toBe(true) expect(result.data.email).toBe('jane@example.com') }) it('rejects invalid email', () => { const result = helcimCustomerSchema.safeParse({ name: 'Jane', email: 'not-an-email' }) expect(result.success).toBe(false) }) it('strips role field', () => { const result = helcimCustomerSchema.safeParse({ name: 'Jane', email: 'jane@example.com', role: 'admin' }) expect(result.success).toBe(true) expect(result.data).not.toHaveProperty('role') }) }) describe('helcimSubscriptionSchema', () => { it('accepts valid subscription data', () => { const result = helcimSubscriptionSchema.safeParse({ customerId: '12345', contributionTier: '15', customerCode: 'CST123' }) expect(result.success).toBe(true) }) it('rejects invalid contribution tier', () => { const result = helcimSubscriptionSchema.safeParse({ customerId: '12345', contributionTier: '999', customerCode: 'CST123' }) expect(result.success).toBe(false) }) it('rejects missing customerCode', () => { const result = helcimSubscriptionSchema.safeParse({ customerId: '12345', contributionTier: '15' }) expect(result.success).toBe(false) }) }) describe('helcimUpdateBillingSchema', () => { const validBilling = { customerId: '123', billingAddress: { street: '123 Main St', city: 'Toronto', country: 'CA', postalCode: 'M5V 1A1' } } it('accepts valid billing data', () => { const result = helcimUpdateBillingSchema.safeParse(validBilling) expect(result.success).toBe(true) }) it('rejects missing street', () => { const result = helcimUpdateBillingSchema.safeParse({ customerId: '123', billingAddress: { city: 'Toronto', country: 'CA', postalCode: 'M5V' } }) expect(result.success).toBe(false) }) it('rejects missing billingAddress', () => { const result = helcimUpdateBillingSchema.safeParse({ customerId: '123' }) expect(result.success).toBe(false) }) }) // --- Event schemas --- describe('ticketPurchaseSchema', () => { it('accepts valid ticket purchase', () => { const result = ticketPurchaseSchema.safeParse({ name: 'Jane', email: 'jane@example.com' }) expect(result.success).toBe(true) }) it('lowercases email', () => { const result = ticketPurchaseSchema.safeParse({ name: 'Jane', email: 'JANE@Example.COM' }) expect(result.success).toBe(true) expect(result.data.email).toBe('jane@example.com') }) it('rejects missing name', () => { const result = ticketPurchaseSchema.safeParse({ email: 'jane@example.com' }) expect(result.success).toBe(false) }) it('strips unknown fields', () => { const result = ticketPurchaseSchema.safeParse({ name: 'Jane', email: 'jane@example.com', role: 'admin', status: 'active' }) expect(result.success).toBe(true) expect(result.data).not.toHaveProperty('role') expect(result.data).not.toHaveProperty('status') }) }) describe('ticketReserveSchema', () => { it('accepts valid email', () => { const result = ticketReserveSchema.safeParse({ email: 'test@example.com' }) expect(result.success).toBe(true) }) it('rejects missing email', () => { const result = ticketReserveSchema.safeParse({}) expect(result.success).toBe(false) }) }) describe('waitlistSchema', () => { it('accepts valid waitlist entry', () => { const result = waitlistSchema.safeParse({ email: 'test@example.com', name: 'Test User' }) expect(result.success).toBe(true) }) it('accepts email-only entry', () => { const result = waitlistSchema.safeParse({ email: 'test@example.com' }) expect(result.success).toBe(true) }) it('rejects missing email', () => { const result = waitlistSchema.safeParse({ name: 'Test' }) expect(result.success).toBe(false) }) }) describe('guestRegisterSchema', () => { it('accepts valid guest data', () => { const result = guestRegisterSchema.safeParse({ name: 'Guest User', email: 'guest@example.com' }) expect(result.success).toBe(true) }) it('rejects missing name', () => { const result = guestRegisterSchema.safeParse({ email: 'guest@example.com' }) expect(result.success).toBe(false) }) }) describe('eventPaymentSchema', () => { it('accepts valid payment data', () => { const result = eventPaymentSchema.safeParse({ name: 'Payer', email: 'payer@example.com', paymentToken: 'tok_abc123' }) expect(result.success).toBe(true) }) it('rejects missing paymentToken', () => { const result = eventPaymentSchema.safeParse({ name: 'Payer', email: 'payer@example.com' }) expect(result.success).toBe(false) }) }) // --- Member schemas --- describe('updateContributionSchema', () => { it('accepts valid contribution tier', () => { const result = updateContributionSchema.safeParse({ contributionTier: '15' }) expect(result.success).toBe(true) }) it('rejects invalid tier', () => { const result = updateContributionSchema.safeParse({ contributionTier: '100' }) expect(result.success).toBe(false) }) it('strips unknown fields', () => { const result = updateContributionSchema.safeParse({ contributionTier: '15', role: 'admin' }) expect(result.success).toBe(true) expect(result.data).not.toHaveProperty('role') }) }) describe('peerSupportUpdateSchema', () => { it('accepts valid peer support data', () => { const result = peerSupportUpdateSchema.safeParse({ enabled: true, skillTopics: ['game design', 'business'], slackUsername: 'jane' }) expect(result.success).toBe(true) }) it('accepts empty object', () => { const result = peerSupportUpdateSchema.safeParse({}) expect(result.success).toBe(true) }) it('rejects non-array skillTopics', () => { const result = peerSupportUpdateSchema.safeParse({ skillTopics: 'not-an-array' }) expect(result.success).toBe(false) }) }) // --- Update schemas --- describe('updatePatchSchema', () => { it('accepts valid update patch', () => { const result = updatePatchSchema.safeParse({ content: 'Updated content', privacy: 'members' }) expect(result.success).toBe(true) }) it('accepts empty object (all optional)', () => { const result = updatePatchSchema.safeParse({}) expect(result.success).toBe(true) }) it('rejects invalid privacy enum', () => { const result = updatePatchSchema.safeParse({ privacy: 'invalid' }) expect(result.success).toBe(false) }) }) // --- Series schemas --- describe('seriesTicketPurchaseSchema', () => { it('accepts valid series ticket purchase', () => { const result = seriesTicketPurchaseSchema.safeParse({ name: 'Buyer', email: 'buyer@example.com' }) expect(result.success).toBe(true) }) it('rejects missing name', () => { const result = seriesTicketPurchaseSchema.safeParse({ email: 'buyer@example.com' }) expect(result.success).toBe(false) }) }) // --- Admin schemas --- describe('adminSeriesCreateSchema', () => { it('accepts valid series', () => { const result = adminSeriesCreateSchema.safeParse({ id: 'test-series', title: 'Test Series', description: 'A test series' }) expect(result.success).toBe(true) }) it('rejects missing id', () => { const result = adminSeriesCreateSchema.safeParse({ title: 'Test', description: 'Desc' }) expect(result.success).toBe(false) }) }) describe('adminMemberCreateSchema', () => { it('accepts valid admin member create', () => { const result = adminMemberCreateSchema.safeParse({ name: 'Admin Created', email: 'admin-created@example.com', circle: 'founder', contributionTier: '30' }) expect(result.success).toBe(true) }) it('strips role field (mass assignment)', () => { const result = adminMemberCreateSchema.safeParse({ name: 'Admin Created', email: 'admin-created@example.com', circle: 'founder', contributionTier: '30', role: 'admin' }) expect(result.success).toBe(true) expect(result.data).not.toHaveProperty('role') }) it('strips status field (mass assignment)', () => { const result = adminMemberCreateSchema.safeParse({ name: 'Admin Created', email: 'admin-created@example.com', circle: 'founder', contributionTier: '30', status: 'active' }) expect(result.success).toBe(true) expect(result.data).not.toHaveProperty('status') }) it('rejects invalid circle', () => { const result = adminMemberCreateSchema.safeParse({ name: 'Admin Created', email: 'admin-created@example.com', circle: 'superadmin', contributionTier: '30' }) expect(result.success).toBe(false) }) }) describe('adminEventUpdateSchema', () => { const validUpdate = { title: 'Updated Event', description: 'Updated description', startDate: '2026-04-01T10:00:00Z', endDate: '2026-04-01T12:00:00Z' } it('accepts valid event update', () => { const result = adminEventUpdateSchema.safeParse(validUpdate) expect(result.success).toBe(true) }) it('rejects missing title', () => { const { title, ...rest } = validUpdate const result = adminEventUpdateSchema.safeParse(rest) expect(result.success).toBe(false) }) it('accepts optional tickets', () => { const result = adminEventUpdateSchema.safeParse({ ...validUpdate, tickets: { enabled: true, public: { available: true, price: 25.00 } } }) expect(result.success).toBe(true) }) }) // --- Error text forwarding regression tests --- describe('error text forwarding regression', () => { const helcimFiles = [ 'create-plan.post.js', 'customer.post.js', 'customer-code.get.js', 'initialize-payment.post.js', 'subscription.post.js', 'update-billing.post.js', 'get-or-create-customer.post.js' ] for (const file of helcimFiles) { it(`${file} does not forward error text to client`, () => { const source = readFileSync( resolve(import.meta.dirname, `../../../server/api/helcim/${file}`), 'utf-8' ) // Should not contain template literals that interpolate errorText or error.message into statusMessage expect(source).not.toMatch(/statusMessage:.*\$\{errorText\}/) expect(source).not.toMatch(/statusMessage:\s*error\.message\b/) }) } it('members/update-contribution.post.js does not forward error text', () => { const source = readFileSync( resolve(import.meta.dirname, '../../../server/api/members/update-contribution.post.js'), 'utf-8' ) expect(source).not.toMatch(/statusMessage:.*\$\{errorText\}/) expect(source).not.toMatch(/statusMessage:\s*error\.message\b/) }) }) import { readFileSync } from 'node:fs' import { resolve } from 'node:path' // --- validateBody migration coverage --- describe('validateBody migration coverage', () => { const endpoints = [ 'helcim/create-plan.post.js', 'helcim/customer.post.js', 'helcim/initialize-payment.post.js', 'helcim/subscription.post.js', 'helcim/update-billing.post.js', 'events/[id]/tickets/purchase.post.js', 'events/[id]/tickets/reserve.post.js', 'events/[id]/tickets/check-eligibility.post.js', 'events/[id]/waitlist.post.js', 'events/[id]/waitlist.delete.js', 'events/[id]/cancel-registration.post.js', 'events/[id]/check-registration.post.js', 'events/[id]/guest-register.post.js', 'events/[id]/payment.post.js', 'members/update-contribution.post.js', 'members/me/peer-support.patch.js', 'updates/[id].patch.js', 'series/[id]/tickets/purchase.post.js', 'series/[id]/tickets/check-eligibility.post.js', 'admin/series.post.js', 'admin/series.put.js', 'admin/series/tickets.put.js', 'admin/series/[id].put.js', 'admin/events/[id].put.js', 'admin/members.post.js' ] for (const endpoint of endpoints) { it(`${endpoint} uses validateBody instead of raw readBody`, () => { const source = readFileSync( resolve(import.meta.dirname, `../../../server/api/${endpoint}`), 'utf-8' ) expect(source).toContain('validateBody(event') // Should not have bare readBody calls (except inside validateBody itself) const lines = source.split('\n') for (const line of lines) { if (line.includes('readBody(event)') && !line.includes('validateBody')) { expect.fail(`${endpoint} still has raw readBody: ${line.trim()}`) } } }) } })