From e4dade18b9f11d5e3dbbb2d5954c50946aa8b777 Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Sun, 19 Apr 2026 18:16:47 +0100 Subject: [PATCH] =?UTF-8?q?feat(validation):=20rename=20contributionTier?= =?UTF-8?q?=20=E2=86=92=20contributionAmount=20in=20Zod=20schemas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/utils/schemas.js | 16 +++---- tests/server/api/validation-phase3.test.js | 51 ++++++++++++---------- tests/server/api/validation.test.js | 17 +++++--- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/server/utils/schemas.js b/server/utils/schemas.js index 4d7ad16..464f77c 100644 --- a/server/utils/schemas.js +++ b/server/utils/schemas.js @@ -9,7 +9,7 @@ export const memberCreateSchema = z.object({ email: z.string().trim().toLowerCase().email(), name: z.string().min(1).max(200), circle: z.enum(['community', 'founder', 'practitioner']), - contributionTier: z.enum(['0', '5', '15', '30', '50']) + contributionAmount: z.number().int().min(0) }) export const memberProfileUpdateSchema = z.object({ @@ -57,7 +57,7 @@ export const helcimCustomerSchema = z.object({ name: z.string().min(1).max(200), email: z.string().trim().toLowerCase().email(), circle: z.enum(['community', 'founder', 'practitioner']).optional(), - contributionTier: z.enum(['0', '5', '15', '30', '50']).optional(), + contributionAmount: z.number().int().min(0).optional(), agreedToGuidelines: z.literal(true) }) @@ -73,7 +73,7 @@ export const helcimInitializePaymentSchema = z.object({ export const helcimSubscriptionSchema = z.object({ customerId: z.union([z.string().min(1), z.number()]), - contributionTier: z.enum(['0', '5', '15', '30', '50']), + contributionAmount: z.number().int().min(0), customerCode: z.union([z.string().min(1).max(200), z.number()]).transform(String), cardToken: z.string().max(500).optional().nullable(), cadence: z.enum(['monthly', 'annual']).default('monthly') @@ -140,7 +140,7 @@ export const eventPaymentSchema = z.object({ // --- Member schemas --- export const updateContributionSchema = z.object({ - contributionTier: z.enum(['0', '5', '15', '30', '50']), + contributionAmount: z.number().int().min(0), cadence: z.enum(['monthly', 'annual']).default('monthly') }) @@ -294,14 +294,14 @@ export const adminMemberCreateSchema = z.object({ name: z.string().min(1).max(200), email: z.string().trim().toLowerCase().email(), circle: z.enum(['community', 'founder', 'practitioner']), - contributionTier: z.enum(['0', '5', '15', '30', '50']) + contributionAmount: z.number().int().min(0) }) export const adminMemberUpdateSchema = z.object({ name: z.string().min(1).max(200), email: z.string().trim().toLowerCase().email(), circle: z.enum(['community', 'founder', 'practitioner']), - contributionTier: z.enum(['0', '5', '15', '30', '50']), + contributionAmount: z.number().int().min(0), status: z.enum(['pending_payment', 'active', 'suspended', 'cancelled']) }) @@ -314,7 +314,7 @@ export const bulkMemberImportSchema = z.object({ name: z.string().min(1).max(200), email: z.string().trim().toLowerCase().email(), circle: z.enum(['community', 'founder', 'practitioner']), - contributionTier: z.enum(['0', '5', '15', '30', '50']) + contributionAmount: z.number().int().min(0) })).min(1).max(100) }) @@ -351,7 +351,7 @@ export const inviteAcceptSchema = z.object({ location: z.string().max(200).optional(), circle: z.enum(['community', 'founder', 'practitioner']), motivation: z.string().max(5000).optional(), - contributionTier: z.enum(['0', '5', '15', '30', '50']), + contributionAmount: z.number().int().min(0), agreedToGuidelines: z.literal(true), token: z.string().min(1) }) diff --git a/tests/server/api/validation-phase3.test.js b/tests/server/api/validation-phase3.test.js index ed12cd4..e34acde 100644 --- a/tests/server/api/validation-phase3.test.js +++ b/tests/server/api/validation-phase3.test.js @@ -138,16 +138,16 @@ describe('helcimSubscriptionSchema', () => { it('accepts valid subscription data', () => { const result = helcimSubscriptionSchema.safeParse({ customerId: '12345', - contributionTier: '15', + contributionAmount: 15, customerCode: 'CST123' }) expect(result.success).toBe(true) }) - it('rejects invalid contribution tier', () => { + it('rejects negative contributionAmount', () => { const result = helcimSubscriptionSchema.safeParse({ customerId: '12345', - contributionTier: '999', + contributionAmount: -1, customerCode: 'CST123' }) expect(result.success).toBe(false) @@ -156,7 +156,7 @@ describe('helcimSubscriptionSchema', () => { it('rejects missing customerCode', () => { const result = helcimSubscriptionSchema.safeParse({ customerId: '12345', - contributionTier: '15' + contributionAmount: 15 }) expect(result.success).toBe(false) }) @@ -164,7 +164,7 @@ describe('helcimSubscriptionSchema', () => { it('accepts cadence: monthly', () => { const result = helcimSubscriptionSchema.safeParse({ customerId: '12345', - contributionTier: '15', + contributionAmount: 15, customerCode: 'CST123', cadence: 'monthly' }) @@ -175,7 +175,7 @@ describe('helcimSubscriptionSchema', () => { it('accepts cadence: annual', () => { const result = helcimSubscriptionSchema.safeParse({ customerId: '12345', - contributionTier: '15', + contributionAmount: 15, customerCode: 'CST123', cadence: 'annual' }) @@ -186,7 +186,7 @@ describe('helcimSubscriptionSchema', () => { it('rejects cadence: weekly', () => { const result = helcimSubscriptionSchema.safeParse({ customerId: '12345', - contributionTier: '15', + contributionAmount: 15, customerCode: 'CST123', cadence: 'weekly' }) @@ -196,7 +196,7 @@ describe('helcimSubscriptionSchema', () => { it('defaults cadence to monthly when omitted', () => { const result = helcimSubscriptionSchema.safeParse({ customerId: '12345', - contributionTier: '15', + contributionAmount: 15, customerCode: 'CST123' }) expect(result.success).toBe(true) @@ -334,19 +334,26 @@ describe('eventPaymentSchema', () => { // --- Member schemas --- describe('updateContributionSchema', () => { - it('accepts valid contribution tier', () => { - const result = updateContributionSchema.safeParse({ contributionTier: '15' }) + it('accepts valid contributionAmount', () => { + const result = updateContributionSchema.safeParse({ contributionAmount: 15 }) expect(result.success).toBe(true) }) - it('rejects invalid tier', () => { - const result = updateContributionSchema.safeParse({ contributionTier: '100' }) - expect(result.success).toBe(false) + it('accepts contributionAmount: 0, 7, 9999', () => { + expect(updateContributionSchema.safeParse({ contributionAmount: 0 }).success).toBe(true) + expect(updateContributionSchema.safeParse({ contributionAmount: 7 }).success).toBe(true) + expect(updateContributionSchema.safeParse({ contributionAmount: 9999 }).success).toBe(true) + }) + + it('rejects invalid contributionAmount values', () => { + expect(updateContributionSchema.safeParse({ contributionAmount: -1 }).success).toBe(false) + expect(updateContributionSchema.safeParse({ contributionAmount: 1.5 }).success).toBe(false) + expect(updateContributionSchema.safeParse({ contributionAmount: '15' }).success).toBe(false) }) it('strips unknown fields', () => { const result = updateContributionSchema.safeParse({ - contributionTier: '15', + contributionAmount: 15, role: 'admin' }) expect(result.success).toBe(true) @@ -354,24 +361,24 @@ describe('updateContributionSchema', () => { }) it('accepts cadence: monthly', () => { - const result = updateContributionSchema.safeParse({ contributionTier: '15', cadence: 'monthly' }) + const result = updateContributionSchema.safeParse({ contributionAmount: 15, cadence: 'monthly' }) expect(result.success).toBe(true) expect(result.data.cadence).toBe('monthly') }) it('accepts cadence: annual', () => { - const result = updateContributionSchema.safeParse({ contributionTier: '15', cadence: 'annual' }) + const result = updateContributionSchema.safeParse({ contributionAmount: 15, cadence: 'annual' }) expect(result.success).toBe(true) expect(result.data.cadence).toBe('annual') }) it('rejects cadence: weekly', () => { - const result = updateContributionSchema.safeParse({ contributionTier: '15', cadence: 'weekly' }) + const result = updateContributionSchema.safeParse({ contributionAmount: 15, cadence: 'weekly' }) expect(result.success).toBe(false) }) it('defaults cadence to monthly when omitted', () => { - const result = updateContributionSchema.safeParse({ contributionTier: '15' }) + const result = updateContributionSchema.safeParse({ contributionAmount: 15 }) expect(result.success).toBe(true) expect(result.data.cadence).toBe('monthly') }) @@ -425,7 +432,7 @@ describe('adminMemberCreateSchema', () => { name: 'Admin Created', email: 'admin-created@example.com', circle: 'founder', - contributionTier: '30' + contributionAmount: 30 }) expect(result.success).toBe(true) }) @@ -435,7 +442,7 @@ describe('adminMemberCreateSchema', () => { name: 'Admin Created', email: 'admin-created@example.com', circle: 'founder', - contributionTier: '30', + contributionAmount: 30, role: 'admin' }) expect(result.success).toBe(true) @@ -447,7 +454,7 @@ describe('adminMemberCreateSchema', () => { name: 'Admin Created', email: 'admin-created@example.com', circle: 'founder', - contributionTier: '30', + contributionAmount: 30, status: 'active' }) expect(result.success).toBe(true) @@ -459,7 +466,7 @@ describe('adminMemberCreateSchema', () => { name: 'Admin Created', email: 'admin-created@example.com', circle: 'superadmin', - contributionTier: '30' + contributionAmount: 30 }) expect(result.success).toBe(false) }) diff --git a/tests/server/api/validation.test.js b/tests/server/api/validation.test.js index c8d45ba..10b59e2 100644 --- a/tests/server/api/validation.test.js +++ b/tests/server/api/validation.test.js @@ -42,7 +42,7 @@ describe('memberCreateSchema', () => { email: 'new@example.com', name: 'Test User', circle: 'community', - contributionTier: '0' + contributionAmount: 0 } it('accepts valid member data', () => { @@ -80,9 +80,16 @@ describe('memberCreateSchema', () => { expect(result.success).toBe(false) }) - it('rejects invalid contributionTier enum', () => { - const result = memberCreateSchema.safeParse({ ...validMember, contributionTier: '999' }) - expect(result.success).toBe(false) + it('accepts contributionAmount: 0, 7, 9999', () => { + expect(memberCreateSchema.safeParse({ ...validMember, contributionAmount: 0 }).success).toBe(true) + expect(memberCreateSchema.safeParse({ ...validMember, contributionAmount: 7 }).success).toBe(true) + expect(memberCreateSchema.safeParse({ ...validMember, contributionAmount: 9999 }).success).toBe(true) + }) + + it('rejects invalid contributionAmount values', () => { + expect(memberCreateSchema.safeParse({ ...validMember, contributionAmount: -1 }).success).toBe(false) + expect(memberCreateSchema.safeParse({ ...validMember, contributionAmount: 1.5 }).success).toBe(false) + expect(memberCreateSchema.safeParse({ ...validMember, contributionAmount: '15' }).success).toBe(false) }) it('rejects missing required fields', () => { @@ -246,7 +253,7 @@ describe('validateBody', () => { it('strips unknown fields from output', async () => { const event = createMockEvent({ method: 'POST', - body: { email: 'test@example.com', name: 'Test', circle: 'community', contributionTier: '0', role: 'admin', _id: 'fake' } + body: { email: 'test@example.com', name: 'Test', circle: 'community', contributionAmount: 0, role: 'admin', _id: 'fake' } }) const data = await validateBody(event, memberCreateSchema) expect(data).not.toHaveProperty('role')