Add Zod validation to all API endpoints and remove debug test route
Adds schema-based input validation across helcim, events, members, series, admin, and updates API endpoints. Removes the peer-support debug test endpoint. Adds validation test coverage.
This commit is contained in:
parent
e4813075b7
commit
025c1a180f
38 changed files with 1132 additions and 309 deletions
52
tests/server/api/helcim-auth.test.js
Normal file
52
tests/server/api/helcim-auth.test.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
// Test that the three Helcim admin endpoints require admin auth.
|
||||
// We verify the handler files import/call requireAdmin by checking
|
||||
// the module source, and we test that requireAdmin rejects properly
|
||||
// via the existing auth.test.js infrastructure.
|
||||
|
||||
// We test the schema + handler wiring by reading the file and
|
||||
// confirming requireAdmin is the first call in the handler.
|
||||
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const serverDir = resolve(import.meta.dirname, '../../../server/api/helcim')
|
||||
|
||||
describe('Helcim admin endpoint auth guards', () => {
|
||||
const files = [
|
||||
'create-plan.post.js',
|
||||
'plans.get.js',
|
||||
'subscriptions.get.js'
|
||||
]
|
||||
|
||||
for (const file of files) {
|
||||
describe(file, () => {
|
||||
const source = readFileSync(resolve(serverDir, file), 'utf-8')
|
||||
|
||||
it('calls requireAdmin', () => {
|
||||
expect(source).toContain('requireAdmin(event)')
|
||||
})
|
||||
|
||||
it('calls requireAdmin before any business logic', () => {
|
||||
const adminIndex = source.indexOf('requireAdmin(event)')
|
||||
const readBodyIndex = source.indexOf('readBody(event)')
|
||||
const validateBodyIndex = source.indexOf('validateBody(event')
|
||||
const fetchIndex = source.indexOf('fetch(')
|
||||
|
||||
expect(adminIndex).toBeGreaterThan(-1)
|
||||
|
||||
// requireAdmin must come before readBody/validateBody/fetch
|
||||
if (readBodyIndex > -1) {
|
||||
expect(adminIndex).toBeLessThan(readBodyIndex)
|
||||
}
|
||||
if (validateBodyIndex > -1) {
|
||||
expect(adminIndex).toBeLessThan(validateBodyIndex)
|
||||
}
|
||||
if (fetchIndex > -1) {
|
||||
expect(adminIndex).toBeLessThan(fetchIndex)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
61
tests/server/api/members-create-response.test.js
Normal file
61
tests/server/api/members-create-response.test.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const createPostPath = resolve(
|
||||
import.meta.dirname,
|
||||
'../../../server/api/members/create.post.js'
|
||||
)
|
||||
|
||||
describe('members/create.post.js response projection', () => {
|
||||
const source = readFileSync(createPostPath, 'utf-8')
|
||||
|
||||
it('does NOT return the raw member object', () => {
|
||||
// The old pattern: `return { success: true, member }`
|
||||
// should be replaced with an explicit projection
|
||||
expect(source).not.toMatch(/return\s*\{\s*success:\s*true,\s*member\s*\}/)
|
||||
})
|
||||
|
||||
it('returns explicit member projection with id', () => {
|
||||
expect(source).toContain('member._id')
|
||||
})
|
||||
|
||||
it('returns explicit member projection with email', () => {
|
||||
expect(source).toContain('member.email')
|
||||
})
|
||||
|
||||
it('returns explicit member projection with name', () => {
|
||||
expect(source).toContain('member.name')
|
||||
})
|
||||
|
||||
it('returns explicit member projection with circle', () => {
|
||||
expect(source).toContain('member.circle')
|
||||
})
|
||||
|
||||
it('returns explicit member projection with status', () => {
|
||||
expect(source).toContain('member.status')
|
||||
})
|
||||
|
||||
it('does NOT expose helcimCustomerId in response', () => {
|
||||
// helcimCustomerId should not appear in any return statement projection
|
||||
// (it's OK if it appears elsewhere, e.g. in DB queries)
|
||||
const returnBlocks = source.match(/return\s*\{[\s\S]*?\n\s*\}/g) || []
|
||||
const successReturn = returnBlocks.find(b => b.includes('success: true'))
|
||||
if (successReturn) {
|
||||
expect(successReturn).not.toContain('helcimCustomerId')
|
||||
}
|
||||
})
|
||||
|
||||
it('does NOT expose role in response projection', () => {
|
||||
const returnBlocks = source.match(/return\s*\{[\s\S]*?\n\s*\}/g) || []
|
||||
const successReturn = returnBlocks.find(b => b.includes('success: true'))
|
||||
if (successReturn) {
|
||||
expect(successReturn).not.toContain('role')
|
||||
}
|
||||
})
|
||||
|
||||
it('does NOT forward error.message to client in catch block', () => {
|
||||
// The outer catch should not use error.message as statusMessage
|
||||
expect(source).not.toMatch(/statusMessage:\s*error\.message/)
|
||||
})
|
||||
})
|
||||
559
tests/server/api/validation-phase3.test.js
Normal file
559
tests/server/api/validation-phase3.test.js
Normal file
|
|
@ -0,0 +1,559 @@
|
|||
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()}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue