Barazo AppView backend barazo.forum
at main 238 lines 7.4 kB view raw
1import { describe, it, expect } from 'vitest' 2import { 3 createOnboardingFieldSchema, 4 updateOnboardingFieldSchema, 5 reorderFieldsSchema, 6 submitOnboardingSchema, 7 validateFieldResponse, 8} from '../../../src/validation/onboarding.js' 9 10// --------------------------------------------------------------------------- 11// createOnboardingFieldSchema 12// --------------------------------------------------------------------------- 13 14describe('createOnboardingFieldSchema', () => { 15 it('accepts valid field creation with all fields', () => { 16 const result = createOnboardingFieldSchema.safeParse({ 17 fieldType: 'custom_text', 18 label: 'What brings you here?', 19 description: 'Tell us about yourself', 20 isMandatory: true, 21 sortOrder: 1, 22 config: null, 23 }) 24 expect(result.success).toBe(true) 25 }) 26 27 it('accepts minimal valid input (defaults applied)', () => { 28 const result = createOnboardingFieldSchema.safeParse({ 29 fieldType: 'tos_acceptance', 30 label: 'Accept our terms', 31 }) 32 expect(result.success).toBe(true) 33 if (result.success) { 34 expect(result.data.isMandatory).toBe(true) 35 expect(result.data.sortOrder).toBe(0) 36 } 37 }) 38 39 it('rejects invalid field type', () => { 40 const result = createOnboardingFieldSchema.safeParse({ 41 fieldType: 'invalid_type', 42 label: 'Test', 43 }) 44 expect(result.success).toBe(false) 45 }) 46 47 it('rejects empty label', () => { 48 const result = createOnboardingFieldSchema.safeParse({ 49 fieldType: 'custom_text', 50 label: '', 51 }) 52 expect(result.success).toBe(false) 53 }) 54 55 it('rejects label over 200 characters', () => { 56 const result = createOnboardingFieldSchema.safeParse({ 57 fieldType: 'custom_text', 58 label: 'a'.repeat(201), 59 }) 60 expect(result.success).toBe(false) 61 }) 62 63 it('accepts all valid field types', () => { 64 const types = [ 65 'age_confirmation', 66 'tos_acceptance', 67 'newsletter_email', 68 'custom_text', 69 'custom_select', 70 'custom_checkbox', 71 ] 72 for (const fieldType of types) { 73 const result = createOnboardingFieldSchema.safeParse({ 74 fieldType, 75 label: 'Test', 76 }) 77 expect(result.success).toBe(true) 78 } 79 }) 80}) 81 82// --------------------------------------------------------------------------- 83// updateOnboardingFieldSchema 84// --------------------------------------------------------------------------- 85 86describe('updateOnboardingFieldSchema', () => { 87 it('accepts partial update with label only', () => { 88 const result = updateOnboardingFieldSchema.safeParse({ 89 label: 'Updated label', 90 }) 91 expect(result.success).toBe(true) 92 }) 93 94 it('accepts partial update with isMandatory', () => { 95 const result = updateOnboardingFieldSchema.safeParse({ 96 isMandatory: false, 97 }) 98 expect(result.success).toBe(true) 99 }) 100 101 it('rejects empty object (no fields)', () => { 102 const result = updateOnboardingFieldSchema.safeParse({}) 103 // Empty object is valid from schema perspective; route enforces at least one field 104 expect(result.success).toBe(true) 105 }) 106}) 107 108// --------------------------------------------------------------------------- 109// reorderFieldsSchema 110// --------------------------------------------------------------------------- 111 112describe('reorderFieldsSchema', () => { 113 it('accepts valid reorder array', () => { 114 const result = reorderFieldsSchema.safeParse([ 115 { id: 'field-1', sortOrder: 0 }, 116 { id: 'field-2', sortOrder: 1 }, 117 ]) 118 expect(result.success).toBe(true) 119 }) 120 121 it('rejects empty array', () => { 122 const result = reorderFieldsSchema.safeParse([]) 123 expect(result.success).toBe(false) 124 }) 125 126 it('rejects negative sort order', () => { 127 const result = reorderFieldsSchema.safeParse([{ id: 'field-1', sortOrder: -1 }]) 128 expect(result.success).toBe(false) 129 }) 130}) 131 132// --------------------------------------------------------------------------- 133// submitOnboardingSchema 134// --------------------------------------------------------------------------- 135 136describe('submitOnboardingSchema', () => { 137 it('accepts valid submission array', () => { 138 const result = submitOnboardingSchema.safeParse([ 139 { fieldId: 'field-1', response: true }, 140 { fieldId: 'field-2', response: 'hello' }, 141 ]) 142 expect(result.success).toBe(true) 143 }) 144 145 it('rejects empty array', () => { 146 const result = submitOnboardingSchema.safeParse([]) 147 expect(result.success).toBe(false) 148 }) 149}) 150 151// --------------------------------------------------------------------------- 152// validateFieldResponse 153// --------------------------------------------------------------------------- 154 155describe('validateFieldResponse', () => { 156 describe('age_confirmation', () => { 157 it('accepts valid age values', () => { 158 for (const age of [0, 13, 14, 15, 16, 18]) { 159 expect(validateFieldResponse('age_confirmation', age, null)).toBeNull() 160 } 161 }) 162 163 it('rejects invalid age values', () => { 164 expect(validateFieldResponse('age_confirmation', 12, null)).not.toBeNull() 165 expect(validateFieldResponse('age_confirmation', 17, null)).not.toBeNull() 166 expect(validateFieldResponse('age_confirmation', '16', null)).not.toBeNull() 167 }) 168 }) 169 170 describe('tos_acceptance', () => { 171 it('accepts true', () => { 172 expect(validateFieldResponse('tos_acceptance', true, null)).toBeNull() 173 }) 174 175 it('rejects false', () => { 176 expect(validateFieldResponse('tos_acceptance', false, null)).not.toBeNull() 177 }) 178 179 it('rejects non-boolean', () => { 180 expect(validateFieldResponse('tos_acceptance', 'yes', null)).not.toBeNull() 181 }) 182 }) 183 184 describe('newsletter_email', () => { 185 it('accepts valid email', () => { 186 expect(validateFieldResponse('newsletter_email', 'test@example.com', null)).toBeNull() 187 }) 188 189 it('accepts empty string (optional)', () => { 190 expect(validateFieldResponse('newsletter_email', '', null)).toBeNull() 191 }) 192 193 it('rejects invalid email', () => { 194 expect(validateFieldResponse('newsletter_email', 'not-an-email', null)).not.toBeNull() 195 }) 196 }) 197 198 describe('custom_text', () => { 199 it('accepts valid text', () => { 200 expect(validateFieldResponse('custom_text', 'Hello world', null)).toBeNull() 201 }) 202 203 it('rejects text over 1000 chars', () => { 204 expect(validateFieldResponse('custom_text', 'a'.repeat(1001), null)).not.toBeNull() 205 }) 206 207 it('rejects non-string', () => { 208 expect(validateFieldResponse('custom_text', 42, null)).not.toBeNull() 209 }) 210 }) 211 212 describe('custom_select', () => { 213 const config = { options: ['opt1', 'opt2', 'opt3'] } 214 215 it('accepts valid selection', () => { 216 expect(validateFieldResponse('custom_select', 'opt1', config)).toBeNull() 217 }) 218 219 it('rejects invalid selection', () => { 220 expect(validateFieldResponse('custom_select', 'opt4', config)).not.toBeNull() 221 }) 222 223 it('rejects non-string', () => { 224 expect(validateFieldResponse('custom_select', 1, config)).not.toBeNull() 225 }) 226 }) 227 228 describe('custom_checkbox', () => { 229 it('accepts boolean values', () => { 230 expect(validateFieldResponse('custom_checkbox', true, null)).toBeNull() 231 expect(validateFieldResponse('custom_checkbox', false, null)).toBeNull() 232 }) 233 234 it('rejects non-boolean', () => { 235 expect(validateFieldResponse('custom_checkbox', 'yes', null)).not.toBeNull() 236 }) 237 }) 238})