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/any.js ADDED
@@ -0,0 +1,1751 @@
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('any', () => {
25
+
26
+ describe('equal()', () => {
27
+
28
+ it('validates valid values', (done) => {
29
+
30
+ Helper.validate(Joi.equal(4), [
31
+ [4, true],
32
+ [5, false]
33
+ ], done);
34
+ });
35
+ });
36
+
37
+ describe('not()', () => {
38
+
39
+ it('validates invalid values', (done) => {
40
+
41
+ Helper.validate(Joi.not(5), [
42
+ [4, true],
43
+ [5, false]
44
+ ], done);
45
+ });
46
+ });
47
+
48
+ describe('exist()', () => {
49
+
50
+ it('validates required values', (done) => {
51
+
52
+ Helper.validate(Joi.exist(), [
53
+ [4, true],
54
+ [undefined, false]
55
+ ], done);
56
+ });
57
+ });
58
+
59
+ describe('strict()', () => {
60
+
61
+ it('validates without converting', (done) => {
62
+
63
+ const schema = Joi.object({
64
+ array: Joi.array().items(Joi.string().min(5), Joi.number().min(3))
65
+ }).strict();
66
+
67
+ Helper.validate(schema, [
68
+ [{ array: ['12345'] }, true],
69
+ [{ array: ['1'] }, false],
70
+ [{ array: [3] }, true],
71
+ [{ array: ['12345', 3] }, true],
72
+ [{ array: ['3'] }, false],
73
+ [{ array: [1] }, false]
74
+ ], done);
75
+ });
76
+
77
+ it('can be disabled', (done) => {
78
+
79
+ const schema = Joi.object({
80
+ array: Joi.array().items(Joi.string().min(5), Joi.number().min(3))
81
+ }).strict().strict(false);
82
+
83
+ Helper.validate(schema, [
84
+ [{ array: ['12345'] }, true],
85
+ [{ array: ['1'] }, false],
86
+ [{ array: [3] }, true],
87
+ [{ array: ['12345', 3] }, true],
88
+ [{ array: ['3'] }, true],
89
+ [{ array: [1] }, false]
90
+ ], done);
91
+ });
92
+ });
93
+
94
+ describe('options()', () => {
95
+
96
+ it('adds to existing options', (done) => {
97
+
98
+ const schema = Joi.object({ b: Joi.number().strict().options({ convert: true }) });
99
+ const input = { b: '2' };
100
+ schema.validate(input, (err, value) => {
101
+
102
+ expect(err).to.not.exist();
103
+ expect(value.b).to.equal(2);
104
+ done();
105
+ });
106
+ });
107
+
108
+ it('throws with an invalid option', (done) => {
109
+
110
+ expect(() => {
111
+
112
+ Joi.any().options({ foo: 'bar' });
113
+ }).to.throw('unknown key foo');
114
+ done();
115
+ });
116
+
117
+ it('throws with an invalid option type', (done) => {
118
+
119
+ expect(() => {
120
+
121
+ Joi.any().options({ convert: 'yes' });
122
+ }).to.throw('convert should be of type boolean');
123
+ done();
124
+ });
125
+
126
+ it('throws with an invalid option value', (done) => {
127
+
128
+ expect(() => {
129
+
130
+ Joi.any().options({ presence: 'yes' });
131
+ }).to.throw('presence should be one of required, optional, forbidden, ignore');
132
+ done();
133
+ });
134
+
135
+ it('does not throw with multiple options including presence key', (done) => {
136
+
137
+ expect(() => {
138
+
139
+ Joi.any().options({ presence: 'optional', raw: true });
140
+ }).to.not.throw();
141
+ done();
142
+ });
143
+ });
144
+
145
+ describe('label()', () => {
146
+
147
+ it('adds to existing options', (done) => {
148
+
149
+ const schema = Joi.object({ b: Joi.string().email().label('Custom label') });
150
+ const input = { b: 'not_a_valid_email' };
151
+ schema.validate(input, (err, value) => {
152
+
153
+ expect(err).to.exist();
154
+ expect(err.details[0].message).to.equal('"Custom label" must be a valid email');
155
+ done();
156
+ });
157
+ });
158
+
159
+ it('throws when label is missing', (done) => {
160
+
161
+ expect(() => {
162
+
163
+ Joi.label();
164
+ }).to.throw('Label name must be a non-empty string');
165
+ done();
166
+ });
167
+
168
+ it('can describe a label', (done) => {
169
+
170
+ const schema = Joi.object().label('lbl').describe();
171
+ expect(schema).to.deep.equal({ type: 'object', label: 'lbl' });
172
+ done();
173
+ });
174
+
175
+ it('does not leak into sub objects', (done) => {
176
+
177
+ const schema = Joi.object({ a: Joi.number() }).label('foo');
178
+ schema.validate({ a: 'a' }, (err, value) => {
179
+
180
+ expect(err).to.exist();
181
+ expect(err.message).to.equal('child "a" fails because ["a" must be a number]');
182
+ done();
183
+ });
184
+ });
185
+
186
+ it('does not leak into sub objects from an array', (done) => {
187
+
188
+ const schema = Joi.array().items(Joi.object({ a: Joi.number() }).label('foo')).label('bar');
189
+ schema.validate([{ a: 'a' }], (err, value) => {
190
+
191
+ expect(err).to.exist();
192
+ expect(err.message).to.equal('"bar" at position 0 fails because [child "a" fails because ["a" must be a number]]');
193
+ done();
194
+ });
195
+ });
196
+
197
+ it('does not leak into unknown keys', (done) => {
198
+
199
+ const schema = Joi.object({ a: Joi.number() }).label('foo');
200
+ schema.validate({ b: 'a' }, (err, value) => {
201
+
202
+ expect(err).to.exist();
203
+ expect(err.message).to.equal('"b" is not allowed');
204
+ done();
205
+ });
206
+ });
207
+ });
208
+
209
+ describe('strict()', () => {
210
+
211
+ it('adds to existing options', (done) => {
212
+
213
+ const schema = Joi.object({ b: Joi.number().options({ convert: true }).strict() });
214
+ const input = { b: '2' };
215
+ schema.validate(input, (err, value) => {
216
+
217
+ expect(err).to.exist();
218
+ expect(value.b).to.equal('2');
219
+ done();
220
+ });
221
+ });
222
+ });
223
+
224
+ describe('raw()', () => {
225
+
226
+ it('gives the raw input', (done) => {
227
+
228
+ const tests = [
229
+ [Joi.array(), '[1,2,3]'],
230
+ [Joi.binary(), 'abc'],
231
+ [Joi.boolean(), 'false'],
232
+ [Joi.date().format('YYYYMMDD'), '19700101'],
233
+ [Joi.number(), '12'],
234
+ [Joi.object(), '{ "a": 1 }'],
235
+ [Joi.any().strict(), 'abc']
236
+ ];
237
+
238
+ tests.forEach((test) => {
239
+
240
+ const baseSchema = test[0];
241
+ const input = test[1];
242
+ const schemas = [
243
+ baseSchema.raw(),
244
+ baseSchema.raw(true),
245
+ baseSchema.options({ raw: true })
246
+ ];
247
+
248
+ schemas.forEach((schema) => {
249
+
250
+ schema.raw().validate(input, (err, value) => {
251
+
252
+ expect(err).to.not.exist();
253
+ expect(value).to.equal(input);
254
+ });
255
+ });
256
+ });
257
+
258
+ done();
259
+ });
260
+ });
261
+
262
+ describe('default()', () => {
263
+
264
+ it('sets the value', (done) => {
265
+
266
+ const schema = Joi.object({ foo: Joi.string().default('test') });
267
+ const input = {};
268
+
269
+ schema.validate(input, (err, value) => {
270
+
271
+ expect(err).to.not.exist();
272
+ expect(value.foo).to.equal('test');
273
+ done();
274
+ });
275
+ });
276
+
277
+ it('throws when value is a method and no description is provided', (done) => {
278
+
279
+ expect(() => {
280
+
281
+ Joi.object({
282
+ foo: Joi.string().default(() => {
283
+
284
+ return 'test';
285
+
286
+ })
287
+ });
288
+ }).to.throw();
289
+
290
+ done();
291
+ });
292
+
293
+ it('allows passing description as a property of a default method', (done) => {
294
+
295
+ const defaultFn = function () {
296
+
297
+ return 'test';
298
+ };
299
+
300
+ defaultFn.description = 'test';
301
+
302
+ expect(() => {
303
+
304
+ Joi.object({ foo: Joi.string().default(defaultFn) });
305
+ }).to.not.throw();
306
+
307
+ done();
308
+ });
309
+
310
+ it('sets the value when passing a method', (done) => {
311
+
312
+ const schema = Joi.object({
313
+ foo: Joi.string().default(() => {
314
+
315
+ return 'test';
316
+ }, 'testing')
317
+ });
318
+ const input = {};
319
+
320
+ schema.validate(input, (err, value) => {
321
+
322
+ expect(err).to.not.exist();
323
+ expect(value.foo).to.equal('test');
324
+ done();
325
+ });
326
+ });
327
+
328
+ it('executes the default method each time validate is called', (done) => {
329
+
330
+ let count = 0;
331
+ const schema = Joi.object({
332
+ foo: Joi.number().default(() => {
333
+
334
+ return ++count;
335
+ }, 'incrementer')
336
+ });
337
+ const input = {};
338
+
339
+ schema.validate(input, (err, value) => {
340
+
341
+ expect(err).to.not.exist();
342
+ expect(value.foo).to.equal(1);
343
+ });
344
+
345
+ schema.validate(input, (err, value) => {
346
+
347
+ expect(err).to.not.exist();
348
+ expect(value.foo).to.equal(2);
349
+ });
350
+
351
+ done();
352
+ });
353
+
354
+ it('passes a clone of the context if the default method accepts an argument', (done) => {
355
+
356
+ const schema = Joi.object({
357
+ foo: Joi.string().default((context) => {
358
+
359
+ return context.bar + 'ing';
360
+ }, 'testing'),
361
+ bar: Joi.string()
362
+ });
363
+ const input = { bar: 'test' };
364
+
365
+ schema.validate(input, (err, value) => {
366
+
367
+ expect(err).to.not.exist();
368
+ expect(value.foo).to.equal('testing');
369
+ done();
370
+ });
371
+ });
372
+
373
+ it('does not modify the original object when modifying the clone in a default method', (done) => {
374
+
375
+ const defaultFn = function (context) {
376
+
377
+ context.bar = 'broken';
378
+ return 'test';
379
+ };
380
+ defaultFn.description = 'testing';
381
+
382
+ const schema = Joi.object({
383
+ foo: Joi.string().default(defaultFn),
384
+ bar: Joi.string()
385
+ });
386
+ const input = { bar: 'test' };
387
+
388
+ schema.validate(input, (err, value) => {
389
+
390
+ expect(err).to.not.exist();
391
+ expect(value.bar).to.equal('test');
392
+ expect(value.foo).to.equal('test');
393
+ done();
394
+ });
395
+ });
396
+
397
+ it('passes undefined as the context if the default method has no parent', (done) => {
398
+
399
+ let c;
400
+ let methodCalled = false;
401
+ const schema = Joi.string().default((context) => {
402
+
403
+ methodCalled = true;
404
+ c = context;
405
+ return 'test';
406
+ }, 'testing');
407
+
408
+ schema.validate(undefined, (err, value) => {
409
+
410
+ expect(err).to.not.exist();
411
+ expect(methodCalled).to.equal(true);
412
+ expect(c).to.equal(undefined);
413
+ expect(value).to.equal('test');
414
+ done();
415
+ });
416
+ });
417
+
418
+ it('allows passing a method with no description to default when the object being validated is a function', (done) => {
419
+
420
+ const defaultFn = function () {
421
+
422
+ return 'just a function';
423
+ };
424
+
425
+ let schema;
426
+ expect(() => {
427
+
428
+ schema = Joi.func().default(defaultFn);
429
+ }).to.not.throw();
430
+
431
+ schema.validate(undefined, (err, value) => {
432
+
433
+ expect(err).to.not.exist();
434
+ expect(value).to.deep.equal(defaultFn);
435
+ done();
436
+ });
437
+ });
438
+
439
+ it('allows passing a method that generates a default method when validating a function', (done) => {
440
+
441
+ const defaultFn = function () {
442
+
443
+ return 'just a function';
444
+ };
445
+
446
+ const defaultGeneratorFn = function () {
447
+
448
+ return defaultFn;
449
+ };
450
+ defaultGeneratorFn.description = 'generate a default fn';
451
+
452
+ let schema;
453
+ expect(() => {
454
+
455
+ schema = Joi.func().default(defaultGeneratorFn);
456
+ }).to.not.throw();
457
+
458
+ schema.validate(undefined, (err, value) => {
459
+
460
+ expect(err).to.not.exist();
461
+ expect(value).to.deep.equal(defaultFn);
462
+ done();
463
+ });
464
+ });
465
+
466
+ it('allows passing a ref as a default without a description', (done) => {
467
+
468
+ const schema = Joi.object({
469
+ a: Joi.string(),
470
+ b: Joi.string().default(Joi.ref('a'))
471
+ });
472
+
473
+ schema.validate({ a: 'test' }, (err, value) => {
474
+
475
+ expect(err).to.not.exist();
476
+ expect(value.a).to.equal('test');
477
+ expect(value.b).to.equal('test');
478
+ done();
479
+ });
480
+ });
481
+
482
+ it('ignores description when passing a ref as a default', (done) => {
483
+
484
+ const schema = Joi.object({
485
+ a: Joi.string(),
486
+ b: Joi.string().default(Joi.ref('a'), 'this is a ref')
487
+ });
488
+
489
+ schema.validate({ a: 'test' }, (err, value) => {
490
+
491
+ expect(err).to.not.exist();
492
+ expect(value.a).to.equal('test');
493
+ expect(value.b).to.equal('test');
494
+ done();
495
+ });
496
+ });
497
+
498
+ it('catches errors in default methods', (done) => {
499
+
500
+ const defaultFn = function () {
501
+
502
+ throw new Error('boom');
503
+ };
504
+ defaultFn.description = 'broken method';
505
+
506
+ const schema = Joi.string().default(defaultFn);
507
+
508
+ schema.validate(undefined, (err, value) => {
509
+
510
+ expect(err).to.exist();
511
+ expect(err.details).to.have.length(1);
512
+ expect(err.details[0].message).to.contain('threw an error when running default method');
513
+ expect(err.details[0].type).to.equal('any.default');
514
+ expect(err.details[0].context).to.be.an.instanceof(Error);
515
+ expect(err.details[0].context.message).to.equal('boom');
516
+ done();
517
+ });
518
+ });
519
+
520
+ it('should not overide a value when value is given', (done) => {
521
+
522
+ const schema = Joi.object({ foo: Joi.string().default('bar') });
523
+ const input = { foo: 'test' };
524
+
525
+ schema.validate(input, (err, value) => {
526
+
527
+ expect(err).to.not.exist();
528
+ expect(value.foo).to.equal('test');
529
+ done();
530
+ });
531
+ });
532
+
533
+ it('sets value based on condition (outer)', (done) => {
534
+
535
+ const schema = Joi.object({
536
+ a: Joi.boolean(),
537
+ b: Joi.boolean().default(false).when('a', { is: true, then: Joi.required(), otherwise: Joi.forbidden() })
538
+ });
539
+
540
+ schema.validate({ a: false }, (err, value) => {
541
+
542
+ expect(err).to.not.exist();
543
+ expect(value.b).to.equal(false);
544
+ done();
545
+ });
546
+ });
547
+
548
+ it('sets value based on condition (inner)', (done) => {
549
+
550
+ const schema = Joi.object({
551
+ a: Joi.boolean(),
552
+ b: Joi.boolean().when('a', { is: true, then: Joi.default(false), otherwise: Joi.forbidden() })
553
+ });
554
+
555
+ schema.validate({ a: true }, (err, value) => {
556
+
557
+ expect(err).to.not.exist();
558
+ expect(value.b).to.equal(false);
559
+ done();
560
+ });
561
+ });
562
+
563
+ it('sets value based on multiple conditions (without otherwise)', (done) => {
564
+
565
+ const schema = Joi.object({
566
+ a: Joi.number().required(),
567
+ b: Joi.number()
568
+ .when('a', { is: 0, then: Joi.valid(1) })
569
+ .when('a', { is: 1, then: Joi.valid(2) })
570
+ .when('a', { is: 2, then: Joi.valid(3) })
571
+ });
572
+
573
+ Helper.validate(schema, [
574
+ [{ a: 0, b: 1 }, true],
575
+ [{ a: 0, b: 2 }, false],
576
+ [{ a: 1, b: 2 }, true],
577
+ [{ a: 1, b: 3 }, false],
578
+ [{ a: 2, b: 3 }, true],
579
+ [{ a: 2, b: 4 }, false],
580
+ [{ a: 42, b: 128 }, true]
581
+ ], done);
582
+ });
583
+
584
+ it('sets value based on multiple conditions (with otherwise)', (done) => {
585
+
586
+ const schema = Joi.object({
587
+ a: Joi.number().required(),
588
+ b: Joi.number()
589
+ .when('a', { is: 0, then: Joi.valid(1) })
590
+ .when('a', { is: 1, otherwise: Joi.valid(2) })
591
+ .when('a', { is: 2, then: Joi.valid(3) })
592
+ });
593
+
594
+ Helper.validate(schema, [
595
+ [{ a: 0, b: 1 }, true],
596
+ [{ a: 0, b: 2 }, false],
597
+ [{ a: 1, b: 2 }, true],
598
+ [{ a: 1, b: 3 }, true],
599
+ [{ a: 2, b: 3 }, false],
600
+ [{ a: 2, b: 2 }, true],
601
+ [{ a: 42, b: 128 }, false],
602
+ [{ a: 42, b: 2 }, true]
603
+ ], done);
604
+ });
605
+
606
+ it('sets value based on multiple conditions (with base rules)', (done) => {
607
+
608
+ const schema = Joi.object({
609
+ a: Joi.number().required(),
610
+ b: Joi.number().valid(10)
611
+ .when('a', { is: 0, then: Joi.valid(1) })
612
+ .when('a', { is: 1, then: Joi.valid(2) })
613
+ .when('a', { is: 2, then: Joi.valid(3) })
614
+ });
615
+
616
+ Helper.validate(schema, [
617
+ [{ a: 0, b: 1 }, true],
618
+ [{ a: 0, b: 2 }, false],
619
+ [{ a: 0, b: 10 }, true],
620
+ [{ a: 1, b: 2 }, true],
621
+ [{ a: 1, b: 3 }, false],
622
+ [{ a: 1, b: 10 }, true],
623
+ [{ a: 2, b: 3 }, true],
624
+ [{ a: 2, b: 4 }, false],
625
+ [{ a: 2, b: 10 }, true],
626
+ [{ a: 42, b: 128 }, false],
627
+ [{ a: 42, b: 10 }, true]
628
+ ], done);
629
+ });
630
+
631
+ it('creates deep defaults', (done) => {
632
+
633
+ const schema = Joi.object({
634
+ a: Joi.number().default(42),
635
+ b: Joi.object({
636
+ c: Joi.boolean().default(true),
637
+ d: Joi.string()
638
+ }).default()
639
+ }).default();
640
+
641
+ Helper.validate(schema, [
642
+ [undefined, true, null, { a: 42, b: { c: true } }],
643
+ [{ a: 24 }, true, null, { a: 24, b: { c: true } }]
644
+ ], done);
645
+ });
646
+
647
+ it('should not affect objects other than object when called without an argument', (done) => {
648
+
649
+ const schema = Joi.object({
650
+ a: Joi.number().default()
651
+ }).default();
652
+
653
+ Helper.validate(schema, [
654
+ [undefined, true, null, {}],
655
+ [{ a: 24 }, true, null, { a: 24 }]
656
+ ], done);
657
+ });
658
+
659
+ it('should set default value as a clone', (done) => {
660
+
661
+ const defaultValue = { bar: 'val' };
662
+ const schema = Joi.object({ foo: Joi.object().default(defaultValue) });
663
+ const input = {};
664
+
665
+ schema.validate(input, (err, value) => {
666
+
667
+ expect(err).to.not.exist();
668
+ expect(value.foo).to.not.equal(defaultValue);
669
+ expect(value.foo).to.only.deep.include({ bar: 'val' });
670
+
671
+ value.foo.bar = 'mutated';
672
+
673
+ schema.validate(input, (err2, value2) => {
674
+
675
+ expect(err2).to.not.exist();
676
+ expect(value2.foo).to.not.equal(defaultValue);
677
+ expect(value2.foo).to.only.deep.include({ bar: 'val' });
678
+
679
+ done();
680
+ });
681
+ });
682
+ });
683
+
684
+ it('should not apply default values if the noDefaults option is enquire', (done) => {
685
+
686
+ const schema = Joi.object({
687
+ a: Joi.string().default('foo'),
688
+ b: Joi.number()
689
+ });
690
+
691
+ const input = { b: 42 };
692
+
693
+ Joi.validate(input, schema, { noDefaults: true }, (err, value) => {
694
+
695
+ expect(err).to.not.exist();
696
+ expect(value.a).to.not.exist();
697
+ expect(value.b).to.be.equal(42);
698
+
699
+ done();
700
+ });
701
+ });
702
+
703
+ it('should not apply default values from functions if the noDefaults option is enquire', (done) => {
704
+
705
+ const func = function (context) {
706
+
707
+ return 'foo';
708
+ };
709
+
710
+ func.description = 'test parameter';
711
+
712
+ const schema = Joi.object({
713
+ a: Joi.string().default(func),
714
+ b: Joi.number()
715
+ });
716
+
717
+ const input = { b: 42 };
718
+
719
+ Joi.validate(input, schema, { noDefaults: true }, (err, value) => {
720
+
721
+ expect(err).to.not.exist();
722
+ expect(value.a).to.not.exist();
723
+ expect(value.b).to.be.equal(42);
724
+
725
+ done();
726
+ });
727
+ });
728
+
729
+ it('should not apply default values from references if the noDefaults option is enquire', (done) => {
730
+
731
+ const schema = Joi.object({
732
+ a: Joi.string().default(Joi.ref('b')),
733
+ b: Joi.number()
734
+ });
735
+
736
+ const input = { b: 42 };
737
+
738
+ Joi.validate(input, schema, { noDefaults: true }, (err, value) => {
739
+
740
+ expect(err).to.not.exist();
741
+ expect(value.a).to.not.exist();
742
+ expect(value.b).to.be.equal(42);
743
+
744
+ done();
745
+ });
746
+ });
747
+ });
748
+
749
+ describe('optional()', () => {
750
+
751
+ it('validates optional with default required', (done) => {
752
+
753
+ const schema = Joi.object({
754
+ a: Joi.any(),
755
+ b: Joi.any(),
756
+ c: {
757
+ d: Joi.any()
758
+ }
759
+ }).options({ presence: 'required' });
760
+
761
+ Helper.validate(schema, [
762
+ [{ a: 5 }, false],
763
+ [{ a: 5, b: 6 }, false],
764
+ [{ a: 5, b: 6, c: {} }, false],
765
+ [{ a: 5, b: 6, c: { d: 7 } }, true],
766
+ [{}, false],
767
+ [{ b: 5 }, false]
768
+ ], done);
769
+ });
770
+ });
771
+
772
+ describe('forbidden()', () => {
773
+
774
+ it('validates forbidden', (done) => {
775
+
776
+ const schema = {
777
+ a: Joi.number(),
778
+ b: Joi.forbidden()
779
+ };
780
+
781
+ Helper.validate(schema, [
782
+ [{ a: 5 }, true],
783
+ [{ a: 5, b: 6 }, false],
784
+ [{ a: 'a' }, false],
785
+ [{}, true],
786
+ [{ b: undefined }, true],
787
+ [{ b: null }, false]
788
+ ], done);
789
+ });
790
+ });
791
+
792
+ describe('strip()', () => {
793
+
794
+ it('validates and returns undefined', (done) => {
795
+
796
+ const schema = Joi.string().strip();
797
+
798
+ schema.validate('test', (err, value) => {
799
+
800
+ expect(err).to.not.exist();
801
+ expect(value).to.not.exist();
802
+ done();
803
+ });
804
+ });
805
+
806
+ it('validates and returns an error', (done) => {
807
+
808
+ const schema = Joi.string().strip();
809
+
810
+ schema.validate(1, (err, value) => {
811
+
812
+ expect(err).to.exist();
813
+ expect(err.message).to.equal('"value" must be a string');
814
+ done();
815
+ });
816
+ });
817
+ });
818
+
819
+ describe('description()', () => {
820
+
821
+ it('sets the description', (done) => {
822
+
823
+ const b = Joi.description('my description');
824
+ expect(b._description).to.equal('my description');
825
+ done();
826
+ });
827
+
828
+ it('throws when description is missing', (done) => {
829
+
830
+ expect(() => {
831
+
832
+ Joi.description();
833
+ }).to.throw('Description must be a non-empty string');
834
+ done();
835
+ });
836
+ });
837
+
838
+ describe('notes()', () => {
839
+
840
+ it('sets the notes', (done) => {
841
+
842
+ const b = Joi.notes(['a']).notes('my notes');
843
+ expect(b._notes).to.deep.equal(['a', 'my notes']);
844
+ done();
845
+ });
846
+
847
+ it('throws when notes are missing', (done) => {
848
+
849
+ expect(() => {
850
+
851
+ Joi.notes();
852
+ }).to.throw('Notes must be a non-empty string or array');
853
+ done();
854
+ });
855
+
856
+ it('throws when notes are invalid', (done) => {
857
+
858
+ expect(() => {
859
+
860
+ Joi.notes(5);
861
+ }).to.throw('Notes must be a non-empty string or array');
862
+ done();
863
+ });
864
+ });
865
+
866
+ describe('tags()', () => {
867
+
868
+ it('sets the tags', (done) => {
869
+
870
+ const b = Joi.tags(['tag1', 'tag2']).tags('tag3');
871
+ expect(b._tags).to.include('tag1');
872
+ expect(b._tags).to.include('tag2');
873
+ expect(b._tags).to.include('tag3');
874
+ done();
875
+ });
876
+
877
+ it('throws when tags are missing', (done) => {
878
+
879
+ expect(() => {
880
+
881
+ Joi.tags();
882
+ }).to.throw('Tags must be a non-empty string or array');
883
+ done();
884
+ });
885
+
886
+ it('throws when tags are invalid', (done) => {
887
+
888
+ expect(() => {
889
+
890
+ Joi.tags(5);
891
+ }).to.throw('Tags must be a non-empty string or array');
892
+ done();
893
+ });
894
+ });
895
+
896
+ describe('meta()', () => {
897
+
898
+ it('sets the meta', (done) => {
899
+
900
+ const meta = { prop: 'val', prop2: 3 };
901
+ let b = Joi.meta(meta);
902
+ expect(b.describe().meta).to.deep.equal([meta]);
903
+
904
+ b = b.meta({ other: true });
905
+ expect(b.describe().meta).to.deep.equal([meta, {
906
+ other: true
907
+ }]);
908
+
909
+ done();
910
+ });
911
+
912
+ it('throws when meta is missing', (done) => {
913
+
914
+ expect(() => {
915
+
916
+ Joi.meta();
917
+ }).to.throw('Meta cannot be undefined');
918
+ done();
919
+ });
920
+ });
921
+
922
+ describe('example()', () => {
923
+
924
+ it('sets an example', (done) => {
925
+
926
+ const schema = Joi.valid(5, 6, 7).example(5);
927
+ expect(schema._examples).to.include(5);
928
+ expect(schema.describe().examples).to.deep.equal([5]);
929
+ done();
930
+ });
931
+
932
+ it('does not flatten examples', (done) => {
933
+
934
+ const schema = Joi.array().items(5, 6, 7).example([5, 6]);
935
+ expect(schema._examples).to.deep.equal([[5, 6]]);
936
+ expect(schema.describe().examples).to.deep.equal([[5, 6]]);
937
+ done();
938
+ });
939
+
940
+ it('throws when tags are missing', (done) => {
941
+
942
+ expect(() => {
943
+
944
+ Joi.example();
945
+ }).to.throw('Missing example');
946
+ done();
947
+ });
948
+
949
+ it('throws when example fails own rules', (done) => {
950
+
951
+ expect(() => {
952
+
953
+ Joi.valid(5, 6, 7).example(4);
954
+ }).to.throw('Bad example: "value" must be one of [5, 6, 7]');
955
+ done();
956
+ });
957
+ });
958
+
959
+ describe('unit()', () => {
960
+
961
+ it('sets the unit', (done) => {
962
+
963
+ const b = Joi.unit('milliseconds');
964
+ expect(b._unit).to.equal('milliseconds');
965
+ expect(b.describe().unit).to.equal('milliseconds');
966
+ done();
967
+ });
968
+
969
+ it('throws when unit is missing', (done) => {
970
+
971
+ expect(() => {
972
+
973
+ Joi.unit();
974
+ }).to.throw('Unit name must be a non-empty string');
975
+ done();
976
+ });
977
+ });
978
+
979
+ describe('_validate()', () => {
980
+
981
+ it('checks value after conversion', (done) => {
982
+
983
+ const schema = Joi.number().invalid(2);
984
+ Joi.validate('2', schema, { abortEarly: false }, (err, value) => {
985
+
986
+ expect(err).to.exist();
987
+ done();
988
+ });
989
+ });
990
+ });
991
+
992
+ describe('concat()', () => {
993
+
994
+ it('throws when schema is not any', (done) => {
995
+
996
+ expect(() => {
997
+
998
+ Joi.string().concat(Joi.number());
999
+ }).to.throw('Cannot merge type string with another type: number');
1000
+ done();
1001
+ });
1002
+
1003
+ it('throws when schema is missing', (done) => {
1004
+
1005
+ expect(() => {
1006
+
1007
+ Joi.string().concat();
1008
+ }).to.throw('Invalid schema object');
1009
+ done();
1010
+ });
1011
+
1012
+ it('throws when schema is invalid', (done) => {
1013
+
1014
+ expect(() => {
1015
+
1016
+ Joi.string().concat(1);
1017
+ }).to.throw('Invalid schema object');
1018
+ done();
1019
+ });
1020
+
1021
+ it('merges two schemas (settings)', (done) => {
1022
+
1023
+ const a = Joi.number().options({ convert: true });
1024
+ const b = Joi.options({ convert: false });
1025
+
1026
+ Helper.validate(a, [
1027
+ [1, true], ['1', true]
1028
+ ]);
1029
+
1030
+ Helper.validate(a.concat(b), [
1031
+ [1, true], ['1', false]
1032
+ ], done);
1033
+ });
1034
+
1035
+ it('merges two schemas (valid)', (done) => {
1036
+
1037
+ const a = Joi.string().valid('a');
1038
+ const b = Joi.string().valid('b');
1039
+
1040
+ Helper.validate(a, [
1041
+ ['a', true],
1042
+ ['b', false]
1043
+ ]);
1044
+
1045
+ Helper.validate(b, [
1046
+ ['b', true],
1047
+ ['a', false]
1048
+ ]);
1049
+
1050
+ Helper.validate(a.concat(b), [
1051
+ ['a', true],
1052
+ ['b', true]
1053
+ ], done);
1054
+ });
1055
+
1056
+ it('merges two schemas (invalid)', (done) => {
1057
+
1058
+ const a = Joi.string().invalid('a');
1059
+ const b = Joi.invalid('b');
1060
+
1061
+ Helper.validate(a, [
1062
+ ['b', true], ['a', false]
1063
+ ]);
1064
+
1065
+ Helper.validate(b, [
1066
+ ['a', true], ['b', false]
1067
+ ]);
1068
+
1069
+ Helper.validate(a.concat(b), [
1070
+ ['a', false], ['b', false]
1071
+ ], done);
1072
+ });
1073
+
1074
+ it('merges two schemas (valid/invalid)', (done) => {
1075
+
1076
+ const a = Joi.string().valid('a').invalid('b');
1077
+ const b = Joi.string().valid('b').invalid('a');
1078
+
1079
+ Helper.validate(a, [
1080
+ ['a', true],
1081
+ ['b', false]
1082
+ ]);
1083
+
1084
+ Helper.validate(b, [
1085
+ ['b', true],
1086
+ ['a', false]
1087
+ ]);
1088
+
1089
+ Helper.validate(a.concat(b), [
1090
+ ['a', false],
1091
+ ['b', true]
1092
+ ], done);
1093
+ });
1094
+
1095
+ it('merges two schemas (tests)', (done) => {
1096
+
1097
+ const a = Joi.number().min(5);
1098
+ const b = Joi.number().max(10);
1099
+
1100
+ Helper.validate(a, [
1101
+ [4, false], [11, true]
1102
+ ]);
1103
+
1104
+ Helper.validate(b, [
1105
+ [6, true], [11, false]
1106
+ ]);
1107
+
1108
+ Helper.validate(a.concat(b), [
1109
+ [4, false], [6, true], [11, false]
1110
+ ], done);
1111
+ });
1112
+
1113
+ it('merges two schemas (flags)', (done) => {
1114
+
1115
+ const a = Joi.string().valid('a');
1116
+ const b = Joi.string().insensitive();
1117
+
1118
+ Helper.validate(a, [
1119
+ ['a', true], ['A', false], ['b', false]
1120
+ ]);
1121
+
1122
+ Helper.validate(a.concat(b), [
1123
+ ['a', true], ['A', true], ['b', false]
1124
+ ], done);
1125
+ });
1126
+
1127
+ it('overrides and append information', (done) => {
1128
+
1129
+ const a = Joi.description('a').unit('a').tags('a').example('a');
1130
+ const b = Joi.description('b').unit('b').tags('b').example('b');
1131
+
1132
+ const desc = a.concat(b).describe();
1133
+ expect(desc).to.deep.equal({
1134
+ type: 'any',
1135
+ description: 'b',
1136
+ tags: ['a', 'b'],
1137
+ examples: ['a', 'b'],
1138
+ unit: 'b'
1139
+ });
1140
+ done();
1141
+ });
1142
+
1143
+ it('merges two objects (any key + specific key)', (done) => {
1144
+
1145
+ const a = Joi.object();
1146
+ const b = Joi.object({ b: 1 });
1147
+
1148
+ Helper.validate(a, [
1149
+ [{ b: 1 }, true], [{ b: 2 }, true]
1150
+ ]);
1151
+
1152
+ Helper.validate(b, [
1153
+ [{ b: 1 }, true], [{ b: 2 }, false]
1154
+ ]);
1155
+
1156
+ Helper.validate(a.concat(b), [
1157
+ [{ b: 1 }, true], [{ b: 2 }, false]
1158
+ ]);
1159
+
1160
+ Helper.validate(b.concat(a), [
1161
+ [{ b: 1 }, true], [{ b: 2 }, false]
1162
+ ], done);
1163
+ });
1164
+
1165
+ it('merges two objects (no key + any key)', (done) => {
1166
+
1167
+ const a = Joi.object({});
1168
+ const b = Joi.object();
1169
+
1170
+ Helper.validate(a, [
1171
+ [{}, true], [{ b: 2 }, false]
1172
+ ]);
1173
+
1174
+ Helper.validate(b, [
1175
+ [{}, true], [{ b: 2 }, true]
1176
+ ]);
1177
+
1178
+ Helper.validate(a.concat(b), [
1179
+ [{}, true], [{ b: 2 }, false]
1180
+ ]);
1181
+
1182
+ Helper.validate(b.concat(a), [
1183
+ [{}, true], [{ b: 2 }, false]
1184
+ ], done);
1185
+ });
1186
+
1187
+ it('merges two objects (key + key)', (done) => {
1188
+
1189
+ const a = Joi.object({ a: 1 });
1190
+ const b = Joi.object({ b: 2 });
1191
+
1192
+ Helper.validate(a, [
1193
+ [{ a: 1 }, true], [{ b: 2 }, false]
1194
+ ]);
1195
+
1196
+ Helper.validate(b, [
1197
+ [{ a: 1 }, false], [{ b: 2 }, true]
1198
+ ]);
1199
+
1200
+ Helper.validate(a.concat(b), [
1201
+ [{ a: 1 }, true], [{ b: 2 }, true]
1202
+ ]);
1203
+
1204
+ Helper.validate(b.concat(a), [
1205
+ [{ a: 1 }, true], [{ b: 2 }, true]
1206
+ ], done);
1207
+ });
1208
+
1209
+ it('merges two objects (renames)', (done) => {
1210
+
1211
+ const a = Joi.object({ a: 1 }).rename('c', 'a');
1212
+ const b = Joi.object({ b: 2 }).rename('d', 'b');
1213
+
1214
+ a.concat(b).validate({ c: 1, d: 2 }, (err, value) => {
1215
+
1216
+ expect(err).to.not.exist();
1217
+ expect(value).to.deep.equal({ a: 1, b: 2 });
1218
+ done();
1219
+ });
1220
+ });
1221
+
1222
+ it('merges two objects (deps)', (done) => {
1223
+
1224
+ const a = Joi.object({ a: 1 });
1225
+ const b = Joi.object({ b: 2 }).and('b', 'a');
1226
+
1227
+ a.concat(b).validate({ a: 1, b: 2 }, (err, value) => {
1228
+
1229
+ expect(err).to.not.exist();
1230
+ done();
1231
+ });
1232
+ });
1233
+
1234
+ it('merges two objects (same key)', (done) => {
1235
+
1236
+ const a = Joi.object({ a: 1, b: 2, c: 3 });
1237
+ const b = Joi.object({ b: 1, c: 2, a: 3 });
1238
+
1239
+ const ab = a.concat(b);
1240
+
1241
+ Helper.validate(a, [
1242
+ [{ a: 1, b: 2, c: 3 }, true],
1243
+ [{ a: 3, b: 1, c: 2 }, false]
1244
+ ]);
1245
+
1246
+ Helper.validate(b, [
1247
+ [{ a: 1, b: 2, c: 3 }, false],
1248
+ [{ a: 3, b: 1, c: 2 }, true]
1249
+ ]);
1250
+
1251
+ Helper.validate(ab, [
1252
+ [{ a: 1, b: 2, c: 3 }, true],
1253
+ [{ a: 3, b: 1, c: 2 }, true],
1254
+ [{ a: 1, b: 2, c: 2 }, true],
1255
+ [{ a: 1, b: 2, c: 4 }, false]
1256
+ ], done);
1257
+ });
1258
+
1259
+ it('throws when schema key types do not match', (done) => {
1260
+
1261
+ const a = Joi.object({ a: Joi.number() });
1262
+ const b = Joi.object({ a: Joi.string() });
1263
+
1264
+ expect(() => {
1265
+
1266
+ a.concat(b);
1267
+ }).to.throw('Cannot merge type number with another type: string');
1268
+ done();
1269
+ });
1270
+
1271
+ it('merges two alternatives with references', (done) => {
1272
+
1273
+ const schema = {
1274
+ a: { c: Joi.number() },
1275
+ b: Joi.alternatives(Joi.ref('a.c')).concat(Joi.alternatives(Joi.ref('c'))),
1276
+ c: Joi.number()
1277
+ };
1278
+
1279
+ Helper.validate(schema, [
1280
+ [{ a: {} }, true],
1281
+ [{ a: { c: '5' }, b: 5 }, true],
1282
+ [{ a: { c: '5' }, b: 6, c: '6' }, true],
1283
+ [{ a: { c: '5' }, b: 7, c: '6' }, false]
1284
+ ], done);
1285
+ });
1286
+
1287
+ it('merges meta properly', (done) => {
1288
+
1289
+ const metaA = { a: 1 };
1290
+ const metaB = { b: 1 };
1291
+ const a = Joi.any().meta(metaA);
1292
+ const b = Joi.any().meta(metaB);
1293
+ const c = Joi.any();
1294
+ const d = Joi.any();
1295
+
1296
+ expect(a.concat(b)._meta).to.deep.equal([{ a: 1 }, { b: 1 }]);
1297
+ expect(a.concat(c)._meta).to.deep.equal([metaA]);
1298
+ expect(b.concat(c)._meta).to.deep.equal([metaB]);
1299
+ expect(c.concat(d)._meta).to.deep.equal([]);
1300
+
1301
+ done();
1302
+ });
1303
+
1304
+ it('merges into an any', (done) => {
1305
+
1306
+ const a = Joi.any().required();
1307
+ const b = Joi.number().only(0);
1308
+
1309
+ expect(() => {
1310
+
1311
+ a.concat(b);
1312
+ }).to.not.throw();
1313
+
1314
+ const schema = a.concat(b);
1315
+ expect(schema.validate().error.message).to.equal('"value" is required');
1316
+ expect(schema.validate(1).error.message).to.equal('"value" must be one of [0]');
1317
+
1318
+ done();
1319
+ });
1320
+ });
1321
+
1322
+ describe('when()', () => {
1323
+
1324
+ it('throws when options are invalid', (done) => {
1325
+
1326
+ expect(() => {
1327
+
1328
+ Joi.when('a');
1329
+ }).to.throw('Invalid options');
1330
+ done();
1331
+ });
1332
+
1333
+ it('forks type into alternatives', (done) => {
1334
+
1335
+ const schema = {
1336
+ a: Joi.any(),
1337
+ b: Joi.string().valid('x').when('a', { is: 5, then: Joi.valid('y'), otherwise: Joi.valid('z') })
1338
+ };
1339
+
1340
+ Helper.validate(schema, [
1341
+ [{ a: 5, b: 'x' }, true],
1342
+ [{ a: 5, b: 'y' }, true],
1343
+ [{ a: 5, b: 'z' }, false],
1344
+ [{ a: 1, b: 'x' }, true],
1345
+ [{ a: 1, b: 'y' }, false],
1346
+ [{ a: 1, b: 'z' }, true],
1347
+ [{ a: 5, b: 'a' }, false],
1348
+ [{ b: 'a' }, false]
1349
+ ], done);
1350
+ });
1351
+
1352
+ it('forks type into alternatives (only then)', (done) => {
1353
+
1354
+ const schema = {
1355
+ a: Joi.any(),
1356
+ b: Joi.string().valid('x').when('a', { is: 5, then: Joi.valid('y') })
1357
+ };
1358
+
1359
+ Helper.validate(schema, [
1360
+ [{ a: 5, b: 'x' }, true],
1361
+ [{ a: 5, b: 'y' }, true],
1362
+ [{ a: 5, b: 'z' }, false],
1363
+ [{ a: 1, b: 'x' }, true],
1364
+ [{ a: 1, b: 'y' }, false],
1365
+ [{ a: 1, b: 'z' }, false],
1366
+ [{ a: 5, b: 'a' }, false],
1367
+ [{ b: 'a' }, false]
1368
+ ], done);
1369
+ });
1370
+
1371
+ it('forks type into alternatives (only otherwise)', (done) => {
1372
+
1373
+ const schema = {
1374
+ a: Joi.any(),
1375
+ b: Joi.string().valid('x').when('a', { is: 5, otherwise: Joi.valid('z') })
1376
+ };
1377
+
1378
+ Helper.validate(schema, [
1379
+ [{ a: 5, b: 'x' }, true],
1380
+ [{ a: 5, b: 'y' }, false],
1381
+ [{ a: 5, b: 'z' }, false],
1382
+ [{ a: 1, b: 'x' }, true],
1383
+ [{ a: 1, b: 'y' }, false],
1384
+ [{ a: 1, b: 'z' }, true],
1385
+ [{ a: 5, b: 'a' }, false],
1386
+ [{ b: 'a' }, false]
1387
+ ], done);
1388
+ });
1389
+
1390
+ it('forks type into alternatives (with a schema)', (done) => {
1391
+
1392
+ const schema = {
1393
+ a: Joi.any(),
1394
+ b: Joi.string().valid('x').when('a', { is: Joi.number().only(5).required(), then: Joi.valid('y') })
1395
+ };
1396
+
1397
+ Helper.validate(schema, [
1398
+ [{ a: 5, b: 'x' }, true],
1399
+ [{ a: 5, b: 'y' }, true],
1400
+ [{ a: 5, b: 'z' }, false],
1401
+ [{ a: 1, b: 'x' }, true],
1402
+ [{ a: 1, b: 'y' }, false],
1403
+ [{ a: 1, b: 'z' }, false],
1404
+ [{ a: 5, b: 'a' }, false],
1405
+ [{ b: 'a' }, false]
1406
+ ], done);
1407
+ });
1408
+
1409
+ it('makes peer required', (done) => {
1410
+
1411
+ const schema = {
1412
+ a: Joi.when('b', { is: 5, then: Joi.required() }),
1413
+ b: Joi.any()
1414
+ };
1415
+
1416
+ Helper.validate(schema, [
1417
+ [{ b: 5 }, false],
1418
+ [{ b: 6 }, true],
1419
+ [{ a: 'b' }, true],
1420
+ [{ b: 5, a: 'x' }, true]
1421
+ ], done);
1422
+ });
1423
+
1424
+ it('can describe as the original object', (done) => {
1425
+
1426
+ const schema = Joi.number().min(10).when('a', { is: 5, then: Joi.number().max(20).required() }).describe();
1427
+ expect(schema).to.deep.equal({
1428
+ type: 'alternatives',
1429
+ flags: {
1430
+ presence: 'ignore'
1431
+ },
1432
+ alternatives: [{
1433
+ ref: 'ref:a',
1434
+ is: {
1435
+ type: 'number',
1436
+ flags: {
1437
+ allowOnly: true,
1438
+ presence: 'required'
1439
+ },
1440
+ valids: [5],
1441
+ invalids: [Infinity, -Infinity]
1442
+ },
1443
+ then: {
1444
+ type: 'number',
1445
+ flags: {
1446
+ presence: 'required'
1447
+ },
1448
+ invalids: [Infinity, -Infinity],
1449
+ rules: [{ name: 'min', arg: 10 }, { name: 'max', arg: 20 }]
1450
+ }
1451
+ }]
1452
+ });
1453
+ done();
1454
+ });
1455
+ });
1456
+
1457
+ describe('requiredKeys()', () => {
1458
+
1459
+ it('should set keys as required', (done) => {
1460
+
1461
+ const schema = Joi.object({ a: 0, b: 0, c: { d: 0, e: { f: 0 } }, g: { h: 0 } })
1462
+ .requiredKeys('a', 'b', 'c.d', 'c.e.f', 'g');
1463
+ Helper.validate(schema, [
1464
+ [{}, false],
1465
+ [{ a: 0 }, false],
1466
+ [{ a: 0, b: 0 }, false],
1467
+ [{ a: 0, b: 0, g: {} }, true],
1468
+ [{ a: 0, b: 0, c: {}, g: {} }, false],
1469
+ [{ a: 0, b: 0, c: { d: 0 }, g: {} }, true],
1470
+ [{ a: 0, b: 0, c: { d: 0, e: {} }, g: {} }, false],
1471
+ [{ a: 0, b: 0, c: { d: 0, e: { f: 0 } }, g: {} }, true]
1472
+ ], done);
1473
+ });
1474
+
1475
+ it('should work on types other than objects', (done) => {
1476
+
1477
+ const schemas = [Joi.array(), Joi.binary(), Joi.boolean(), Joi.date(), Joi.func(), Joi.number(), Joi.string()];
1478
+ schemas.forEach((schema) => {
1479
+
1480
+ expect(() => {
1481
+
1482
+ schema.applyFunctionToChildren([''], 'required');
1483
+ }).to.not.throw();
1484
+
1485
+ expect(() => {
1486
+
1487
+ schema.applyFunctionToChildren(['', 'a'], 'required');
1488
+ }).to.throw();
1489
+
1490
+ expect(() => {
1491
+
1492
+ schema.applyFunctionToChildren(['a'], 'required');
1493
+ }).to.throw();
1494
+ });
1495
+
1496
+ done();
1497
+ });
1498
+
1499
+ it('should throw on unknown key', (done) => {
1500
+
1501
+ expect(() => {
1502
+
1503
+ Joi.object({ a: 0, b: 0 }).requiredKeys('a', 'c', 'b', 'd', 'd.e.f');
1504
+ }).to.throw(Error, 'unknown key(s) c, d');
1505
+
1506
+ expect(() => {
1507
+
1508
+ Joi.object({ a: 0, b: 0 }).requiredKeys('a', 'b', 'a.c.d');
1509
+ }).to.throw(Error, 'unknown key(s) a.c.d');
1510
+
1511
+ done();
1512
+ });
1513
+
1514
+ it('should throw on empty object', (done) => {
1515
+
1516
+ expect(() => {
1517
+
1518
+ Joi.object().requiredKeys('a', 'c', 'b', 'd');
1519
+ }).to.throw(Error, 'unknown key(s) a, b, c, d');
1520
+ done();
1521
+ });
1522
+
1523
+ it('should not modify original object', (done) => {
1524
+
1525
+ const schema = Joi.object({ a: 0 });
1526
+ const requiredSchema = schema.requiredKeys('a');
1527
+ schema.validate({}, (err) => {
1528
+
1529
+ expect(err).to.not.exist();
1530
+
1531
+ requiredSchema.validate({}, (err) => {
1532
+
1533
+ expect(err).to.exist();
1534
+ done();
1535
+ });
1536
+ });
1537
+ });
1538
+ });
1539
+
1540
+ describe('optionalKeys()', () => {
1541
+
1542
+ it('should set keys as optional', (done) => {
1543
+
1544
+ const schema = Joi.object({ a: Joi.number().required(), b: Joi.number().required() }).optionalKeys('a', 'b');
1545
+ Helper.validate(schema, [
1546
+ [{}, true],
1547
+ [{ a: 0 }, true],
1548
+ [{ a: 0, b: 0 }, true]
1549
+ ], done);
1550
+ });
1551
+ });
1552
+
1553
+ describe('empty()', () => {
1554
+
1555
+ it('should void values when considered empty', (done) => {
1556
+
1557
+ const schema = Joi.string().empty('');
1558
+ Helper.validate(schema, [
1559
+ [undefined, true, null, undefined],
1560
+ ['abc', true, null, 'abc'],
1561
+ ['', true, null, undefined]
1562
+ ], done);
1563
+ });
1564
+
1565
+ it('should override any previous empty', (done) => {
1566
+
1567
+ const schema = Joi.string().empty('').empty('abc');
1568
+ Helper.validate(schema, [
1569
+ [undefined, true, null, undefined],
1570
+ ['abc', true, null, undefined],
1571
+ ['', false, null, '"value" is not allowed to be empty'],
1572
+ ['def', true, null, 'def']
1573
+ ], done);
1574
+ });
1575
+
1576
+ it('should be possible to reset the empty value', (done) => {
1577
+
1578
+ const schema = Joi.string().empty('').empty();
1579
+ Helper.validate(schema, [
1580
+ [undefined, true, null, undefined],
1581
+ ['abc', true, null, 'abc'],
1582
+ ['', false, null, '"value" is not allowed to be empty']
1583
+ ], done);
1584
+ });
1585
+
1586
+ it('should have no effect if only reset is used', (done) => {
1587
+
1588
+ const schema = Joi.string().empty();
1589
+ Helper.validate(schema, [
1590
+ [undefined, true, null, undefined],
1591
+ ['abc', true, null, 'abc'],
1592
+ ['', false, null, '"value" is not allowed to be empty']
1593
+ ], done);
1594
+ });
1595
+
1596
+ it('should work with dependencies', (done) => {
1597
+
1598
+ const schema = Joi.object({
1599
+ a: Joi.string().empty(''),
1600
+ b: Joi.string().empty('')
1601
+ }).or('a', 'b');
1602
+
1603
+ Helper.validate(schema, [
1604
+ [{}, false, null, '"value" must contain at least one of [a, b]'],
1605
+ [{ a: '' }, false, null, '"value" must contain at least one of [a, b]'],
1606
+ [{ a: 'a' }, true, null, { a: 'a' }],
1607
+ [{ a: '', b: 'b' }, true, null, { b: 'b' }]
1608
+ ], done);
1609
+ });
1610
+ });
1611
+
1612
+ describe('Set', () => {
1613
+
1614
+ describe('has()', () => {
1615
+
1616
+ it('compares date to null', (done) => {
1617
+
1618
+ const any = Joi.any().clone();
1619
+ any._valids.add(null);
1620
+ expect(any._valids.has(new Date())).to.equal(false);
1621
+ done();
1622
+ });
1623
+
1624
+ it('compares buffer to null', (done) => {
1625
+
1626
+ const any = Joi.any().clone();
1627
+ any._valids.add(null);
1628
+ expect(any._valids.has(new Buffer(''))).to.equal(false);
1629
+ done();
1630
+ });
1631
+
1632
+ });
1633
+
1634
+ describe('values()', () => {
1635
+
1636
+ it('returns array', (done) => {
1637
+
1638
+ const a = Joi.any().valid('x').invalid('y');
1639
+ const b = a.invalid('x');
1640
+ expect(a._valids.values().length).to.equal(1);
1641
+ expect(b._valids.values().length).to.equal(0);
1642
+ expect(a._invalids.values().length).to.equal(1);
1643
+ expect(b._invalids.values().length).to.equal(2);
1644
+ done();
1645
+ });
1646
+
1647
+ it('strips undefined', (done) => {
1648
+
1649
+ const any = Joi.any().clone();
1650
+ any._valids.add(undefined);
1651
+ expect(any._valids.values({ stripUndefined: true })).to.not.include(undefined);
1652
+ done();
1653
+ });
1654
+ });
1655
+
1656
+ describe('allow()', () => {
1657
+
1658
+ it('allows valid values to be set', (done) => {
1659
+
1660
+ expect(() => {
1661
+
1662
+ Joi.any().allow(true, 1, 'hello', new Date());
1663
+ }).not.to.throw();
1664
+ done();
1665
+ });
1666
+
1667
+ it('throws when passed undefined', (done) => {
1668
+
1669
+ expect(() => {
1670
+
1671
+ Joi.any().allow(undefined);
1672
+ }).to.throw(Error, 'Cannot call allow/valid/invalid with undefined');
1673
+ done();
1674
+ });
1675
+ });
1676
+
1677
+ describe('valid()', () => {
1678
+
1679
+ it('allows valid values to be set', (done) => {
1680
+
1681
+ expect(() => {
1682
+
1683
+ Joi.any().valid(true, 1, 'hello', new Date(), Symbol('foo'), () => {}, {});
1684
+ }).not.to.throw();
1685
+ done();
1686
+ });
1687
+
1688
+ it('throws when passed undefined', (done) => {
1689
+
1690
+ expect(() => {
1691
+
1692
+ Joi.any().valid(undefined);
1693
+ }).to.throw(Error, 'Cannot call allow/valid/invalid with undefined');
1694
+ done();
1695
+ });
1696
+
1697
+ it('validates differents types of values', (done) => {
1698
+
1699
+ expect(Joi.valid(1).validate(1).error).to.be.null();
1700
+ expect(Joi.valid(1).validate(2).error).to.exist();
1701
+
1702
+ const d = new Date();
1703
+ expect(Joi.valid(d).validate(new Date(d.getTime())).error).to.be.null();
1704
+ expect(Joi.valid(d).validate(new Date(d.getTime() + 1)).error).to.exist();
1705
+
1706
+ const str = 'foo';
1707
+ expect(Joi.valid(str).validate(str).error).to.be.null();
1708
+ expect(Joi.valid(str).validate('foobar').error).to.exist();
1709
+
1710
+ const s = Symbol('foo');
1711
+ expect(Joi.valid(s).validate(s).error).to.be.null();
1712
+ expect(Joi.valid(s).validate(Symbol('foo')).error).to.exist();
1713
+
1714
+ const o = {};
1715
+ expect(Joi.valid(o).validate(o).error).to.be.null();
1716
+ expect(Joi.valid(o).validate({}).error).to.exist();
1717
+
1718
+ const f = () => {};
1719
+ expect(Joi.valid(f).validate(f).error).to.be.null();
1720
+ expect(Joi.valid(f).validate(() => {}).error).to.exist();
1721
+
1722
+ const b = new Buffer('foo');
1723
+ expect(Joi.valid(b).validate(b).error).to.be.null();
1724
+ expect(Joi.valid(b).validate(new Buffer('foobar')).error).to.exist();
1725
+
1726
+ done();
1727
+ });
1728
+ });
1729
+
1730
+ describe('invalid()', () => {
1731
+
1732
+ it('allows invalid values to be set', (done) => {
1733
+
1734
+ expect(() => {
1735
+
1736
+ Joi.any().valid(true, 1, 'hello', new Date());
1737
+ }).not.to.throw();
1738
+ done();
1739
+ });
1740
+
1741
+ it('throws when passed undefined', (done) => {
1742
+
1743
+ expect(() => {
1744
+
1745
+ Joi.any().invalid(undefined);
1746
+ }).to.throw('Cannot call allow/valid/invalid with undefined');
1747
+ done();
1748
+ });
1749
+ });
1750
+ });
1751
+ });