Barazo AppView backend
barazo.forum
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})