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/object.js ADDED
@@ -0,0 +1,1318 @@
1
+ 'use strict';
2
+
3
+ // Load modules
4
+
5
+ const Lab = require('lab');
6
+ const Code = require('code');
7
+ const Joi = require('../lib');
8
+ const Helper = require('./helper');
9
+
10
+
11
+ // Declare internals
12
+
13
+ const internals = {};
14
+
15
+
16
+ // Test shortcuts
17
+
18
+ const lab = exports.lab = Lab.script();
19
+ const describe = lab.describe;
20
+ const it = lab.it;
21
+ const expect = Code.expect;
22
+
23
+
24
+ describe('object', () => {
25
+
26
+ it('converts a json string to an object', (done) => {
27
+
28
+ Joi.object().validate('{"hi":true}', (err, value) => {
29
+
30
+ expect(err).to.not.exist();
31
+ expect(value.hi).to.equal(true);
32
+ done();
33
+ });
34
+ });
35
+
36
+ it('errors on non-object string', (done) => {
37
+
38
+ Joi.object().validate('a string', (err, value) => {
39
+
40
+ expect(err).to.exist();
41
+ expect(value).to.equal('a string');
42
+ done();
43
+ });
44
+ });
45
+
46
+ it('validates an object', (done) => {
47
+
48
+ const schema = Joi.object().required();
49
+ Helper.validate(schema, [
50
+ [{}, true],
51
+ [{ hi: true }, true],
52
+ ['', false]
53
+ ], done);
54
+ });
55
+
56
+ it('return object reference when no rules specified', (done) => {
57
+
58
+ const schema = Joi.object({
59
+ a: Joi.object()
60
+ });
61
+
62
+ const item = { x: 5 };
63
+ schema.validate({ a: item }, (err, value) => {
64
+
65
+ expect(err).not.to.exist();
66
+ expect(value.a).to.equal(item);
67
+ done();
68
+ });
69
+ });
70
+
71
+ it('retains ignored values', (done) => {
72
+
73
+ const schema = Joi.object();
74
+ schema.validate({ a: 5 }, (err, value) => {
75
+
76
+ expect(err).not.to.exist();
77
+ expect(value.a).to.equal(5);
78
+ done();
79
+ });
80
+ });
81
+
82
+ it('retains skipped values', (done) => {
83
+
84
+ const schema = Joi.object({ b: 5 }).unknown(true);
85
+ schema.validate({ b: '5', a: 5 }, (err, value) => {
86
+
87
+ expect(err).not.to.exist();
88
+ expect(value.a).to.equal(5);
89
+ expect(value.b).to.equal(5);
90
+ done();
91
+ });
92
+ });
93
+
94
+ it('allows any key when schema is undefined', (done) => {
95
+
96
+ Joi.object().validate({ a: 4 }, (err, value) => {
97
+
98
+ expect(err).to.not.exist();
99
+
100
+ Joi.object(undefined).validate({ a: 4 }, (err2, value2) => {
101
+
102
+ expect(err2).to.not.exist();
103
+ done();
104
+ });
105
+ });
106
+ });
107
+
108
+ it('allows any key when schema is null', (done) => {
109
+
110
+ Joi.object(null).validate({ a: 4 }, (err, value) => {
111
+
112
+ expect(err).to.not.exist();
113
+ done();
114
+ });
115
+ });
116
+
117
+ it('throws on invalid object schema', (done) => {
118
+
119
+ expect(() => {
120
+
121
+ Joi.object(4);
122
+ }).to.throw('Object schema must be a valid object');
123
+ done();
124
+ });
125
+
126
+ it('throws on joi object schema', (done) => {
127
+
128
+ expect(() => {
129
+
130
+ Joi.object(Joi.object());
131
+ }).to.throw('Object schema cannot be a joi schema');
132
+ done();
133
+ });
134
+
135
+ it('skips conversion when value is undefined', (done) => {
136
+
137
+ Joi.object({ a: Joi.object() }).validate(undefined, (err, value) => {
138
+
139
+ expect(err).to.not.exist();
140
+ expect(value).to.not.exist();
141
+ done();
142
+ });
143
+ });
144
+
145
+ it('errors on array', (done) => {
146
+
147
+ Joi.object().validate([1, 2, 3], (err, value) => {
148
+
149
+ expect(err).to.exist();
150
+ done();
151
+ });
152
+ });
153
+
154
+ it('should prevent extra keys from existing by default', (done) => {
155
+
156
+ const schema = Joi.object({ item: Joi.string().required() }).required();
157
+ Helper.validate(schema, [
158
+ [{ item: 'something' }, true],
159
+ [{ item: 'something', item2: 'something else' }, false],
160
+ ['', false]
161
+ ], done);
162
+ });
163
+
164
+ it('should validate count when min is set', (done) => {
165
+
166
+ const schema = Joi.object().min(3);
167
+ Helper.validate(schema, [
168
+ [{ item: 'something' }, false],
169
+ [{ item: 'something', item2: 'something else' }, false],
170
+ [{ item: 'something', item2: 'something else', item3: 'something something else' }, true],
171
+ ['', false]
172
+ ], done);
173
+ });
174
+
175
+ it('should validate count when max is set', (done) => {
176
+
177
+ const schema = Joi.object().max(2);
178
+ Helper.validate(schema, [
179
+ [{ item: 'something' }, true],
180
+ [{ item: 'something', item2: 'something else' }, true],
181
+ [{ item: 'something', item2: 'something else', item3: 'something something else' }, false],
182
+ ['', false]
183
+ ], done);
184
+ });
185
+
186
+ it('should validate count when min and max is set', (done) => {
187
+
188
+ const schema = Joi.object().max(3).min(2);
189
+ Helper.validate(schema, [
190
+ [{ item: 'something' }, false],
191
+ [{ item: 'something', item2: 'something else' }, true],
192
+ [{ item: 'something', item2: 'something else', item3: 'something something else' }, true],
193
+ [{ item: 'something', item2: 'something else', item3: 'something something else', item4: 'item4' }, false],
194
+ ['', false]
195
+ ], done);
196
+ });
197
+
198
+ it('should validate count when length is set', (done) => {
199
+
200
+ const schema = Joi.object().length(2);
201
+ Helper.validate(schema, [
202
+ [{ item: 'something' }, false],
203
+ [{ item: 'something', item2: 'something else' }, true],
204
+ [{ item: 'something', item2: 'something else', item3: 'something something else' }, false],
205
+ ['', false]
206
+ ], done);
207
+ });
208
+
209
+ it('should validate constructor when type is set', (done) => {
210
+
211
+ const schema = Joi.object().type(RegExp);
212
+ Helper.validate(schema, [
213
+ [{ item: 'something' }, false],
214
+ ['', false],
215
+ [new Date(), false],
216
+ [/abcd/, true],
217
+ [new RegExp(), true]
218
+ ], done);
219
+ });
220
+
221
+ it('should traverse an object and validate all properties in the top level', (done) => {
222
+
223
+ const schema = Joi.object({
224
+ num: Joi.number()
225
+ });
226
+
227
+ Helper.validate(schema, [
228
+ [{ num: 1 }, true],
229
+ [{ num: [1, 2, 3] }, false]
230
+ ], done);
231
+ });
232
+
233
+ it('should traverse an object and child objects and validate all properties', (done) => {
234
+
235
+ const schema = Joi.object({
236
+ num: Joi.number(),
237
+ obj: Joi.object({
238
+ item: Joi.string()
239
+ })
240
+ });
241
+
242
+ Helper.validate(schema, [
243
+ [{ num: 1 }, true],
244
+ [{ num: [1, 2, 3] }, false],
245
+ [{ num: 1, obj: { item: 'something' } }, true],
246
+ [{ num: 1, obj: { item: 123 } }, false]
247
+ ], done);
248
+ });
249
+
250
+ it('should traverse an object several levels', (done) => {
251
+
252
+ const schema = Joi.object({
253
+ obj: Joi.object({
254
+ obj: Joi.object({
255
+ obj: Joi.object({
256
+ item: Joi.boolean()
257
+ })
258
+ })
259
+ })
260
+ });
261
+
262
+ Helper.validate(schema, [
263
+ [{ num: 1 }, false],
264
+ [{ obj: {} }, true],
265
+ [{ obj: { obj: {} } }, true],
266
+ [{ obj: { obj: { obj: {} } } }, true],
267
+ [{ obj: { obj: { obj: { item: true } } } }, true],
268
+ [{ obj: { obj: { obj: { item: 10 } } } }, false]
269
+ ], done);
270
+ });
271
+
272
+ it('should traverse an object several levels with required levels', (done) => {
273
+
274
+ const schema = Joi.object({
275
+ obj: Joi.object({
276
+ obj: Joi.object({
277
+ obj: Joi.object({
278
+ item: Joi.boolean()
279
+ })
280
+ }).required()
281
+ })
282
+ });
283
+
284
+ Helper.validate(schema, [
285
+ [null, false],
286
+ [undefined, true],
287
+ [{}, true],
288
+ [{ obj: {} }, false],
289
+ [{ obj: { obj: {} } }, true],
290
+ [{ obj: { obj: { obj: {} } } }, true],
291
+ [{ obj: { obj: { obj: { item: true } } } }, true],
292
+ [{ obj: { obj: { obj: { item: 10 } } } }, false]
293
+ ], done);
294
+ });
295
+
296
+ it('should traverse an object several levels with required levels (without Joi.obj())', (done) => {
297
+
298
+ const schema = {
299
+ obj: {
300
+ obj: {
301
+ obj: {
302
+ item: Joi.boolean().required()
303
+ }
304
+ }
305
+ }
306
+ };
307
+
308
+ Helper.validate(schema, [
309
+ [null, false],
310
+ [undefined, true],
311
+ [{}, true],
312
+ [{ obj: {} }, true],
313
+ [{ obj: { obj: {} } }, true],
314
+ [{ obj: { obj: { obj: {} } } }, false],
315
+ [{ obj: { obj: { obj: { item: true } } } }, true],
316
+ [{ obj: { obj: { obj: { item: 10 } } } }, false]
317
+ ], done);
318
+ });
319
+
320
+ it('errors on unknown keys when functions allows', (done) => {
321
+
322
+ const schema = Joi.object({ a: Joi.number() }).options({ skipFunctions: true });
323
+ const obj = { a: 5, b: 'value' };
324
+ schema.validate(obj, (err, value) => {
325
+
326
+ expect(err).to.exist();
327
+ done();
328
+ });
329
+ });
330
+
331
+ it('validates both valid() and with()', (done) => {
332
+
333
+ const schema = Joi.object({
334
+ first: Joi.valid('value'),
335
+ second: Joi.any()
336
+ }).with('first', 'second');
337
+
338
+ Helper.validate(schema, [
339
+ [{ first: 'value' }, false]
340
+ ], done);
341
+ });
342
+
343
+ it('validates referenced arrays in valid()', (done) => {
344
+
345
+ const schema = Joi.object({
346
+ foo: Joi.valid(Joi.ref('$x'))
347
+ });
348
+
349
+ Helper.validate(schema, [
350
+ [{ foo: 'bar' }, true, { context: { x: 'bar' } }],
351
+ [{ foo: 'bar' }, true, { context: { x: ['baz', 'bar'] } }],
352
+ [{ foo: 'bar' }, false, { context: { x: 'baz' } }],
353
+ [{ foo: 'bar' }, false, { context: { x: ['baz', 'qux'] } }],
354
+ [{ foo: 'bar' }, false]
355
+ ], done);
356
+ });
357
+
358
+ it('errors on unknown nested keys with the correct path', (done) => {
359
+
360
+ const schema = Joi.object({ a: Joi.object().keys({}) });
361
+ const obj = { a: { b: 'value' } };
362
+ schema.validate(obj, (err, value) => {
363
+
364
+ expect(err).to.exist();
365
+ expect(err.details[0].path).to.equal('a.b');
366
+ done();
367
+ });
368
+ });
369
+
370
+ it('errors on unknown nested keys with the correct path at the root level', (done) => {
371
+
372
+ const schema = Joi.object({ a: Joi.object().keys({}) });
373
+ const obj = { c: 'hello' };
374
+ schema.validate(obj, (err, value) => {
375
+
376
+ expect(err).to.exist();
377
+ expect(err.details[0].path).to.equal('c');
378
+ done();
379
+ });
380
+ });
381
+
382
+ it('should work on prototype-less objects', (done) => {
383
+
384
+ const input = Object.create(null);
385
+ const schema = Joi.object().keys({
386
+ a: Joi.number()
387
+ });
388
+
389
+ input.a = 1337;
390
+
391
+ Joi.validate(input, schema, (err) => {
392
+
393
+ expect(err).to.not.exist();
394
+ done();
395
+ });
396
+ });
397
+
398
+ it('should be able to use rename safely with a fake hasOwnProperty', (done) => {
399
+
400
+ const input = { a: 1, hasOwnProperty: 'foo' };
401
+ const schema = Joi.object().rename('b', 'a');
402
+
403
+ Joi.validate(input, schema, (err) => {
404
+
405
+ expect(err.message).to.equal('"value" cannot rename child "b" because override is disabled and target "a" exists');
406
+ done();
407
+ });
408
+ });
409
+
410
+ it('should be able to use object.with() safely with a fake hasOwnProperty', (done) => {
411
+
412
+ const input = { a: 1, hasOwnProperty: 'foo' };
413
+ const schema = Joi.object({ a: 1 }).with('a', 'b');
414
+
415
+ Joi.validate(input, schema, (err) => {
416
+
417
+ expect(err.message).to.equal('"hasOwnProperty" is not allowed. "a" missing required peer "b"');
418
+ done();
419
+ });
420
+ });
421
+
422
+ describe('keys()', () => {
423
+
424
+ it('allows any key', (done) => {
425
+
426
+ const a = Joi.object({ a: 4 });
427
+ const b = a.keys();
428
+ a.validate({ b: 3 }, (err, value) => {
429
+
430
+ expect(err).to.exist();
431
+ b.validate({ b: 3 }, (err2, value2) => {
432
+
433
+ expect(err2).to.not.exist();
434
+ done();
435
+ });
436
+ });
437
+ });
438
+
439
+ it('forbids all keys', (done) => {
440
+
441
+ const a = Joi.object();
442
+ const b = a.keys({});
443
+ a.validate({ b: 3 }, (err, value) => {
444
+
445
+ expect(err).to.not.exist();
446
+ b.validate({ b: 3 }, (err2, value2) => {
447
+
448
+ expect(err2).to.exist();
449
+ done();
450
+ });
451
+ });
452
+ });
453
+
454
+ it('adds to existing keys', (done) => {
455
+
456
+ const a = Joi.object({ a: 1 });
457
+ const b = a.keys({ b: 2 });
458
+ a.validate({ a: 1, b: 2 }, (err, value) => {
459
+
460
+ expect(err).to.exist();
461
+ b.validate({ a: 1, b: 2 }, (err2, value2) => {
462
+
463
+ expect(err2).to.not.exist();
464
+ done();
465
+ });
466
+ });
467
+ });
468
+
469
+ it('overrides existing keys', (done) => {
470
+
471
+ const a = Joi.object({ a: 1 });
472
+ const b = a.keys({ a: Joi.string() });
473
+
474
+ Helper.validate(a, [
475
+ [{ a: 1 }, true, null, { a: 1 }],
476
+ [{ a: '1' }, true, null, { a: 1 }],
477
+ [{ a: '2' }, false, null, 'child "a" fails because ["a" must be one of [1]]']
478
+ ], () => {
479
+
480
+ Helper.validate(b, [
481
+ [{ a: 1 }, false, null, 'child "a" fails because ["a" must be a string]'],
482
+ [{ a: '1' }, true, null, { a: '1' }]
483
+ ], done);
484
+ });
485
+ });
486
+
487
+ it('strips keys flagged with strip', (done) => {
488
+
489
+ const schema = Joi.object({
490
+ a: Joi.string().strip(),
491
+ b: Joi.string()
492
+ });
493
+ schema.validate({ a: 'test', b: 'test' }, (err, value) => {
494
+
495
+ expect(err).to.not.exist();
496
+ expect(value.a).to.not.exist();
497
+ expect(value.b).to.equal('test');
498
+ done();
499
+ });
500
+ });
501
+
502
+ it('does not alter the original object when stripping keys', (done) => {
503
+
504
+ const schema = Joi.object({
505
+ a: Joi.string().strip(),
506
+ b: Joi.string()
507
+ });
508
+
509
+ const valid = {
510
+ a: 'test',
511
+ b: 'test'
512
+ };
513
+
514
+ schema.validate(valid, (err, value) => {
515
+
516
+ expect(err).to.not.exist();
517
+ expect(value.a).to.not.exist();
518
+ expect(valid.a).to.equal('test');
519
+ expect(value.b).to.equal('test');
520
+ expect(valid.b).to.equal('test');
521
+ done();
522
+ });
523
+ });
524
+
525
+ it('should strip from an alternative', (done) => {
526
+
527
+ const schema = Joi.object({
528
+ a: [Joi.boolean().strip()]
529
+ });
530
+
531
+ const valid = {
532
+ a: true
533
+ };
534
+
535
+ schema.validate(valid, (err, value) => {
536
+
537
+ expect(err).to.not.exist();
538
+ expect(value).to.deep.equal({});
539
+ done();
540
+ });
541
+ });
542
+ });
543
+
544
+ describe('unknown()', () => {
545
+
546
+ it('allows local unknown without applying to children', (done) => {
547
+
548
+ const schema = Joi.object({
549
+ a: {
550
+ b: Joi.number()
551
+ }
552
+ }).unknown();
553
+
554
+ Helper.validate(schema, [
555
+ [{ a: { b: 5 } }, true],
556
+ [{ a: { b: 'x' } }, false],
557
+ [{ a: { b: 5 }, c: 'ignore' }, true],
558
+ [{ a: { b: 5, c: 'ignore' } }, false]
559
+ ], done);
560
+ });
561
+
562
+ it('forbids local unknown without applying to children', (done) => {
563
+
564
+ const schema = Joi.object({
565
+ a: Joi.object({
566
+ b: Joi.number()
567
+ }).unknown()
568
+ }).options({ allowUnknown: false });
569
+
570
+ Helper.validate(schema, [
571
+ [{ a: { b: 5 } }, true],
572
+ [{ a: { b: 'x' } }, false],
573
+ [{ a: { b: 5 }, c: 'ignore' }, false],
574
+ [{ a: { b: 5, c: 'ignore' } }, true]
575
+ ], done);
576
+ });
577
+ });
578
+
579
+ describe('rename()', () => {
580
+
581
+ it('allows renaming multiple times with multiple enabled', (done) => {
582
+
583
+ const schema = Joi.object({
584
+ test: Joi.string()
585
+ }).rename('test1', 'test').rename('test2', 'test', { multiple: true });
586
+
587
+ Joi.compile(schema).validate({ test1: 'a', test2: 'b' }, (err, value) => {
588
+
589
+ expect(err).to.not.exist();
590
+ done();
591
+ });
592
+ });
593
+
594
+ it('errors renaming multiple times with multiple disabled', (done) => {
595
+
596
+ const schema = Joi.object({
597
+ test: Joi.string()
598
+ }).rename('test1', 'test').rename('test2', 'test');
599
+
600
+ Joi.compile(schema).validate({ test1: 'a', test2: 'b' }, (err, value) => {
601
+
602
+ expect(err.message).to.equal('"value" cannot rename child "test2" because multiple renames are disabled and another key was already renamed to "test"');
603
+ done();
604
+ });
605
+ });
606
+
607
+ it('errors multiple times when abortEarly is false', (done) => {
608
+
609
+ Joi.object().rename('a', 'b').rename('c', 'b').rename('d', 'b').options({ abortEarly: false }).validate({ a: 1, c: 1, d: 1 }, (err, value) => {
610
+
611
+ expect(err).to.exist();
612
+ expect(err.message).to.equal('"value" cannot rename child "c" because multiple renames are disabled and another key was already renamed to "b". "value" cannot rename child "d" because multiple renames are disabled and another key was already renamed to "b"');
613
+ done();
614
+ });
615
+ });
616
+
617
+ it('aliases a key', (done) => {
618
+
619
+ const schema = Joi.object({
620
+ a: Joi.number(),
621
+ b: Joi.number()
622
+ }).rename('a', 'b', { alias: true });
623
+
624
+ const obj = { a: 10 };
625
+
626
+ Joi.compile(schema).validate(obj, (err, value) => {
627
+
628
+ expect(err).to.not.exist();
629
+ expect(value.a).to.equal(10);
630
+ expect(value.b).to.equal(10);
631
+ done();
632
+ });
633
+ });
634
+
635
+ it('with override disabled should not allow overwriting existing value', (done) => {
636
+
637
+ const schema = Joi.object({
638
+ test1: Joi.string()
639
+ }).rename('test', 'test1');
640
+
641
+ schema.validate({ test: 'b', test1: 'a' }, (err, value) => {
642
+
643
+ expect(err.message).to.equal('"value" cannot rename child "test" because override is disabled and target "test1" exists');
644
+ done();
645
+ });
646
+ });
647
+
648
+ it('with override enabled should allow overwriting existing value', (done) => {
649
+
650
+ const schema = Joi.object({
651
+ test1: Joi.string()
652
+ }).rename('test', 'test1', { override: true });
653
+
654
+ schema.validate({ test: 'b', test1: 'a' }, (err, value) => {
655
+
656
+ expect(err).to.not.exist();
657
+ done();
658
+ });
659
+ });
660
+
661
+ it('renames when data is nested in an array via items', (done) => {
662
+
663
+ const schema = {
664
+ arr: Joi.array().items(Joi.object({
665
+ one: Joi.string(),
666
+ two: Joi.string()
667
+ }).rename('uno', 'one').rename('dos', 'two'))
668
+ };
669
+
670
+ const data = { arr: [{ uno: '1', dos: '2' }] };
671
+ Joi.object(schema).validate(data, (err, value) => {
672
+
673
+ expect(err).to.not.exist();
674
+ expect(value.arr[0].one).to.equal('1');
675
+ expect(value.arr[0].two).to.equal('2');
676
+ done();
677
+ });
678
+ });
679
+
680
+ it('applies rename and validation in the correct order regardless of key order', (done) => {
681
+
682
+ const schema1 = Joi.object({
683
+ a: Joi.number()
684
+ }).rename('b', 'a');
685
+
686
+ const input1 = { b: '5' };
687
+
688
+ schema1.validate(input1, (err1, value1) => {
689
+
690
+ expect(err1).to.not.exist();
691
+ expect(value1.b).to.not.exist();
692
+ expect(value1.a).to.equal(5);
693
+
694
+ const schema2 = Joi.object({ a: Joi.number(), b: Joi.any() }).rename('b', 'a');
695
+ const input2 = { b: '5' };
696
+
697
+ schema2.validate(input2, (err2, value2) => {
698
+
699
+ expect(err2).to.not.exist();
700
+ expect(value2.b).to.not.exist();
701
+ expect(value2.a).to.equal(5);
702
+
703
+ done();
704
+ });
705
+ });
706
+ });
707
+
708
+ it('sets the default value after key is renamed', (done) => {
709
+
710
+ const schema = Joi.object({
711
+ foo2: Joi.string().default('test')
712
+ }).rename('foo', 'foo2');
713
+
714
+ const input = {};
715
+
716
+ Joi.validate(input, schema, (err, value) => {
717
+
718
+ expect(err).to.not.exist();
719
+ expect(value.foo2).to.equal('test');
720
+
721
+ done();
722
+ });
723
+ });
724
+
725
+ it('should be able to rename keys that are empty strings', (done) => {
726
+
727
+ const schema = Joi.object().rename('', 'notEmpty');
728
+ const input = {
729
+ '': 'something'
730
+ };
731
+
732
+ schema.validate(input, (err, value) => {
733
+
734
+ expect(err).to.not.exist();
735
+ expect(value['']).to.not.exist();
736
+ expect(value.notEmpty).to.equal('something');
737
+ done();
738
+ });
739
+ });
740
+
741
+ it('should not create new keys when they key in question does not exist', (done) => {
742
+
743
+ const schema = Joi.object().rename('b', '_b');
744
+
745
+ const input = {
746
+ a: 'something'
747
+ };
748
+
749
+ schema.validate(input, (err, value) => {
750
+
751
+ expect(err).to.not.exist();
752
+ expect(Object.keys(value)).to.include('a');
753
+ expect(value.a).to.equal('something');
754
+ done();
755
+ });
756
+ });
757
+
758
+ it('should remove a key with override if from does not exist', (done) => {
759
+
760
+ const schema = Joi.object().rename('b', 'a', { override: true });
761
+
762
+ const input = {
763
+ a: 'something'
764
+ };
765
+
766
+ schema.validate(input, (err, value) => {
767
+
768
+ expect(err).to.not.exist();
769
+ expect(value).to.deep.equal({});
770
+ done();
771
+ });
772
+ });
773
+
774
+ it('should ignore a key with ignoredUndefined if from does not exist', (done) => {
775
+
776
+ const schema = Joi.object().rename('b', 'a', { ignoreUndefined: true });
777
+
778
+ const input = {
779
+ a: 'something'
780
+ };
781
+
782
+ schema.validate(input, (err, value) => {
783
+
784
+ expect(err).to.not.exist();
785
+ expect(value).to.deep.equal({ a: 'something' });
786
+ done();
787
+ });
788
+ });
789
+
790
+ it('shouldn\'t delete a key with override and ignoredUndefined if from does not exist', (done) => {
791
+
792
+ const schema = Joi.object().rename('b', 'a', { ignoreUndefined: true, override: true });
793
+
794
+ const input = {
795
+ a: 'something'
796
+ };
797
+
798
+ schema.validate(input, (err, value) => {
799
+
800
+ expect(err).to.not.exist();
801
+ expect(value).to.deep.equal({ a: 'something' });
802
+ done();
803
+ });
804
+ });
805
+ });
806
+
807
+ describe('describe()', () => {
808
+
809
+ it('return empty description when no schema defined', (done) => {
810
+
811
+ const schema = Joi.object();
812
+ const desc = schema.describe();
813
+ expect(desc).to.deep.equal({
814
+ type: 'object'
815
+ });
816
+ done();
817
+ });
818
+
819
+ it('respects the shallow parameter', (done) => {
820
+
821
+ const schema = Joi.object({
822
+ name: Joi.string(),
823
+ child: Joi.object({
824
+ name: Joi.string()
825
+ })
826
+ });
827
+
828
+ expect(Object.keys(schema.describe(true))).to.not.include('children');
829
+ expect(Object.keys(schema.describe())).to.include('children');
830
+
831
+ done();
832
+ });
833
+
834
+ it('describes patterns', (done) => {
835
+
836
+ const schema = Joi.object({
837
+ a: Joi.string()
838
+ }).pattern(/\w\d/i, Joi.boolean());
839
+
840
+ expect(schema.describe()).to.deep.equal({
841
+ type: 'object',
842
+ children: {
843
+ a: {
844
+ type: 'string',
845
+ invalids: ['']
846
+ }
847
+ },
848
+ patterns: [
849
+ {
850
+ regex: '/\\w\\d/i',
851
+ rule: {
852
+ type: 'boolean'
853
+ }
854
+ }
855
+ ]
856
+ });
857
+
858
+ done();
859
+ });
860
+ });
861
+
862
+ describe('length()', () => {
863
+
864
+ it('throws when length is not a number', (done) => {
865
+
866
+ expect(() => {
867
+
868
+ Joi.object().length('a');
869
+ }).to.throw('limit must be a positive integer');
870
+ done();
871
+ });
872
+ });
873
+
874
+ describe('min()', () => {
875
+
876
+ it('throws when limit is not a number', (done) => {
877
+
878
+ expect(() => {
879
+
880
+ Joi.object().min('a');
881
+ }).to.throw('limit must be a positive integer');
882
+ done();
883
+ });
884
+ });
885
+
886
+ describe('max()', () => {
887
+
888
+ it('throws when limit is not a number', (done) => {
889
+
890
+ expect(() => {
891
+
892
+ Joi.object().max('a');
893
+ }).to.throw('limit must be a positive integer');
894
+ done();
895
+ });
896
+ });
897
+
898
+ describe('pattern()', () => {
899
+
900
+ it('shows path to errors in schema', (done) => {
901
+
902
+ expect(() => {
903
+
904
+ Joi.object().pattern(/.*/, {
905
+ a: {
906
+ b: {
907
+ c: {
908
+ d: undefined
909
+ }
910
+ }
911
+ }
912
+ });
913
+ }).to.throw(Error, 'Invalid schema content: (a.b.c.d)');
914
+
915
+ expect(() => {
916
+
917
+ Joi.object().pattern(/.*/, () => {
918
+
919
+ });
920
+ }).to.throw(Error, 'Invalid schema content: ');
921
+
922
+ done();
923
+ });
924
+
925
+ it('validates unknown keys using a pattern', (done) => {
926
+
927
+ const schema = Joi.object({
928
+ a: Joi.number()
929
+ }).pattern(/\d+/, Joi.boolean()).pattern(/\w\w+/, 'x');
930
+
931
+ Joi.validate({ bb: 'y', 5: 'x' }, schema, { abortEarly: false }, (err, value) => {
932
+
933
+ expect(err).to.exist();
934
+ expect(err.message).to.equal('child "5" fails because ["5" must be a boolean]. child "bb" fails because ["bb" must be one of [x]]');
935
+
936
+ Helper.validate(schema, [
937
+ [{ a: 5 }, true],
938
+ [{ a: 'x' }, false],
939
+ [{ b: 'x' }, false],
940
+ [{ bb: 'x' }, true],
941
+ [{ 5: 'x' }, false],
942
+ [{ 5: false }, true],
943
+ [{ 5: undefined }, true]
944
+ ], done);
945
+ });
946
+ });
947
+
948
+ it('validates unknown keys using a pattern (nested)', (done) => {
949
+
950
+ const schema = {
951
+ x: Joi.object({
952
+ a: Joi.number()
953
+ }).pattern(/\d+/, Joi.boolean()).pattern(/\w\w+/, 'x')
954
+ };
955
+
956
+ Joi.validate({ x: { bb: 'y', 5: 'x' } }, schema, { abortEarly: false }, (err, value) => {
957
+
958
+ expect(err).to.exist();
959
+ expect(err.message).to.equal('child "x" fails because [child "5" fails because ["5" must be a boolean], child "bb" fails because ["bb" must be one of [x]]]');
960
+ done();
961
+ });
962
+ });
963
+
964
+ it('errors when using a pattern on empty schema with unknown(false) and pattern mismatch', (done) => {
965
+
966
+ const schema = Joi.object().pattern(/\d/, Joi.number()).unknown(false);
967
+
968
+ Joi.validate({ a: 5 }, schema, { abortEarly: false }, (err, value) => {
969
+
970
+ expect(err).to.exist();
971
+ expect(err.message).to.equal('"a" is not allowed');
972
+ done();
973
+ });
974
+ });
975
+
976
+ it('removes global flag from patterns', (done) => {
977
+
978
+ const schema = Joi.object().pattern(/a/g, Joi.number());
979
+
980
+ Joi.validate({ a1: 5, a2: 6 }, schema, (err, value) => {
981
+
982
+ expect(err).to.not.exist();
983
+ done();
984
+ });
985
+ });
986
+ });
987
+
988
+ describe('with()', () => {
989
+
990
+ it('should throw an error when a parameter is not a string', (done) => {
991
+
992
+ let error;
993
+ try {
994
+ Joi.object().with({});
995
+ error = false;
996
+ }
997
+ catch (e) {
998
+ error = true;
999
+ }
1000
+ expect(error).to.equal(true);
1001
+
1002
+ try {
1003
+ Joi.object().with(123);
1004
+ error = false;
1005
+ }
1006
+ catch (e) {
1007
+ error = true;
1008
+ }
1009
+ expect(error).to.equal(true);
1010
+ done();
1011
+ });
1012
+
1013
+ it('should validate correctly when key is an empty string', (done) => {
1014
+
1015
+ const schema = Joi.object().with('', 'b');
1016
+ Helper.validate(schema, [
1017
+ [{ c: 'hi', d: 'there' }, true]
1018
+ ]);
1019
+ done();
1020
+ });
1021
+ });
1022
+
1023
+ describe('without()', () => {
1024
+
1025
+ it('should throw an error when a parameter is not a string', (done) => {
1026
+
1027
+ let error;
1028
+ try {
1029
+ Joi.object().without({});
1030
+ error = false;
1031
+ }
1032
+ catch (e) {
1033
+ error = true;
1034
+ }
1035
+ expect(error).to.equal(true);
1036
+
1037
+ try {
1038
+ Joi.object().without(123);
1039
+ error = false;
1040
+ }
1041
+ catch (e) {
1042
+ error = true;
1043
+ }
1044
+ expect(error).to.equal(true);
1045
+
1046
+
1047
+ done();
1048
+ });
1049
+
1050
+ it('should validate correctly when key is an empty string', (done) => {
1051
+
1052
+ const schema = Joi.object().without('', 'b');
1053
+ Helper.validate(schema, [
1054
+ [{ a: 'hi', b: 'there' }, true]
1055
+ ]);
1056
+ done();
1057
+ });
1058
+ });
1059
+
1060
+ describe('xor()', () => {
1061
+
1062
+ it('should throw an error when a parameter is not a string', (done) => {
1063
+
1064
+ let error;
1065
+ try {
1066
+ Joi.object().xor({});
1067
+ error = false;
1068
+ }
1069
+ catch (e) {
1070
+ error = true;
1071
+ }
1072
+ expect(error).to.equal(true);
1073
+
1074
+ try {
1075
+ Joi.object().xor(123);
1076
+ error = false;
1077
+ }
1078
+ catch (e) {
1079
+ error = true;
1080
+ }
1081
+ expect(error).to.equal(true);
1082
+ done();
1083
+ });
1084
+ });
1085
+
1086
+ describe('or()', () => {
1087
+
1088
+ it('should throw an error when a parameter is not a string', (done) => {
1089
+
1090
+ let error;
1091
+ try {
1092
+ Joi.object().or({});
1093
+ error = false;
1094
+ }
1095
+ catch (e) {
1096
+ error = true;
1097
+ }
1098
+ expect(error).to.equal(true);
1099
+
1100
+ try {
1101
+ Joi.object().or(123);
1102
+ error = false;
1103
+ }
1104
+ catch (e) {
1105
+ error = true;
1106
+ }
1107
+ expect(error).to.equal(true);
1108
+ done();
1109
+ });
1110
+
1111
+ it('errors multiple levels deep', (done) => {
1112
+
1113
+ Joi.object({
1114
+ a: {
1115
+ b: Joi.object().or('x', 'y')
1116
+ }
1117
+ }).validate({ a: { b: { c: 1 } } }, (err, value) => {
1118
+
1119
+ expect(err).to.exist();
1120
+ expect(err.details[0].path).to.equal('a.b');
1121
+ expect(err.message).to.equal('child "a" fails because [child "b" fails because ["value" must contain at least one of [x, y]]]');
1122
+ done();
1123
+ });
1124
+ });
1125
+ });
1126
+
1127
+ describe('assert()', () => {
1128
+
1129
+ it('shows path to errors in schema', (done) => {
1130
+
1131
+ expect(() => {
1132
+
1133
+ Joi.object().assert('a.b', {
1134
+ a: {
1135
+ b: {
1136
+ c: {
1137
+ d: undefined
1138
+ }
1139
+ }
1140
+ }
1141
+ });
1142
+ }).to.throw(Error, 'Invalid schema content: (a.b.c.d)');
1143
+ done();
1144
+ });
1145
+
1146
+ it('shows errors in schema', (done) => {
1147
+
1148
+ expect(() => {
1149
+
1150
+ Joi.object().assert('a.b', undefined);
1151
+ }).to.throw(Error, 'Invalid schema content: ');
1152
+ done();
1153
+ });
1154
+
1155
+ it('validates upwards reference', (done) => {
1156
+
1157
+ const schema = Joi.object({
1158
+ a: {
1159
+ b: Joi.string(),
1160
+ c: Joi.number()
1161
+ },
1162
+ d: {
1163
+ e: Joi.any()
1164
+ }
1165
+ }).assert(Joi.ref('d/e', { separator: '/' }), Joi.ref('a.c'), 'equal to a.c');
1166
+
1167
+ schema.validate({ a: { b: 'x', c: 5 }, d: { e: 6 } }, (err, value) => {
1168
+
1169
+ expect(err).to.exist();
1170
+ expect(err.message).to.equal('"d.e" validation failed because "d.e" failed to equal to a.c');
1171
+
1172
+ Helper.validate(schema, [
1173
+ [{ a: { b: 'x', c: 5 }, d: { e: 5 } }, true]
1174
+ ], done);
1175
+ });
1176
+ });
1177
+
1178
+ it('validates upwards reference with implicit context', (done) => {
1179
+
1180
+ const schema = Joi.object({
1181
+ a: {
1182
+ b: Joi.string(),
1183
+ c: Joi.number()
1184
+ },
1185
+ d: {
1186
+ e: Joi.any()
1187
+ }
1188
+ }).assert('d.e', Joi.ref('a.c'), 'equal to a.c');
1189
+
1190
+ schema.validate({ a: { b: 'x', c: 5 }, d: { e: 6 } }, (err, value) => {
1191
+
1192
+ expect(err).to.exist();
1193
+ expect(err.message).to.equal('"d.e" validation failed because "d.e" failed to equal to a.c');
1194
+
1195
+ Helper.validate(schema, [
1196
+ [{ a: { b: 'x', c: 5 }, d: { e: 5 } }, true]
1197
+ ], done);
1198
+ });
1199
+ });
1200
+
1201
+ it('throws when context is at root level', (done) => {
1202
+
1203
+ expect(() => {
1204
+
1205
+ Joi.object({
1206
+ a: {
1207
+ b: Joi.string(),
1208
+ c: Joi.number()
1209
+ },
1210
+ d: {
1211
+ e: Joi.any()
1212
+ }
1213
+ }).assert('a', Joi.ref('d.e'), 'equal to d.e');
1214
+ }).to.throw('Cannot use assertions for root level references - use direct key rules instead');
1215
+ done();
1216
+ });
1217
+
1218
+ it('allows root level context ref', (done) => {
1219
+
1220
+ expect(() => {
1221
+
1222
+ Joi.object({
1223
+ a: {
1224
+ b: Joi.string(),
1225
+ c: Joi.number()
1226
+ },
1227
+ d: {
1228
+ e: Joi.any()
1229
+ }
1230
+ }).assert('$a', Joi.ref('d.e'), 'equal to d.e');
1231
+ }).to.not.throw();
1232
+ done();
1233
+ });
1234
+
1235
+ it('provides a default message for failed assertions', (done) => {
1236
+
1237
+ const schema = Joi.object({
1238
+ a: {
1239
+ b: Joi.string(),
1240
+ c: Joi.number()
1241
+ },
1242
+ d: {
1243
+ e: Joi.any()
1244
+ }
1245
+ }).assert('d.e', Joi.boolean());
1246
+
1247
+ schema.validate({
1248
+ d: {
1249
+ e: []
1250
+ }
1251
+ }, (err) => {
1252
+
1253
+ expect(err).to.exist();
1254
+ expect(err.message).to.equal('"d.e" validation failed because "d.e" failed to pass the assertion test');
1255
+ done();
1256
+ });
1257
+ });
1258
+ });
1259
+
1260
+ describe('type()', () => {
1261
+
1262
+ it('uses constructor name for default type name', (done) => {
1263
+
1264
+ const Foo = function Foo() { };
1265
+
1266
+ const schema = Joi.object().type(Foo);
1267
+ schema.validate({}, (err) => {
1268
+
1269
+ expect(err).to.exist();
1270
+ expect(err.message).to.equal('"value" must be an instance of "Foo"');
1271
+ done();
1272
+ });
1273
+ });
1274
+
1275
+ it('uses custom type name if supplied', (done) => {
1276
+
1277
+ const Foo = function () { };
1278
+
1279
+ const schema = Joi.object().type(Foo, 'Bar');
1280
+ schema.validate({}, (err) => {
1281
+
1282
+ expect(err).to.exist();
1283
+ expect(err.message).to.equal('"value" must be an instance of "Bar"');
1284
+ done();
1285
+ });
1286
+ });
1287
+
1288
+ it('overrides constructor name with custom name', (done) => {
1289
+
1290
+ const Foo = function Foo() { };
1291
+
1292
+ const schema = Joi.object().type(Foo, 'Bar');
1293
+ schema.validate({}, (err) => {
1294
+
1295
+ expect(err).to.exist();
1296
+ expect(err.message).to.equal('"value" must be an instance of "Bar"');
1297
+ done();
1298
+ });
1299
+ });
1300
+
1301
+ it('throws when constructor is not a function', (done) => {
1302
+
1303
+ expect(() => {
1304
+
1305
+ Joi.object().type('');
1306
+ }).to.throw('type must be a constructor function');
1307
+ done();
1308
+ });
1309
+
1310
+ it('uses the constructor name in the schema description', (done) => {
1311
+
1312
+ const description = Joi.object().type(RegExp).describe();
1313
+
1314
+ expect(description.rules).to.deep.include({ name: 'type', arg: 'RegExp' });
1315
+ done();
1316
+ });
1317
+ });
1318
+ });