digital-objects 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +476 -0
- package/dist/ai-database-adapter.d.ts +49 -0
- package/dist/ai-database-adapter.d.ts.map +1 -0
- package/dist/ai-database-adapter.js +89 -0
- package/dist/ai-database-adapter.js.map +1 -0
- package/dist/errors.d.ts +47 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +72 -0
- package/dist/errors.js.map +1 -0
- package/dist/http-schemas.d.ts +165 -0
- package/dist/http-schemas.d.ts.map +1 -0
- package/dist/http-schemas.js +55 -0
- package/dist/http-schemas.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/linguistic.d.ts +54 -0
- package/dist/linguistic.d.ts.map +1 -0
- package/dist/linguistic.js +226 -0
- package/dist/linguistic.js.map +1 -0
- package/dist/memory-provider.d.ts +46 -0
- package/dist/memory-provider.d.ts.map +1 -0
- package/dist/memory-provider.js +279 -0
- package/dist/memory-provider.js.map +1 -0
- package/dist/ns-client.d.ts +88 -0
- package/dist/ns-client.d.ts.map +1 -0
- package/dist/ns-client.js +253 -0
- package/dist/ns-client.js.map +1 -0
- package/dist/ns-exports.d.ts +23 -0
- package/dist/ns-exports.d.ts.map +1 -0
- package/dist/ns-exports.js +21 -0
- package/dist/ns-exports.js.map +1 -0
- package/dist/ns.d.ts +60 -0
- package/dist/ns.d.ts.map +1 -0
- package/dist/ns.js +818 -0
- package/dist/ns.js.map +1 -0
- package/dist/r2-persistence.d.ts +112 -0
- package/dist/r2-persistence.d.ts.map +1 -0
- package/dist/r2-persistence.js +252 -0
- package/dist/r2-persistence.js.map +1 -0
- package/dist/schema-validation.d.ts +80 -0
- package/dist/schema-validation.d.ts.map +1 -0
- package/dist/schema-validation.js +233 -0
- package/dist/schema-validation.js.map +1 -0
- package/dist/types.d.ts +184 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +26 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
- package/src/ai-database-adapter.test.ts +610 -0
- package/src/ai-database-adapter.ts +189 -0
- package/src/benchmark.test.ts +109 -0
- package/src/errors.ts +91 -0
- package/src/http-schemas.ts +67 -0
- package/src/index.ts +87 -0
- package/src/linguistic.test.ts +1107 -0
- package/src/linguistic.ts +253 -0
- package/src/memory-provider.ts +470 -0
- package/src/ns-client.test.ts +1360 -0
- package/src/ns-client.ts +342 -0
- package/src/ns-exports.ts +23 -0
- package/src/ns.test.ts +1381 -0
- package/src/ns.ts +1215 -0
- package/src/provider.test.ts +675 -0
- package/src/r2-persistence.test.ts +263 -0
- package/src/r2-persistence.ts +367 -0
- package/src/schema-validation.test.ts +167 -0
- package/src/schema-validation.ts +330 -0
- package/src/types.ts +252 -0
- package/test/action-status.test.ts +42 -0
- package/test/batch-limits.test.ts +165 -0
- package/test/docs.test.ts +48 -0
- package/test/errors.test.ts +148 -0
- package/test/http-validation.test.ts +401 -0
- package/test/ns-client-errors.test.ts +208 -0
- package/test/ns-namespace.test.ts +307 -0
- package/test/performance.test.ts +168 -0
- package/test/schema-validation-error.test.ts +213 -0
- package/test/schema-validation.test.ts +440 -0
- package/test/search-escaping.test.ts +359 -0
- package/test/security.test.ts +322 -0
- package/tsconfig.json +10 -0
- package/wrangler.jsonc +16 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
validateData,
|
|
4
|
+
validateOnly,
|
|
5
|
+
type SchemaValidationError,
|
|
6
|
+
type ValidationResult,
|
|
7
|
+
type ValidationErrorCode,
|
|
8
|
+
} from '../src/schema-validation.js'
|
|
9
|
+
import type { FieldDefinition } from '../src/types.js'
|
|
10
|
+
import { createMemoryProvider } from '../src/memory-provider.js'
|
|
11
|
+
import { ValidationError } from '../src/errors.js'
|
|
12
|
+
|
|
13
|
+
describe('Schema Validation', () => {
|
|
14
|
+
describe('validateOnly', () => {
|
|
15
|
+
it('should return valid:true for data matching schema', () => {
|
|
16
|
+
const schema: Record<string, FieldDefinition> = {
|
|
17
|
+
name: 'string',
|
|
18
|
+
age: 'number',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = validateOnly({ name: 'Alice', age: 25 }, schema)
|
|
22
|
+
|
|
23
|
+
expect(result.valid).toBe(true)
|
|
24
|
+
expect(result.errors).toHaveLength(0)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should return valid:true when no schema is provided', () => {
|
|
28
|
+
const result = validateOnly({ anything: 'goes' }, undefined)
|
|
29
|
+
|
|
30
|
+
expect(result.valid).toBe(true)
|
|
31
|
+
expect(result.errors).toHaveLength(0)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should return errors for type mismatches', () => {
|
|
35
|
+
const schema: Record<string, FieldDefinition> = {
|
|
36
|
+
name: 'string',
|
|
37
|
+
age: 'number',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = validateOnly({ name: 'Alice', age: '25' }, schema)
|
|
41
|
+
|
|
42
|
+
expect(result.valid).toBe(false)
|
|
43
|
+
expect(result.errors).toHaveLength(1)
|
|
44
|
+
expect(result.errors[0].field).toBe('age')
|
|
45
|
+
expect(result.errors[0].code).toBe('TYPE_MISMATCH')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should return errors for missing required fields', () => {
|
|
49
|
+
const schema: Record<string, FieldDefinition> = {
|
|
50
|
+
email: { type: 'string', required: true },
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = validateOnly({}, schema)
|
|
54
|
+
|
|
55
|
+
expect(result.valid).toBe(false)
|
|
56
|
+
expect(result.errors).toHaveLength(1)
|
|
57
|
+
expect(result.errors[0].field).toBe('email')
|
|
58
|
+
expect(result.errors[0].code).toBe('REQUIRED_FIELD')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should allow missing optional fields', () => {
|
|
62
|
+
const schema: Record<string, FieldDefinition> = {
|
|
63
|
+
name: 'string',
|
|
64
|
+
bio: 'string?', // Optional
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const result = validateOnly({ name: 'Alice' }, schema)
|
|
68
|
+
|
|
69
|
+
expect(result.valid).toBe(true)
|
|
70
|
+
expect(result.errors).toHaveLength(0)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should skip validation for relation types', () => {
|
|
74
|
+
const schema: Record<string, FieldDefinition> = {
|
|
75
|
+
authorId: 'Author.id',
|
|
76
|
+
tags: '[Tag.posts]',
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = validateOnly({ authorId: 'abc', tags: ['t1', 't2'] }, schema)
|
|
80
|
+
|
|
81
|
+
expect(result.valid).toBe(true)
|
|
82
|
+
expect(result.errors).toHaveLength(0)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('validateData', () => {
|
|
87
|
+
it('should not throw when validation is disabled', () => {
|
|
88
|
+
const schema: Record<string, FieldDefinition> = {
|
|
89
|
+
email: { type: 'string', required: true },
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// No options = validation disabled
|
|
93
|
+
expect(() => validateData({}, schema)).not.toThrow()
|
|
94
|
+
|
|
95
|
+
// validate: false = validation disabled
|
|
96
|
+
expect(() => validateData({}, schema, { validate: false })).not.toThrow()
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('should throw on validation failure when enabled', () => {
|
|
100
|
+
const schema: Record<string, FieldDefinition> = {
|
|
101
|
+
email: { type: 'string', required: true },
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
expect(() => validateData({}, schema, { validate: true })).toThrow(/Validation failed/)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should include error count in message', () => {
|
|
108
|
+
const schema: Record<string, FieldDefinition> = {
|
|
109
|
+
email: { type: 'string', required: true },
|
|
110
|
+
name: { type: 'string', required: true },
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
validateData({}, schema, { validate: true })
|
|
115
|
+
expect.fail('Should have thrown')
|
|
116
|
+
} catch (error) {
|
|
117
|
+
expect((error as Error).message).toMatch(/2 errors/)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should use singular error count for single error', () => {
|
|
122
|
+
const schema: Record<string, FieldDefinition> = {
|
|
123
|
+
email: { type: 'string', required: true },
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
validateData({}, schema, { validate: true })
|
|
128
|
+
expect.fail('Should have thrown')
|
|
129
|
+
} catch (error) {
|
|
130
|
+
expect((error as Error).message).toMatch(/1 error/)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
describe('Error Messages', () => {
|
|
136
|
+
it('should include field path in error message', () => {
|
|
137
|
+
const schema: Record<string, FieldDefinition> = {
|
|
138
|
+
age: 'number',
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const result = validateOnly({ age: 'twenty-five' }, schema)
|
|
142
|
+
|
|
143
|
+
expect(result.errors[0].message).toContain("'age'")
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should include expected and received types', () => {
|
|
147
|
+
const schema: Record<string, FieldDefinition> = {
|
|
148
|
+
count: 'number',
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const result = validateOnly({ count: 'five' }, schema)
|
|
152
|
+
|
|
153
|
+
expect(result.errors[0].expected).toBe('number')
|
|
154
|
+
expect(result.errors[0].received).toBe('string')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should have clear missing field message', () => {
|
|
158
|
+
const schema: Record<string, FieldDefinition> = {
|
|
159
|
+
email: { type: 'string', required: true },
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const result = validateOnly({}, schema)
|
|
163
|
+
|
|
164
|
+
expect(result.errors[0].message).toBe("Missing required field 'email'")
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('should have clear type mismatch message', () => {
|
|
168
|
+
const schema: Record<string, FieldDefinition> = {
|
|
169
|
+
age: 'number',
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const result = validateOnly({ age: 'not-a-number' }, schema)
|
|
173
|
+
|
|
174
|
+
expect(result.errors[0].message).toBe(
|
|
175
|
+
"Field 'age' has wrong type: expected number, got string"
|
|
176
|
+
)
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
describe('Suggestions', () => {
|
|
181
|
+
it('should suggest conversion for string to number', () => {
|
|
182
|
+
const schema: Record<string, FieldDefinition> = {
|
|
183
|
+
age: 'number',
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const result = validateOnly({ age: '25' }, schema)
|
|
187
|
+
|
|
188
|
+
expect(result.errors[0].suggestion).toBe('Convert to number: 25')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('should suggest valid number for non-numeric string', () => {
|
|
192
|
+
const schema: Record<string, FieldDefinition> = {
|
|
193
|
+
age: 'number',
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const result = validateOnly({ age: 'twenty-five' }, schema)
|
|
197
|
+
|
|
198
|
+
expect(result.errors[0].suggestion).toBe('Provide a valid number')
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('should suggest conversion for number to string', () => {
|
|
202
|
+
const schema: Record<string, FieldDefinition> = {
|
|
203
|
+
name: 'string',
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const result = validateOnly({ name: 42 }, schema)
|
|
207
|
+
|
|
208
|
+
expect(result.errors[0].suggestion).toBe('Convert to string: "42"')
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should suggest conversion for string boolean', () => {
|
|
212
|
+
const schema: Record<string, FieldDefinition> = {
|
|
213
|
+
active: 'boolean',
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const result = validateOnly({ active: 'true' }, schema)
|
|
217
|
+
|
|
218
|
+
expect(result.errors[0].suggestion).toBe('Convert to boolean: true')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('should suggest wrapping value in array', () => {
|
|
222
|
+
const schema: Record<string, FieldDefinition> = {
|
|
223
|
+
items: { type: 'array', required: true },
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const result = validateOnly({ items: 'single-item' }, schema)
|
|
227
|
+
|
|
228
|
+
expect(result.errors[0].suggestion).toBe('Wrap value in an array: [value]')
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('should suggest object format', () => {
|
|
232
|
+
const schema: Record<string, FieldDefinition> = {
|
|
233
|
+
metadata: { type: 'object', required: true },
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const result = validateOnly({ metadata: 'not-an-object' }, schema)
|
|
237
|
+
|
|
238
|
+
expect(result.errors[0].suggestion).toBe('Provide an object: { ... }')
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
describe('Special Types', () => {
|
|
243
|
+
it('should validate date/datetime as string', () => {
|
|
244
|
+
const schema: Record<string, FieldDefinition> = {
|
|
245
|
+
createdAt: 'datetime',
|
|
246
|
+
publishDate: 'date',
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Valid - strings are accepted
|
|
250
|
+
const validResult = validateOnly(
|
|
251
|
+
{
|
|
252
|
+
createdAt: '2024-01-15T10:00:00Z',
|
|
253
|
+
publishDate: '2024-01-15',
|
|
254
|
+
},
|
|
255
|
+
schema
|
|
256
|
+
)
|
|
257
|
+
expect(validResult.valid).toBe(true)
|
|
258
|
+
|
|
259
|
+
// Invalid - numbers are not accepted
|
|
260
|
+
const invalidResult = validateOnly({ createdAt: 1705312800000 }, schema)
|
|
261
|
+
expect(invalidResult.valid).toBe(false)
|
|
262
|
+
expect(invalidResult.errors[0].suggestion).toBe('Provide a valid ISO 8601 date string')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('should validate url as string', () => {
|
|
266
|
+
const schema: Record<string, FieldDefinition> = {
|
|
267
|
+
website: 'url',
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const validResult = validateOnly({ website: 'https://example.com' }, schema)
|
|
271
|
+
expect(validResult.valid).toBe(true)
|
|
272
|
+
|
|
273
|
+
const invalidResult = validateOnly({ website: 123 }, schema)
|
|
274
|
+
expect(invalidResult.valid).toBe(false)
|
|
275
|
+
expect(invalidResult.errors[0].suggestion).toBe(
|
|
276
|
+
'Provide a valid URL starting with http:// or https://'
|
|
277
|
+
)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('should validate markdown as string', () => {
|
|
281
|
+
const schema: Record<string, FieldDefinition> = {
|
|
282
|
+
content: 'markdown',
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const validResult = validateOnly({ content: '# Hello\n\nWorld' }, schema)
|
|
286
|
+
expect(validResult.valid).toBe(true)
|
|
287
|
+
|
|
288
|
+
const invalidResult = validateOnly({ content: { text: 'hello' } }, schema)
|
|
289
|
+
expect(invalidResult.valid).toBe(false)
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('should validate json as string', () => {
|
|
293
|
+
const schema: Record<string, FieldDefinition> = {
|
|
294
|
+
config: 'json',
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const validResult = validateOnly({ config: '{"key": "value"}' }, schema)
|
|
298
|
+
expect(validResult.valid).toBe(true)
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
describe('Extended Field Definitions', () => {
|
|
303
|
+
it('should validate required fields', () => {
|
|
304
|
+
const schema: Record<string, FieldDefinition> = {
|
|
305
|
+
email: { type: 'string', required: true },
|
|
306
|
+
name: { type: 'string', required: false },
|
|
307
|
+
bio: { type: 'string' }, // required defaults to false
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Missing required field
|
|
311
|
+
const result1 = validateOnly({ name: 'Alice', bio: 'Hello' }, schema)
|
|
312
|
+
expect(result1.valid).toBe(false)
|
|
313
|
+
expect(result1.errors).toHaveLength(1)
|
|
314
|
+
expect(result1.errors[0].field).toBe('email')
|
|
315
|
+
|
|
316
|
+
// With required field
|
|
317
|
+
const result2 = validateOnly({ email: 'alice@example.com' }, schema)
|
|
318
|
+
expect(result2.valid).toBe(true)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('should handle null as missing for required fields', () => {
|
|
322
|
+
const schema: Record<string, FieldDefinition> = {
|
|
323
|
+
email: { type: 'string', required: true },
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const result = validateOnly({ email: null }, schema)
|
|
327
|
+
expect(result.valid).toBe(false)
|
|
328
|
+
expect(result.errors[0].received).toBe('null')
|
|
329
|
+
})
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
describe('Integration with MemoryProvider', () => {
|
|
333
|
+
it('should validate on create when enabled', async () => {
|
|
334
|
+
const provider = createMemoryProvider()
|
|
335
|
+
|
|
336
|
+
await provider.defineNoun({
|
|
337
|
+
name: 'User',
|
|
338
|
+
schema: {
|
|
339
|
+
email: { type: 'string', required: true },
|
|
340
|
+
age: 'number',
|
|
341
|
+
},
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
// Without validation - should succeed
|
|
345
|
+
const user1 = await provider.create('User', { name: 'Anonymous' })
|
|
346
|
+
expect(user1.id).toBeDefined()
|
|
347
|
+
|
|
348
|
+
// With validation - should fail
|
|
349
|
+
await expect(
|
|
350
|
+
provider.create('User', { name: 'Anonymous' }, undefined, { validate: true })
|
|
351
|
+
).rejects.toThrow(/Validation failed/)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('should include field errors in ValidationError', async () => {
|
|
355
|
+
const provider = createMemoryProvider()
|
|
356
|
+
|
|
357
|
+
await provider.defineNoun({
|
|
358
|
+
name: 'Product',
|
|
359
|
+
schema: {
|
|
360
|
+
price: 'number',
|
|
361
|
+
},
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
await provider.create('Product', { price: '19.99' }, undefined, { validate: true })
|
|
366
|
+
expect.fail('Should have thrown')
|
|
367
|
+
} catch (error) {
|
|
368
|
+
expect(error).toBeInstanceOf(ValidationError)
|
|
369
|
+
const validationError = error as ValidationError
|
|
370
|
+
expect(validationError.errors).toHaveLength(1)
|
|
371
|
+
expect(validationError.errors[0].field).toBe('price')
|
|
372
|
+
expect(validationError.errors[0].message).toMatch(/number/i)
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
describe('Error Codes', () => {
|
|
378
|
+
it('should use REQUIRED_FIELD code for missing required fields', () => {
|
|
379
|
+
const schema: Record<string, FieldDefinition> = {
|
|
380
|
+
email: { type: 'string', required: true },
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const result = validateOnly({}, schema)
|
|
384
|
+
expect(result.errors[0].code).toBe('REQUIRED_FIELD')
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it('should use TYPE_MISMATCH code for wrong types', () => {
|
|
388
|
+
const schema: Record<string, FieldDefinition> = {
|
|
389
|
+
count: 'number',
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const result = validateOnly({ count: 'five' }, schema)
|
|
393
|
+
expect(result.errors[0].code).toBe('TYPE_MISMATCH')
|
|
394
|
+
})
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
describe('Multiple Errors', () => {
|
|
398
|
+
it('should collect all validation errors', () => {
|
|
399
|
+
const schema: Record<string, FieldDefinition> = {
|
|
400
|
+
email: { type: 'string', required: true },
|
|
401
|
+
age: 'number',
|
|
402
|
+
active: 'boolean',
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const result = validateOnly({ age: 'not-a-number', active: 'yes' }, schema)
|
|
406
|
+
|
|
407
|
+
expect(result.errors).toHaveLength(3)
|
|
408
|
+
|
|
409
|
+
const codes = result.errors.map((e) => e.code)
|
|
410
|
+
expect(codes).toContain('REQUIRED_FIELD')
|
|
411
|
+
expect(codes.filter((c) => c === 'TYPE_MISMATCH')).toHaveLength(2)
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it('should include multiple errors in ValidationError errors array', () => {
|
|
415
|
+
const schema: Record<string, FieldDefinition> = {
|
|
416
|
+
email: { type: 'string', required: true },
|
|
417
|
+
age: 'number',
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
validateData({ age: 'twenty' }, schema, { validate: true })
|
|
422
|
+
expect.fail('Should have thrown')
|
|
423
|
+
} catch (error) {
|
|
424
|
+
expect(error).toBeInstanceOf(ValidationError)
|
|
425
|
+
const validationError = error as ValidationError
|
|
426
|
+
expect(validationError.errors).toHaveLength(2)
|
|
427
|
+
|
|
428
|
+
const fields = validationError.errors.map((e) => e.field)
|
|
429
|
+
expect(fields).toContain('email')
|
|
430
|
+
expect(fields).toContain('age')
|
|
431
|
+
|
|
432
|
+
const emailError = validationError.errors.find((e) => e.field === 'email')
|
|
433
|
+
expect(emailError?.message).toMatch(/required/i)
|
|
434
|
+
|
|
435
|
+
const ageError = validationError.errors.find((e) => e.field === 'age')
|
|
436
|
+
expect(ageError?.message).toMatch(/wrong type/i)
|
|
437
|
+
}
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
})
|