joi 8.4.2 → 9.0.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/test/errors.js ADDED
@@ -0,0 +1,512 @@
1
+ 'use strict';
2
+
3
+ // Load modules
4
+
5
+ const Lab = require('lab');
6
+ const Code = require('code');
7
+ const Joi = require('../lib');
8
+
9
+
10
+ // Declare internals
11
+
12
+ const internals = {};
13
+
14
+
15
+ // Test shortcuts
16
+
17
+ const lab = exports.lab = Lab.script();
18
+ const describe = lab.describe;
19
+ const it = lab.it;
20
+ const expect = Code.expect;
21
+
22
+
23
+ describe('errors', () => {
24
+
25
+ it('has an isJoi property', (done) => {
26
+
27
+ Joi.validate('bar', Joi.valid('foo'), (err) => {
28
+
29
+ expect(err).to.exist();
30
+ expect(err.isJoi).to.be.true();
31
+ done();
32
+ });
33
+ });
34
+
35
+ it('supports custom errors when validating types', (done) => {
36
+
37
+ const schema = Joi.object({
38
+ email: Joi.string().email(),
39
+ date: Joi.date(),
40
+ alphanum: Joi.string().alphanum(),
41
+ min: Joi.string().min(3),
42
+ max: Joi.string().max(3),
43
+ required: Joi.string().required(),
44
+ xor: Joi.string(),
45
+ renamed: Joi.string().valid('456'),
46
+ notEmpty: Joi.string().required()
47
+ }).rename('renamed', 'required').without('required', 'xor').without('xor', 'required');
48
+
49
+ const input = {
50
+ email: 'invalid-email',
51
+ date: 'invalid-date',
52
+ alphanum: '\b\n\f\r\t',
53
+ min: 'ab',
54
+ max: 'abcd',
55
+ required: 'hello',
56
+ xor: '123',
57
+ renamed: '456',
58
+ notEmpty: ''
59
+ };
60
+
61
+ const lang = {
62
+ any: {
63
+ empty: '3'
64
+ },
65
+ date: {
66
+ base: '18'
67
+ },
68
+ string: {
69
+ base: '13',
70
+ min: '14',
71
+ max: '15',
72
+ alphanum: '16',
73
+ email: '19'
74
+ },
75
+ object: {
76
+ without: '7',
77
+ rename: {
78
+ override: '11'
79
+ }
80
+ }
81
+ };
82
+
83
+ Joi.validate(input, schema, { abortEarly: false, language: lang }, (err, value) => {
84
+
85
+ expect(err).to.exist();
86
+ expect(err.name).to.equal('ValidationError');
87
+ expect(err.message).to.equal('"value" 11. child "email" fails because ["email" 19]. child "date" fails because ["date" 18]. child "alphanum" fails because ["alphanum" 16]. child "min" fails because ["min" 14]. child "max" fails because ["max" 15]. child "notEmpty" fails because ["notEmpty" 3]. "required" 7. "xor" 7');
88
+ done();
89
+ });
90
+ });
91
+
92
+ it('does not prefix with key when language uses context.key', (done) => {
93
+
94
+ Joi.valid('sad').options({ language: { any: { allowOnly: 'my hero "{{key}}" is not {{valids}}' } } }).validate(5, (err, value) => {
95
+
96
+ expect(err.message).to.equal('my hero "value" is not [sad]');
97
+ done();
98
+ });
99
+ });
100
+
101
+ it('escapes unsafe keys', (done) => {
102
+
103
+ const schema = {
104
+ 'a()': Joi.number()
105
+ };
106
+
107
+ Joi.validate({ 'a()': 'x' }, schema, (err, value) => {
108
+
109
+ expect(err.message).to.equal('child "a()" fails because ["a()" must be a number]');
110
+
111
+ Joi.validate({ 'b()': 'x' }, schema, (err2, value2) => {
112
+
113
+ expect(err2.message).to.equal('"b()" is not allowed');
114
+ done();
115
+ });
116
+ });
117
+ });
118
+
119
+ it('returns error type in validation error', (done) => {
120
+
121
+ const input = {
122
+ notNumber: '',
123
+ notString: true,
124
+ notBoolean: 9
125
+ };
126
+
127
+ const schema = {
128
+ notNumber: Joi.number().required(),
129
+ notString: Joi.string().required(),
130
+ notBoolean: Joi.boolean().required()
131
+ };
132
+
133
+ Joi.validate(input, schema, { abortEarly: false }, (err, value) => {
134
+
135
+ expect(err).to.exist();
136
+ expect(err.details).to.have.length(3);
137
+ expect(err.details[0].type).to.equal('number.base');
138
+ expect(err.details[1].type).to.equal('string.base');
139
+ expect(err.details[2].type).to.equal('boolean.base');
140
+ done();
141
+ });
142
+ });
143
+
144
+ it('returns a full path to an error value on an array (items)', (done) => {
145
+
146
+ const schema = Joi.array().items(Joi.array().items({ x: Joi.number() }));
147
+ const input = [
148
+ [{ x: 1 }],
149
+ [{ x: 1 }, { x: 'a' }]
150
+ ];
151
+
152
+ schema.validate(input, (err, value) => {
153
+
154
+ expect(err).to.exist();
155
+ expect(err.details[0].path).to.equal('1.1.x');
156
+ done();
157
+ });
158
+ });
159
+
160
+ it('returns a full path to an error value on an array (items forbidden)', (done) => {
161
+
162
+ const schema = Joi.array().items(Joi.array().items(Joi.object({ x: Joi.string() }).forbidden()));
163
+ const input = [
164
+ [{ x: 1 }],
165
+ [{ x: 1 }, { x: 'a' }]
166
+ ];
167
+
168
+ schema.validate(input, (err, value) => {
169
+
170
+ expect(err).to.exist();
171
+ expect(err.details[0].path).to.equal('1.1');
172
+ done();
173
+ });
174
+ });
175
+
176
+ it('returns a full path to an error value on an object', (done) => {
177
+
178
+ const schema = {
179
+ x: Joi.array().items({ x: Joi.number() })
180
+ };
181
+
182
+ const input = {
183
+ x: [{ x: 1 }, { x: 'a' }]
184
+ };
185
+
186
+ Joi.validate(input, schema, (err, value) => {
187
+
188
+ expect(err).to.exist();
189
+ expect(err.details[0].path).to.equal('x.1.x');
190
+ done();
191
+ });
192
+ });
193
+
194
+ it('overrides root key language', (done) => {
195
+
196
+ Joi.string().options({ language: { root: 'blah' } }).validate(4, (err, value) => {
197
+
198
+ expect(err.message).to.equal('"blah" must be a string');
199
+ done();
200
+ });
201
+ });
202
+
203
+ it('overrides label key language', (done) => {
204
+
205
+ Joi.string().options({ language: { key: 'my own {{!key}} ' } }).validate(4, (err, value) => {
206
+
207
+ expect(err.message).to.equal('my own value must be a string');
208
+ done();
209
+ });
210
+ });
211
+
212
+ it('overrides wrapArrays', (done) => {
213
+
214
+ Joi.array().items(Joi.boolean()).options({ language: { messages: { wrapArrays: false } } }).validate([4], (err, value) => {
215
+
216
+ expect(err.message).to.equal('"value" at position 0 fails because "0" must be a boolean');
217
+ done();
218
+ });
219
+ });
220
+
221
+ it('allows html escaping', (done) => {
222
+
223
+ Joi.string().options({ language: { root: 'blah', label: 'bleh' } }).validate(4, (err, value) => {
224
+
225
+ expect(err.message).to.equal('"bleh" must be a string');
226
+ done();
227
+ });
228
+ });
229
+
230
+ it('provides context with the error', (done) => {
231
+
232
+ Joi.object({ length: Joi.number().min(3).required() }).validate({ length: 1 }, (err) => {
233
+
234
+ expect(err.details).to.deep.equal([{
235
+ message: '"length" must be larger than or equal to 3',
236
+ path: 'length',
237
+ type: 'number.min',
238
+ context: {
239
+ limit: 3,
240
+ key: 'length',
241
+ value: 1
242
+ }
243
+ }]);
244
+ done();
245
+ });
246
+ });
247
+
248
+ it('has a name that is ValidationError', (done) => {
249
+
250
+ const schema = Joi.number();
251
+ schema.validate('a', (validateErr) => {
252
+
253
+ expect(validateErr).to.exist();
254
+ expect(validateErr.name).to.be.equal('ValidationError');
255
+
256
+ try {
257
+ Joi.assert('a', schema);
258
+ throw new Error('should not reach that');
259
+ }
260
+ catch (assertErr) {
261
+ expect(assertErr.name).to.be.equal('ValidationError');
262
+ }
263
+
264
+ try {
265
+ Joi.assert('a', schema, 'foo');
266
+ throw new Error('should not reach that');
267
+ }
268
+ catch (assertErr) {
269
+ expect(assertErr.name).to.be.equal('ValidationError');
270
+ }
271
+
272
+ try {
273
+ Joi.assert('a', schema, new Error('foo'));
274
+ throw new Error('should not reach that');
275
+ }
276
+ catch (assertErr) {
277
+ expect(assertErr.name).to.equal('Error');
278
+ done();
279
+ }
280
+ });
281
+ });
282
+
283
+ describe('annotate()', () => {
284
+
285
+ it('annotates error', (done) => {
286
+
287
+ const object = {
288
+ a: 'm',
289
+ y: {
290
+ b: {
291
+ c: 10
292
+ }
293
+ }
294
+ };
295
+
296
+ const schema = {
297
+ a: Joi.string().valid('a', 'b', 'c', 'd'),
298
+ y: Joi.object({
299
+ u: Joi.string().valid(['e', 'f', 'g', 'h']).required(),
300
+ b: Joi.string().valid('i', 'j').allow(false),
301
+ d: Joi.object({
302
+ x: Joi.string().valid('k', 'l').required(),
303
+ c: Joi.number()
304
+ })
305
+ })
306
+ };
307
+
308
+ Joi.validate(object, schema, { abortEarly: false }, (err, value) => {
309
+
310
+ expect(err).to.exist();
311
+ expect(err.annotate()).to.equal('{\n \"y\": {\n \"b\" \u001b[31m[1]\u001b[0m: {\n \"c\": 10\n },\n \u001b[41m\"u\"\u001b[0m\u001b[31m [2]: -- missing --\u001b[0m\n },\n "a" \u001b[31m[3]\u001b[0m: \"m\"\n}\n\u001b[31m\n[1] "a" must be one of [a, b, c, d]\n[2] "u" is required\n[3] "b" must be a string\u001b[0m');
312
+ done();
313
+ });
314
+ });
315
+
316
+ it('annotates error within array', (done) => {
317
+
318
+ const object = {
319
+ a: [1, 2, 3, 4, 2, 5]
320
+ };
321
+
322
+ const schema = {
323
+ a: Joi.array().items(Joi.valid(1, 2))
324
+ };
325
+
326
+ Joi.validate(object, schema, { abortEarly: false }, (err, value) => {
327
+
328
+ expect(err).to.exist();
329
+ expect(err.annotate()).to.equal('{\n \"a\": [\n 1,\n 2,\n 3, \u001b[31m[1]\u001b[0m\n 4, \u001b[31m[2]\u001b[0m\n 2,\n 5 \u001b[31m[3]\u001b[0m\n ]\n}\n\u001b[31m\n[1] \"2\" must be one of [1, 2]\n[2] \"3\" must be one of [1, 2]\n[3] \"5\" must be one of [1, 2]\u001b[0m');
330
+ done();
331
+ });
332
+ });
333
+
334
+ it('annotates error within array multiple times on the same element', (done) => {
335
+
336
+ const object = {
337
+ a: [2, 3, 4]
338
+ };
339
+
340
+ const schema = {
341
+ a: Joi.array().items(Joi.number().min(4).max(2))
342
+ };
343
+
344
+ Joi.validate(object, schema, { abortEarly: false }, (err, value) => {
345
+
346
+ expect(err).to.exist();
347
+ expect(err.annotate()).to.equal('{\n \"a\": [\n 2, \u001b[31m[1]\u001b[0m\n 3, \u001b[31m[3, 2]\u001b[0m\n 4 \u001b[31m[4]\u001b[0m\n ]\n}\n\u001b[31m\n[1] \"0\" must be larger than or equal to 4\n[2] \"1\" must be larger than or equal to 4\n[3] \"1\" must be less than or equal to 2\n[4] \"2\" must be less than or equal to 2\u001b[0m');
348
+ done();
349
+ });
350
+ });
351
+
352
+ it('annotates error within array when it is an object', (done) => {
353
+
354
+ const object = {
355
+ a: [{ b: 2 }]
356
+ };
357
+
358
+ const schema = {
359
+ a: Joi.array().items(Joi.number())
360
+ };
361
+
362
+ Joi.validate(object, schema, { abortEarly: false }, (err, value) => {
363
+
364
+ expect(err).to.exist();
365
+ expect(err.annotate()).to.equal('{\n \"a\": [\n { \u001b[31m[1]\u001b[0m\n \"b\": 2\n }\n ]\n}\n\u001b[31m\n[1] \"0\" must be a number\u001b[0m');
366
+ done();
367
+ });
368
+ });
369
+
370
+ it('annotates error within multiple arrays and multiple times on the same element', (done) => {
371
+
372
+ const object = {
373
+ a: [2, 3, 4],
374
+ b: [2, 3, 4]
375
+ };
376
+
377
+ const schema = {
378
+ a: Joi.array().items(Joi.number().min(4).max(2)),
379
+ b: Joi.array().items(Joi.number().min(4).max(2))
380
+ };
381
+
382
+ Joi.validate(object, schema, { abortEarly: false }, (err, value) => {
383
+
384
+ expect(err).to.exist();
385
+ expect(err.annotate()).to.equal('{\n \"a\": [\n 2, \u001b[31m[1]\u001b[0m\n 3, \u001b[31m[3, 2]\u001b[0m\n 4 \u001b[31m[4]\u001b[0m\n ],\n \"b\": [\n 2, \u001b[31m[5]\u001b[0m\n 3, \u001b[31m[7, 6]\u001b[0m\n 4 \u001b[31m[8]\u001b[0m\n ]\n}\n\u001b[31m\n[1] \"0\" must be larger than or equal to 4\n[2] \"1\" must be larger than or equal to 4\n[3] \"1\" must be less than or equal to 2\n[4] \"2\" must be less than or equal to 2\n[5] \"0\" must be larger than or equal to 4\n[6] \"1\" must be larger than or equal to 4\n[7] \"1\" must be less than or equal to 2\n[8] \"2\" must be less than or equal to 2\u001b[0m');
386
+ done();
387
+ });
388
+ });
389
+
390
+ it('displays alternatives fail as a single line', (done) => {
391
+
392
+ const schema = {
393
+ x: [
394
+ Joi.string(),
395
+ Joi.number(),
396
+ Joi.date()
397
+ ]
398
+ };
399
+
400
+ Joi.validate({ x: true }, schema, (err, value) => {
401
+
402
+ expect(err).to.exist();
403
+ expect(err.annotate()).to.equal('{\n \"x\" \u001b[31m[1, 2, 3]\u001b[0m: true\n}\n\u001b[31m\n[1] "x" must be a string\n[2] "x" must be a number\n[3] "x" must be a number of milliseconds or valid date string\u001b[0m');
404
+ done();
405
+ });
406
+ });
407
+
408
+ it('annotates circular input', (done) => {
409
+
410
+ const schema = {
411
+ x: Joi.object({
412
+ y: Joi.object({
413
+ z: Joi.number()
414
+ })
415
+ })
416
+ };
417
+
418
+ const input = {};
419
+ input.x = input;
420
+
421
+ Joi.validate(input, schema, (err, value) => {
422
+
423
+ expect(err).to.exist();
424
+ expect(err.annotate()).to.equal('{\n \"x\" \u001b[31m[1]\u001b[0m: \"[Circular ~]\"\n}\n\u001b[31m\n[1] \"x\" is not allowed\u001b[0m');
425
+ done();
426
+ });
427
+ });
428
+
429
+ it('annotates deep circular input', (done) => {
430
+
431
+ const schema = {
432
+ x: Joi.object({
433
+ y: Joi.object({
434
+ z: Joi.number()
435
+ })
436
+ })
437
+ };
438
+
439
+ const input = { x: { y: {} } };
440
+ input.x.y.z = input.x.y;
441
+
442
+ Joi.validate(input, schema, (err, value) => {
443
+
444
+ expect(err).to.exist();
445
+ expect(err.annotate()).to.equal('{\n \"x\": {\n \"y\": {\n \"z\" \u001b[31m[1]\u001b[0m: \"[Circular ~.x.y]\"\n }\n }\n}\n\u001b[31m\n[1] \"z\" must be a number\u001b[0m');
446
+ done();
447
+ });
448
+ });
449
+
450
+ it('annotates deep circular input with extra keys', (done) => {
451
+
452
+ const schema = {
453
+ x: Joi.object({
454
+ y: Joi.object({
455
+ z: Joi.number()
456
+ })
457
+ })
458
+ };
459
+
460
+ const input = { x: { y: { z: 1, foo: {} } } };
461
+ input.x.y.foo = input.x.y;
462
+
463
+ Joi.validate(input, schema, (err, value) => {
464
+
465
+ expect(err).to.exist();
466
+ expect(err.annotate()).to.equal('{\n \"x\": {\n \"y\": {\n \"z\": 1,\n \"foo\" \u001b[31m[1]\u001b[0m: \"[Circular ~.x.y]\"\n }\n }\n}\n\u001b[31m\n[1] \"foo\" is not allowed\u001b[0m');
467
+ done();
468
+ });
469
+ });
470
+
471
+ it('prints NaN, Infinity and -Infinity correctly in errors', (done) => {
472
+
473
+ const schema = {
474
+ x: Joi.object({
475
+ y: Joi.date().allow(null),
476
+ z: Joi.date().allow(null),
477
+ u: Joi.date().allow(null),
478
+ g: Joi.date().allow(null),
479
+ h: Joi.date().allow(null),
480
+ i: Joi.date().allow(null),
481
+ k: Joi.date().allow(null),
482
+ p: Joi.date().allow(null),
483
+ f: Joi.date().allow(null)
484
+ })
485
+ };
486
+
487
+ const input = {
488
+ x: {
489
+ y: NaN,
490
+ z: Infinity,
491
+ u: -Infinity,
492
+ g: Symbol('foo'),
493
+ h: -Infinity,
494
+ i: Infinity,
495
+ k: (a) => a,
496
+ p: Symbol('bar'),
497
+ f: function (x) {
498
+
499
+ return [{ y: 2 }];
500
+ }
501
+ }
502
+ };
503
+
504
+ Joi.validate(input, schema, (err, value) => {
505
+
506
+ expect(err).to.exist();
507
+ expect(err.annotate()).to.equal('{\n \"x\": {\n \"z\": Infinity,\n \"u\": -Infinity,\n \"g\": Symbol(foo),\n \"h\": -Infinity,\n \"i\": Infinity,\n \"k\": (a) => a,\n \"p\": Symbol(bar),\n \"f\": function (x) {\\n\\n return [{ y: 2 }];\\n },\n \"y\" \u001b[31m[1]\u001b[0m: NaN\n }\n}\n\u001b[31m\n[1] \"y\" must be a number of milliseconds or valid date string\u001b[0m');
508
+ done();
509
+ });
510
+ });
511
+ });
512
+ });