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/index.js ADDED
@@ -0,0 +1,2450 @@
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('Joi', () => {
25
+
26
+ it('validates object', (done) => {
27
+
28
+ const schema = Joi.object({
29
+ a: Joi.number().min(0).max(3),
30
+ b: Joi.string().valid('a', 'b', 'c'),
31
+ c: Joi.string().email().optional()
32
+ }).without('a', 'none');
33
+
34
+ const obj = {
35
+ a: 1,
36
+ b: 'a',
37
+ c: 'joe@example.com'
38
+ };
39
+
40
+ schema.validate(obj, (err, value) => {
41
+
42
+ expect(err).to.not.exist();
43
+ done();
44
+ });
45
+ });
46
+
47
+ it('keeps schema immutable', (done) => {
48
+
49
+ const a = Joi.string();
50
+ const b = a.valid('b');
51
+
52
+ Helper.validate(a, [
53
+ ['a', true],
54
+ ['b', true],
55
+ [5, false]
56
+ ], () => {
57
+
58
+ Helper.validate(b, [
59
+ ['a', false],
60
+ ['b', true],
61
+ [5, false]
62
+ ], done);
63
+ });
64
+
65
+ });
66
+
67
+ it('validates null', (done) => {
68
+
69
+ Joi.string().validate(null, (err, value) => {
70
+
71
+ expect(err).to.exist();
72
+ expect(err.annotate()).to.equal('{\n \u001b[41m\"value\"\u001b[0m\u001b[31m [1]: -- missing --\u001b[0m\n}\n\u001b[31m\n[1] "value" must be a string\u001b[0m');
73
+ done();
74
+ });
75
+ });
76
+
77
+ it('validates null schema', (done) => {
78
+
79
+ Helper.validate(null, [
80
+ ['a', false],
81
+ [null, true]
82
+ ], done);
83
+ });
84
+
85
+ it('validates number literal', (done) => {
86
+
87
+ Helper.validate(5, [
88
+ [6, false],
89
+ [5, true]
90
+ ], done);
91
+ });
92
+
93
+ it('validates string literal', (done) => {
94
+
95
+ Helper.validate('5', [
96
+ ['6', false],
97
+ ['5', true]
98
+ ], done);
99
+ });
100
+
101
+ it('validates boolean literal', (done) => {
102
+
103
+ Helper.validate(true, [
104
+ [false, false],
105
+ [true, true]
106
+ ], done);
107
+ });
108
+
109
+ it('validates date literal', (done) => {
110
+
111
+ const now = Date.now();
112
+ Helper.validate(new Date(now), [
113
+ [new Date(now), true],
114
+ [now, true],
115
+ [now * 2, false]
116
+ ], done);
117
+ });
118
+
119
+ it('validates complex literal', (done) => {
120
+
121
+ const schema = ['key', 5, { a: true, b: [/^a/, 'boom'] }];
122
+ Helper.validate(schema, [
123
+ ['key', true],
124
+ [5, true],
125
+ ['other', false],
126
+ [6, false],
127
+ [{ c: 5 }, false],
128
+ [{}, true],
129
+ [{ b: 'abc' }, true],
130
+ [{ a: true, b: 'boom' }, true],
131
+ [{ a: 5, b: 'a' }, false]
132
+ ], done);
133
+ });
134
+
135
+ it('validates a compiled complex literal', (done) => {
136
+
137
+ const schema = Joi.compile(['key', 5, { a: true, b: [/^a/, 'boom'] }]);
138
+ Helper.validate(schema, [
139
+ ['key', true],
140
+ [5, true],
141
+ ['other', false],
142
+ [6, false],
143
+ [{ c: 5 }, false],
144
+ [{}, true],
145
+ [{ b: 'abc' }, true],
146
+ [{ a: true, b: 'boom' }, true],
147
+ [{ a: 5, b: 'a' }, false]
148
+ ], done);
149
+ });
150
+
151
+ it('validates regex directly', (done) => {
152
+
153
+ Joi.compile(/^5$/).validate('5', (err, value) => {
154
+
155
+ expect(err).to.not.exist();
156
+ Joi.compile(/.{2}/).validate('6', (err2, value2) => {
157
+
158
+ expect(err2).to.exist();
159
+ done();
160
+ });
161
+ });
162
+ });
163
+
164
+ it('validated with', (done) => {
165
+
166
+ const schema = Joi.object({
167
+ txt: Joi.string(),
168
+ upc: Joi.string()
169
+ }).with('txt', 'upc');
170
+
171
+ Joi.validate({ txt: 'a' }, schema, { abortEarly: false }, (err, value) => {
172
+
173
+ expect(err.message).to.equal('"txt" missing required peer "upc"');
174
+
175
+ Helper.validate(schema, [
176
+ [{ upc: 'test' }, true],
177
+ [{ txt: 'test' }, false],
178
+ [{ txt: 'test', upc: null }, false],
179
+ [{ txt: 'test', upc: '' }, false],
180
+ [{ txt: 'test', upc: undefined }, false],
181
+ [{ txt: 'test', upc: 'test' }, true]
182
+ ], done);
183
+ });
184
+ });
185
+
186
+ it('validated without', (done) => {
187
+
188
+ const schema = Joi.object({
189
+ txt: Joi.string(),
190
+ upc: Joi.string()
191
+ }).without('txt', 'upc');
192
+
193
+ Joi.validate({ txt: 'a', upc: 'b' }, schema, { abortEarly: false }, (err, value) => {
194
+
195
+ expect(err.message).to.equal('"txt" conflict with forbidden peer "upc"');
196
+
197
+ Helper.validate(schema, [
198
+ [{ upc: 'test' }, true],
199
+ [{ txt: 'test' }, true],
200
+ [{ txt: 'test', upc: null }, false],
201
+ [{ txt: 'test', upc: '' }, false],
202
+ [{ txt: 'test', upc: undefined }, true],
203
+ [{ txt: 'test', upc: 'test' }, false]
204
+ ], done);
205
+ });
206
+ });
207
+
208
+ it('validates xor', (done) => {
209
+
210
+ const schema = Joi.object({
211
+ txt: Joi.string(),
212
+ upc: Joi.string()
213
+ }).xor('txt', 'upc');
214
+
215
+ Joi.validate({}, schema, { abortEarly: false }, (err, value) => {
216
+
217
+ expect(err.message).to.equal('"value" must contain at least one of [txt, upc]');
218
+
219
+ Helper.validate(schema, [
220
+ [{ upc: null }, false],
221
+ [{ upc: 'test' }, true],
222
+ [{ txt: null }, false],
223
+ [{ txt: 'test' }, true],
224
+ [{ txt: 'test', upc: null }, false],
225
+ [{ txt: 'test', upc: '' }, false],
226
+ [{ txt: '', upc: 'test' }, false],
227
+ [{ txt: null, upc: 'test' }, false],
228
+ [{ txt: undefined, upc: 'test' }, true],
229
+ [{ txt: 'test', upc: undefined }, true],
230
+ [{ txt: 'test', upc: '' }, false],
231
+ [{ txt: 'test', upc: null }, false],
232
+ [{ txt: '', upc: undefined }, false],
233
+ [{ txt: '', upc: '' }, false],
234
+ [{ txt: 'test', upc: 'test' }, false]
235
+ ], done);
236
+ });
237
+ });
238
+
239
+ it('validates multiple peers xor', (done) => {
240
+
241
+ const schema = Joi.object({
242
+ txt: Joi.string(),
243
+ upc: Joi.string(),
244
+ code: Joi.string()
245
+ }).xor('txt', 'upc', 'code');
246
+
247
+ Helper.validate(schema, [
248
+ [{ upc: 'test' }, true],
249
+ [{ txt: 'test' }, true],
250
+ [{}, false]
251
+ ], done);
252
+ });
253
+
254
+ it('validates xor with number types', (done) => {
255
+
256
+ const schema = Joi.object({
257
+ code: Joi.number(),
258
+ upc: Joi.number()
259
+ }).xor('code', 'upc');
260
+
261
+ Helper.validate(schema, [
262
+ [{ upc: 123 }, true],
263
+ [{ code: 456 }, true],
264
+ [{ code: 456, upc: 123 }, false],
265
+ [{}, false]
266
+ ], done);
267
+ });
268
+
269
+ it('validates xor when empty value of peer allowed', (done) => {
270
+
271
+ const schema = Joi.object({
272
+ code: Joi.string(),
273
+ upc: Joi.string().allow('')
274
+ }).xor('code', 'upc');
275
+
276
+ Helper.validate(schema, [
277
+ [{ upc: '' }, true],
278
+ [{ upc: '123' }, true],
279
+ [{ code: '456' }, true],
280
+ [{ code: '456', upc: '' }, false],
281
+ [{}, false]
282
+ ], done);
283
+ });
284
+
285
+ it('validates or', (done) => {
286
+
287
+ const schema = Joi.object({
288
+ txt: Joi.string(),
289
+ upc: Joi.string().allow(null, ''),
290
+ code: Joi.number()
291
+ }).or('txt', 'upc', 'code');
292
+
293
+ Joi.validate({}, schema, { abortEarly: false }, (err, value) => {
294
+
295
+ expect(err.message).to.equal('"value" must contain at least one of [txt, upc, code]');
296
+
297
+ Helper.validate(schema, [
298
+ [{ upc: null }, true],
299
+ [{ upc: 'test' }, true],
300
+ [{ txt: null }, false],
301
+ [{ txt: 'test' }, true],
302
+ [{ code: null }, false],
303
+ [{ code: 123 }, true],
304
+ [{ txt: 'test', upc: null }, true],
305
+ [{ txt: 'test', upc: '' }, true],
306
+ [{ txt: '', upc: 'test' }, false],
307
+ [{ txt: null, upc: 'test' }, false],
308
+ [{ txt: undefined, upc: 'test' }, true],
309
+ [{ txt: 'test', upc: undefined }, true],
310
+ [{ txt: 'test', upc: '' }, true],
311
+ [{ txt: 'test', upc: null }, true],
312
+ [{ txt: '', upc: undefined }, false],
313
+ [{ txt: '', upc: undefined, code: 999 }, false],
314
+ [{ txt: '', upc: undefined, code: undefined }, false],
315
+ [{ txt: '', upc: '' }, false],
316
+ [{ txt: 'test', upc: 'test' }, true],
317
+ [{ txt: 'test', upc: 'test', code: 322 }, true]
318
+ ], done);
319
+ });
320
+ });
321
+
322
+ it('validates and', (done) => {
323
+
324
+ const schema = Joi.object({
325
+ txt: Joi.string(),
326
+ upc: Joi.string().allow(null, ''),
327
+ code: Joi.number()
328
+ }).and('txt', 'upc', 'code');
329
+
330
+ Joi.validate({ txt: 'x' }, schema, { abortEarly: false }, (err, value) => {
331
+
332
+ expect(err.message).to.equal('"value" contains [txt] without its required peers [upc, code]');
333
+
334
+ Helper.validate(schema, [
335
+ [{}, true],
336
+ [{ upc: null }, false],
337
+ [{ upc: 'test' }, false],
338
+ [{ txt: null }, false],
339
+ [{ txt: 'test' }, false],
340
+ [{ code: null }, false],
341
+ [{ code: 123 }, false],
342
+ [{ txt: 'test', upc: null }, false],
343
+ [{ txt: 'test', upc: '' }, false],
344
+ [{ txt: '', upc: 'test' }, false],
345
+ [{ txt: null, upc: 'test' }, false],
346
+ [{ txt: undefined, upc: 'test' }, false],
347
+ [{ txt: 'test', upc: undefined }, false],
348
+ [{ txt: 'test', upc: '' }, false],
349
+ [{ txt: 'test', upc: null }, false],
350
+ [{ txt: '', upc: undefined }, false],
351
+ [{ txt: '', upc: undefined, code: 999 }, false],
352
+ [{ txt: '', upc: undefined, code: undefined }, false],
353
+ [{ txt: '', upc: '' }, false],
354
+ [{ txt: 'test', upc: 'test' }, false],
355
+ [{ txt: 'test', upc: 'test', code: 322 }, true],
356
+ [{ txt: 'test', upc: null, code: 322 }, true]
357
+ ], done);
358
+ });
359
+ });
360
+
361
+ it('validates nand()', (done) => {
362
+
363
+ const schema = Joi.object({
364
+ txt: Joi.string(),
365
+ upc: Joi.string().allow(null, ''),
366
+ code: Joi.number()
367
+ }).nand('txt', 'upc', 'code');
368
+
369
+ Joi.validate({ txt: 'x', upc: 'y', code: 123 }, schema, { abortEarly: false }, (err, value) => {
370
+
371
+ expect(err.message).to.equal('"txt" must not exist simultaneously with [upc, code]');
372
+
373
+ Helper.validate(schema, [
374
+ [{}, true],
375
+ [{ upc: null }, true],
376
+ [{ upc: 'test' }, true],
377
+ [{ txt: 'test' }, true],
378
+ [{ code: 123 }, true],
379
+ [{ txt: 'test', upc: null }, true],
380
+ [{ txt: 'test', upc: '' }, true],
381
+ [{ txt: undefined, upc: 'test' }, true],
382
+ [{ txt: 'test', upc: undefined }, true],
383
+ [{ txt: 'test', upc: '' }, true],
384
+ [{ txt: 'test', upc: null }, true],
385
+ [{ txt: 'test', upc: undefined, code: 999 }, true],
386
+ [{ txt: 'test', upc: 'test' }, true],
387
+ [{ txt: 'test', upc: 'test', code: 322 }, false],
388
+ [{ txt: 'test', upc: null, code: 322 }, false]
389
+ ], done);
390
+ });
391
+ });
392
+
393
+ it('validates an array of valid types', (done) => {
394
+
395
+ const schema = Joi.object({
396
+ auth: [
397
+ Joi.object({
398
+ mode: Joi.string().valid('required', 'optional', 'try').allow(null)
399
+ }).allow(null),
400
+ Joi.string(),
401
+ Joi.boolean()
402
+ ]
403
+ });
404
+
405
+ schema.validate({ auth: { mode: 'none' } }, (err, value) => {
406
+
407
+ expect(err).to.exist();
408
+ expect(err.message).to.equal('child "auth" fails because [child "mode" fails because ["mode" must be one of [required, optional, try, null]], "auth" must be a string, "auth" must be a boolean]');
409
+
410
+ Helper.validate(schema, [
411
+ [{ auth: { mode: 'try' } }, true],
412
+ [{ something: undefined }, false],
413
+ [{ auth: { something: undefined } }, false],
414
+ [{ auth: null }, true],
415
+ [{ auth: undefined }, true],
416
+ [{}, true],
417
+ [{ auth: true }, true],
418
+ [{ auth: 123 }, false]
419
+ ], done);
420
+ });
421
+ });
422
+
423
+ it('validates alternatives', (done) => {
424
+
425
+ const schema = Joi.object({
426
+ auth: Joi.alternatives(
427
+ Joi.object({
428
+ mode: Joi.string().valid('required', 'optional', 'try').allow(null)
429
+ }).allow(null),
430
+ Joi.string(),
431
+ Joi.boolean()
432
+ )
433
+ });
434
+
435
+ schema.validate({ auth: { mode: 'none' } }, (err, value) => {
436
+
437
+ expect(err).to.exist();
438
+ expect(err.message).to.equal('child "auth" fails because [child "mode" fails because ["mode" must be one of [required, optional, try, null]], "auth" must be a string, "auth" must be a boolean]');
439
+
440
+ Helper.validate(schema, [
441
+ [{ auth: { mode: 'try' } }, true],
442
+ [{ something: undefined }, false],
443
+ [{ auth: { something: undefined } }, false],
444
+ [{ auth: null }, true],
445
+ [{ auth: undefined }, true],
446
+ [{}, true],
447
+ [{ auth: true }, true],
448
+ [{ auth: 123 }, false]
449
+ ], done);
450
+ });
451
+ });
452
+
453
+ it('validates required alternatives', (done) => {
454
+
455
+ const schema = {
456
+ a: Joi.alternatives(
457
+ Joi.string().required(),
458
+ Joi.boolean().required()
459
+ )
460
+ };
461
+
462
+ Helper.validate(schema, [
463
+ [{ a: null }, false],
464
+ [{ a: undefined }, true],
465
+ [{}, true],
466
+ [{ a: true }, true],
467
+ [{ a: 'true' }, true],
468
+ [{ a: 123 }, false],
469
+ [{ a: { c: 1 } }, false],
470
+ [{ b: undefined }, false]
471
+ ], done);
472
+ });
473
+
474
+ it('validates required [] alternatives', (done) => {
475
+
476
+ const schema = {
477
+ a: [
478
+ Joi.string().required(),
479
+ Joi.boolean().required()
480
+ ]
481
+ };
482
+
483
+ Helper.validate(schema, [
484
+ [{ a: null }, false],
485
+ [{ a: undefined }, true],
486
+ [{}, true],
487
+ [{ a: true }, true],
488
+ [{ a: 'true' }, true],
489
+ [{ a: 123 }, false],
490
+ [{ a: { c: 1 } }, false],
491
+ [{ b: undefined }, false]
492
+ ], done);
493
+ });
494
+
495
+ it('validates an array of string with valid', (done) => {
496
+
497
+ const schema = {
498
+ brand: Joi.array().items(Joi.string().valid('amex', 'visa'))
499
+ };
500
+
501
+ Helper.validate(schema, [
502
+ [{ brand: ['amex'] }, true],
503
+ [{ brand: ['visa', 'mc'] }, false]
504
+ ], done);
505
+ });
506
+
507
+ it('validates pre and post convert value', (done) => {
508
+
509
+ const schema = Joi.number().valid(5);
510
+
511
+ Helper.validate(schema, [
512
+ [5, true],
513
+ ['5', true]
514
+ ], done);
515
+ });
516
+
517
+ it('does not change object when validation fails', (done) => {
518
+
519
+ const schema = Joi.object({
520
+ a: Joi.number().valid(2)
521
+ });
522
+
523
+ const obj = {
524
+ a: '5'
525
+ };
526
+
527
+ schema.validate(obj, (err, value) => {
528
+
529
+ expect(err).to.exist();
530
+ expect(value.a).to.equal('5');
531
+ done();
532
+ });
533
+ });
534
+
535
+ it('does not set optional keys when missing', (done) => {
536
+
537
+ const schema = Joi.object({
538
+ a: Joi.number()
539
+ });
540
+
541
+ const obj = {};
542
+
543
+ schema.validate(obj, (err, value) => {
544
+
545
+ expect(err).to.not.exist();
546
+ expect(value.hasOwnProperty('a')).to.equal(false);
547
+ done();
548
+ });
549
+ });
550
+
551
+ it('invalidates pre and post convert value', (done) => {
552
+
553
+ const schema = Joi.number().invalid(5);
554
+
555
+ Helper.validate(schema, [
556
+ [5, false],
557
+ ['5', false]
558
+ ], done);
559
+ });
560
+
561
+ it('invalidates missing peers', (done) => {
562
+
563
+ const schema = Joi.object({
564
+ username: Joi.string(),
565
+ password: Joi.string()
566
+ }).with('username', 'password').without('password', 'access_token');
567
+
568
+ schema.validate({ username: 'bob' }, (err, value) => {
569
+
570
+ expect(err).to.exist();
571
+ done();
572
+ });
573
+ });
574
+
575
+ it('validates config where the root item is a joi type', (done) => {
576
+
577
+ Joi.boolean().allow(null).validate(true, (err, value) => {
578
+
579
+ expect(err).to.be.null();
580
+ Joi.object().validate({ auth: { mode: 'try' } }, (err2, value2) => {
581
+
582
+ expect(err2).to.be.null();
583
+
584
+ Joi.object().validate(true, (err3, value3) => {
585
+
586
+ expect(err3.message).to.contain('"value" must be an object');
587
+
588
+ Joi.string().validate(true, (err4, value4) => {
589
+
590
+ expect(err4.message).to.contain('"value" must be a string');
591
+
592
+ Joi.string().email().validate('test@test.com', (err5, value5) => {
593
+
594
+ expect(err5).to.be.null();
595
+ Joi.object({ param: Joi.string().required() }).validate({ param: 'item' }, (err6, value6) => {
596
+
597
+ expect(err6).to.be.null();
598
+ done();
599
+ });
600
+ });
601
+ });
602
+ });
603
+ });
604
+ });
605
+ });
606
+
607
+ it('converts string to number', (done) => {
608
+
609
+ const schema = Joi.object({
610
+ a: Joi.number()
611
+ });
612
+
613
+ const input = { a: '5' };
614
+ schema.validate(input, (err, value) => {
615
+
616
+ expect(err).to.be.null();
617
+ expect(value.a).to.equal(5);
618
+ expect(input.a).to.equal('5');
619
+ done();
620
+ });
621
+ });
622
+
623
+ it('allows unknown keys in objects if no schema was given', (done) => {
624
+
625
+ Joi.object().validate({ foo: 'bar' }, (err, value) => {
626
+
627
+ expect(err).to.not.exist();
628
+ done();
629
+ });
630
+ });
631
+
632
+ it('fails on unknown keys in objects if a schema was given', (done) => {
633
+
634
+ Joi.object({}).validate({ foo: 'bar' }, (err, value) => {
635
+
636
+ expect(err).to.exist();
637
+ expect(err.message).to.equal('"foo" is not allowed');
638
+
639
+ Joi.compile({}).validate({ foo: 'bar' }, (err2, value2) => {
640
+
641
+ expect(err2).to.exist();
642
+ expect(err2.message).to.equal('"foo" is not allowed');
643
+
644
+ Joi.compile({ other: Joi.number() }).validate({ foo: 'bar' }, (err3, value3) => {
645
+
646
+ expect(err3).to.exist();
647
+ expect(err3.message).to.equal('"foo" is not allowed');
648
+
649
+ done();
650
+ });
651
+ });
652
+ });
653
+ });
654
+
655
+ it('validates an unknown option', (done) => {
656
+
657
+ const config = {
658
+ auth: Joi.object({
659
+ mode: Joi.string().valid('required', 'optional', 'try').allow(null)
660
+ }).allow(null)
661
+ };
662
+
663
+ Joi.compile(config).validate({ auth: { unknown: true } }, (err, value) => {
664
+
665
+ expect(err).to.not.be.null();
666
+ expect(err.message).to.contain('"unknown" is not allowed');
667
+
668
+ Joi.compile(config).validate({ something: false }, (err2, value2) => {
669
+
670
+ expect(err2).to.not.be.null();
671
+ expect(err2.message).to.contain('"something" is not allowed');
672
+
673
+ done();
674
+ });
675
+ });
676
+ });
677
+
678
+ it('validates required key with multiple options', (done) => {
679
+
680
+ const config = {
681
+ module: Joi.alternatives([
682
+ Joi.object({
683
+ compile: Joi.func().required(),
684
+ execute: Joi.func()
685
+ }),
686
+ Joi.string()
687
+ ]).required()
688
+ };
689
+
690
+ Joi.compile(config).validate({}, (err, value) => {
691
+
692
+ expect(err).to.exist();
693
+ expect(err.message).to.contain('"module" is required');
694
+
695
+ Joi.compile(config).validate({ module: 'test' }, (err2, value2) => {
696
+
697
+ expect(err2).to.be.null();
698
+
699
+ Joi.compile(config).validate({ module: {} }, (err3, value3) => {
700
+
701
+ expect(err3).to.not.be.null();
702
+ expect(err3.message).to.contain('"compile" is required');
703
+ expect(err3.message).to.contain('"module" must be a string');
704
+
705
+ Joi.compile(config).validate({ module: { compile: function () { } } }, (err4, value4) => {
706
+
707
+ expect(err4).to.be.null();
708
+ done();
709
+ });
710
+ });
711
+ });
712
+ });
713
+ });
714
+
715
+ it('validates key with required alternatives', (done) => {
716
+
717
+ const config = {
718
+ module: Joi.alt().try(
719
+ Joi.object({
720
+ compile: Joi.func().required(),
721
+ execute: Joi.func()
722
+ }).required(),
723
+ Joi.string().required()
724
+ )
725
+ };
726
+
727
+ Joi.compile(config).validate({}, (err, value) => {
728
+
729
+ expect(err).to.not.exist();
730
+ done();
731
+ });
732
+ });
733
+
734
+ it('validates required key with alternatives', (done) => {
735
+
736
+ const config = {
737
+ module: Joi.alt().try(
738
+ Joi.object({
739
+ compile: Joi.func().required(),
740
+ execute: Joi.func()
741
+ }),
742
+ Joi.string()
743
+ ).required()
744
+ };
745
+
746
+ Joi.compile(config).validate({}, (err, value) => {
747
+
748
+ expect(err).to.exist();
749
+ expect(err.message).to.contain('"module" is required');
750
+ done();
751
+ });
752
+ });
753
+
754
+ it('does not require optional numbers', (done) => {
755
+
756
+ const config = {
757
+ position: Joi.number(),
758
+ suggestion: Joi.string()
759
+ };
760
+
761
+ Joi.compile(config).validate({ suggestion: 'something' }, (err, value) => {
762
+
763
+ expect(err).to.be.null();
764
+
765
+ Joi.compile(config).validate({ position: 1 }, (err2, value2) => {
766
+
767
+ expect(err2).to.be.null();
768
+ done();
769
+ });
770
+ });
771
+ });
772
+
773
+ it('does not require optional objects', (done) => {
774
+
775
+ const config = {
776
+ position: Joi.number(),
777
+ suggestion: Joi.object()
778
+ };
779
+
780
+ Joi.compile(config).validate({ suggestion: {} }, (err, value) => {
781
+
782
+ expect(err).to.be.null();
783
+
784
+ Joi.compile(config).validate({ position: 1 }, (err2, value2) => {
785
+
786
+ expect(err2).to.be.null();
787
+ done();
788
+ });
789
+ });
790
+ });
791
+
792
+ it('validates object successfully when config has an array of types', (done) => {
793
+
794
+ const schema = {
795
+ f: [Joi.number(), Joi.boolean()],
796
+ g: [Joi.string(), Joi.object()]
797
+ };
798
+
799
+ const obj = {
800
+ f: true,
801
+ g: 'test'
802
+ };
803
+
804
+ Joi.compile(schema).validate(obj, (err, value) => {
805
+
806
+ expect(err).to.not.exist();
807
+ done();
808
+ });
809
+ });
810
+
811
+ it('validates object successfully when config allows for optional key and key is missing', (done) => {
812
+
813
+ const schema = {
814
+ h: Joi.number(),
815
+ i: Joi.string(),
816
+ j: Joi.object()
817
+ };
818
+
819
+ const obj = {
820
+ h: 12,
821
+ i: 'test'
822
+ };
823
+
824
+ Joi.compile(schema).validate(obj, (err, value) => {
825
+
826
+ expect(err).to.not.exist();
827
+ done();
828
+ });
829
+ });
830
+
831
+ it('fails validation', (done) => {
832
+
833
+ const schema = {
834
+ a: Joi.number().min(0).max(3),
835
+ b: Joi.string().valid('a', 'b', 'c'),
836
+ c: Joi.string().email().optional()
837
+ };
838
+
839
+ const obj = {
840
+ a: 10,
841
+ b: 'a',
842
+ c: 'joe@example.com'
843
+ };
844
+
845
+ Joi.compile(schema).validate(obj, (err, value) => {
846
+
847
+ expect(err).to.exist();
848
+ done();
849
+ });
850
+ });
851
+
852
+ it('fails validation when the wrong types are supplied', (done) => {
853
+
854
+ const schema = {
855
+ a: Joi.number().min(0).max(3),
856
+ b: Joi.string().valid('a', 'b', 'c'),
857
+ c: Joi.string().email().optional()
858
+ };
859
+
860
+ const obj = {
861
+ a: 'a',
862
+ b: 'a',
863
+ c: 'joe@example.com'
864
+ };
865
+
866
+ Joi.compile(schema).validate(obj, (err, value) => {
867
+
868
+ expect(err).to.exist();
869
+ done();
870
+ });
871
+ });
872
+
873
+ it('fails validation when missing a required parameter', (done) => {
874
+
875
+ const obj = {
876
+ c: 10
877
+ };
878
+
879
+ Joi.compile({ a: Joi.string().required() }).validate(obj, (err, value) => {
880
+
881
+ expect(err).to.exist();
882
+ done();
883
+ });
884
+ });
885
+
886
+ it('fails validation when missing a required parameter within an object config', (done) => {
887
+
888
+ const obj = {
889
+ a: {}
890
+ };
891
+
892
+ Joi.compile({ a: Joi.object({ b: Joi.string().required() }) }).validate(obj, (err, value) => {
893
+
894
+ expect(err).to.exist();
895
+ done();
896
+ });
897
+ });
898
+
899
+ it('fails validation when parameter is required to be an object but is given as string', (done) => {
900
+
901
+ const obj = {
902
+ a: 'a string'
903
+ };
904
+
905
+ Joi.compile({ a: Joi.object({ b: Joi.string().required() }) }).validate(obj, (err, value) => {
906
+
907
+ expect(err).to.exist();
908
+ done();
909
+ });
910
+ });
911
+
912
+ it('validates when parameter is required to be an object and is given correctly as a json string', (done) => {
913
+
914
+ const schema = {
915
+ a: Joi.object({
916
+ b: Joi.string().required()
917
+ })
918
+ };
919
+
920
+ const input = {
921
+ a: '{"b":"string"}'
922
+ };
923
+
924
+ Joi.validate(input, schema, (err, value) => {
925
+
926
+ expect(err).to.not.exist();
927
+ expect(input.a).to.equal('{"b":"string"}');
928
+ expect(value.a.b).to.equal('string');
929
+ done();
930
+ });
931
+ });
932
+
933
+ it('fails validation when parameter is required to be an object but is given as a json string that is incorrect (number instead of string)', (done) => {
934
+
935
+ const obj = {
936
+ a: '{"b":2}'
937
+ };
938
+
939
+ Joi.object({ a: Joi.object({ b: Joi.string().required() }) }).validate(obj, (err, value) => {
940
+
941
+ expect(err).to.exist();
942
+ done();
943
+ });
944
+ });
945
+
946
+ it('fails validation when parameter is required to be an Array but is given as string', (done) => {
947
+
948
+ const obj = {
949
+ a: 'an array'
950
+ };
951
+
952
+ Joi.object({ a: Joi.array() }).validate(obj, (err, value) => {
953
+
954
+ expect(err).to.exist();
955
+ done();
956
+ });
957
+ });
958
+
959
+ it('validates when parameter is required to be an Array and is given correctly as a json string', (done) => {
960
+
961
+ const obj = {
962
+ a: '[1,2]'
963
+ };
964
+
965
+ Joi.object({ a: Joi.array() }).validate(obj, (err, value) => {
966
+
967
+ expect(err).to.be.null();
968
+ done();
969
+ });
970
+ });
971
+
972
+ it('fails validation when parameter is required to be an Array but is given as a json that is incorrect (object instead of array)', (done) => {
973
+
974
+ const obj = {
975
+ a: '{"b":2}'
976
+ };
977
+
978
+ Joi.object({ a: Joi.object({ b: Joi.string().required() }) }).validate(obj, (err, value) => {
979
+
980
+ expect(err).to.exist();
981
+ done();
982
+ });
983
+ });
984
+
985
+ it('fails validation when config is an array and fails', (done) => {
986
+
987
+ const schema = {
988
+ d: [Joi.string(), Joi.boolean()],
989
+ e: [Joi.number(), Joi.object()]
990
+ };
991
+
992
+ const obj = {
993
+ d: 10,
994
+ e: 'a'
995
+ };
996
+
997
+ Joi.compile(schema).validate(obj, (err, value) => {
998
+
999
+ expect(err).to.exist();
1000
+ done();
1001
+ });
1002
+ });
1003
+
1004
+ it('fails validation when config is an array and fails with extra keys', (done) => {
1005
+
1006
+ const schema = {
1007
+ d: [Joi.string(), Joi.boolean()],
1008
+ e: [Joi.number(), Joi.object()]
1009
+ };
1010
+
1011
+ const obj = {
1012
+ a: 10,
1013
+ b: 'a'
1014
+ };
1015
+
1016
+ Joi.compile(schema).validate(obj, (err, value) => {
1017
+
1018
+ expect(err).to.exist();
1019
+ done();
1020
+ });
1021
+ });
1022
+
1023
+ it('fails validation with extra keys', (done) => {
1024
+
1025
+ const schema = {
1026
+ a: Joi.number()
1027
+ };
1028
+
1029
+ const obj = {
1030
+ a: 1,
1031
+ b: 'a'
1032
+ };
1033
+
1034
+ Joi.compile(schema).validate(obj, (err, value) => {
1035
+
1036
+ expect(err).to.exist();
1037
+ done();
1038
+ });
1039
+ });
1040
+
1041
+ it('validates missing optional key with string condition', (done) => {
1042
+
1043
+ const schema = {
1044
+ key: Joi.string().alphanum(false).min(8)
1045
+ };
1046
+
1047
+ Joi.compile(schema).validate({}, (err, value) => {
1048
+
1049
+ expect(err).to.not.exist();
1050
+ done();
1051
+ });
1052
+ });
1053
+
1054
+ it('validates with extra keys and remove them when stripUnknown is set', (done) => {
1055
+
1056
+ const schema = {
1057
+ a: Joi.number().min(0).max(3),
1058
+ b: Joi.string().valid('a', 'b', 'c'),
1059
+ c: Joi.string().email().optional()
1060
+ };
1061
+
1062
+ const obj = {
1063
+ a: 1,
1064
+ b: 'a',
1065
+ d: 'c'
1066
+ };
1067
+
1068
+ Joi.validate(obj, schema, { stripUnknown: true, allowUnknown: true }, (err, value) => {
1069
+
1070
+ expect(err).to.be.null();
1071
+ expect(value).to.deep.equal({ a: 1, b: 'a' });
1072
+ done();
1073
+ });
1074
+ });
1075
+
1076
+ it('validates dependencies when stripUnknown is set', (done) => {
1077
+
1078
+ const schema = Joi.object({
1079
+ a: Joi.number(),
1080
+ b: Joi.string()
1081
+ }).and('a', 'b');
1082
+
1083
+ const obj = {
1084
+ a: 1,
1085
+ foo: 'bar'
1086
+ };
1087
+
1088
+ Joi.validate(obj, schema, { stripUnknown: true }, (err, value) => {
1089
+
1090
+ expect(err).to.exist();
1091
+ expect(err.message).to.equal('"value" contains [a] without its required peers [b]');
1092
+ done();
1093
+ });
1094
+ });
1095
+
1096
+ it('fails to validate with incorrect property when asked to strip unkown keys without aborting early', (done) => {
1097
+
1098
+ const schema = {
1099
+ a: Joi.number().min(0).max(3),
1100
+ b: Joi.string().valid('a', 'b', 'c'),
1101
+ c: Joi.string().email().optional()
1102
+ };
1103
+
1104
+ const obj = {
1105
+ a: 1,
1106
+ b: 'f',
1107
+ d: 'c'
1108
+ };
1109
+
1110
+ Joi.validate(obj, schema, { stripUnknown: true, abortEarly: false }, (err, value) => {
1111
+
1112
+ expect(err).to.exist();
1113
+ done();
1114
+ });
1115
+ });
1116
+
1117
+ it('should pass validation with extra keys when allowUnknown is set', (done) => {
1118
+
1119
+ const schema = {
1120
+ a: Joi.number().min(0).max(3),
1121
+ b: Joi.string().valid('a', 'b', 'c'),
1122
+ c: Joi.string().email().optional()
1123
+ };
1124
+
1125
+ const obj = {
1126
+ a: 1,
1127
+ b: 'a',
1128
+ d: 'c'
1129
+ };
1130
+
1131
+ Joi.validate(obj, schema, { allowUnknown: true }, (err, value) => {
1132
+
1133
+ expect(err).to.be.null();
1134
+ expect(value).to.deep.equal({ a: 1, b: 'a', d: 'c' });
1135
+ done();
1136
+ });
1137
+ });
1138
+
1139
+ it('should pass validation with extra keys set', (done) => {
1140
+
1141
+ const localConfig = Joi.object({
1142
+ a: Joi.number().min(0).max(3),
1143
+ b: Joi.string().valid('a', 'b', 'c')
1144
+ }).options({ allowUnknown: true });
1145
+
1146
+ const obj = {
1147
+ a: 1,
1148
+ b: 'a',
1149
+ d: 'c'
1150
+ };
1151
+
1152
+ localConfig.validate(obj, (err, value) => {
1153
+
1154
+ expect(err).to.be.null();
1155
+ expect(value).to.deep.equal({ a: 1, b: 'a', d: 'c' });
1156
+
1157
+ localConfig.validate(value, (err2, value2) => {
1158
+
1159
+ expect(err2).to.be.null();
1160
+ expect(value2).to.deep.equal({ a: 1, b: 'a', d: 'c' });
1161
+ done();
1162
+ });
1163
+ });
1164
+ });
1165
+
1166
+ it('should pass validation with extra keys and remove them when skipExtraKeys is set locally', (done) => {
1167
+
1168
+ const localConfig = Joi.object({
1169
+ a: Joi.number().min(0).max(3),
1170
+ b: Joi.string().valid('a', 'b', 'c')
1171
+ }).options({ stripUnknown: true, allowUnknown: true });
1172
+
1173
+ const obj = {
1174
+ a: 1,
1175
+ b: 'a',
1176
+ d: 'c'
1177
+ };
1178
+
1179
+ localConfig.validate(obj, (err, value) => {
1180
+
1181
+ expect(err).to.be.null();
1182
+ expect(value).to.deep.equal({ a: 1, b: 'a' });
1183
+
1184
+ localConfig.validate(value, (err2, value2) => {
1185
+
1186
+ expect(err2).to.be.null();
1187
+ expect(value2).to.deep.equal({ a: 1, b: 'a' });
1188
+ done();
1189
+ });
1190
+ });
1191
+ });
1192
+
1193
+ it('should work when the skipFunctions setting is enabled', (done) => {
1194
+
1195
+ const schema = Joi.object({ username: Joi.string() }).options({ skipFunctions: true });
1196
+ const input = { username: 'test', func: function () { } };
1197
+ Joi.validate(input, schema, (err, value) => {
1198
+
1199
+ expect(err).to.not.exist();
1200
+ done();
1201
+ });
1202
+ });
1203
+
1204
+ it('should work when the skipFunctions setting is disabled', (done) => {
1205
+
1206
+ const schema = { username: Joi.string() };
1207
+ const input = { username: 'test', func: function () { } };
1208
+
1209
+ Joi.validate(input, schema, { skipFunctions: false }, (err, value) => {
1210
+
1211
+ expect(err).to.exist();
1212
+ expect(err.message).to.contain('"func" is not allowed');
1213
+ done();
1214
+ });
1215
+ });
1216
+
1217
+ it('should not convert values when convert is false', (done) => {
1218
+
1219
+ const schema = {
1220
+ arr: Joi.array().items(Joi.string())
1221
+ };
1222
+
1223
+ const input = { arr: 'foo' };
1224
+ Joi.validate(input, schema, { convert: false }, (err, value) => {
1225
+
1226
+ expect(err).to.exist();
1227
+ done();
1228
+ });
1229
+ });
1230
+
1231
+ it('full errors when abortEarly is false', (done) => {
1232
+
1233
+ const schema = {
1234
+ a: Joi.string(),
1235
+ b: Joi.string()
1236
+ };
1237
+
1238
+ const input = { a: 1, b: 2 };
1239
+
1240
+ Joi.validate(input, schema, (errOne, valueOne) => {
1241
+
1242
+ Joi.validate(input, schema, { abortEarly: false }, (errFull, valueFull) => {
1243
+
1244
+ expect(errOne).to.exist();
1245
+ expect(errFull).to.exist();
1246
+ expect(errFull.details.length).to.be.greaterThan(errOne.details.length);
1247
+ done();
1248
+ });
1249
+ });
1250
+ });
1251
+
1252
+ it('errors multiple times when abortEarly is false in a complex object', (done) => {
1253
+
1254
+ const schema = Joi.object({
1255
+ test: Joi.array().items(Joi.object().keys({
1256
+ foo: Joi.string().required().max(3),
1257
+ bar: Joi.string().max(5)
1258
+ })),
1259
+ test2: Joi.object({
1260
+ test3: Joi.array().items(Joi.object().keys({
1261
+ foo: Joi.string().required().max(3),
1262
+ bar: Joi.string().max(5),
1263
+ baz: Joi.object({
1264
+ test4: Joi.array().items(Joi.object().keys({
1265
+ foo: Joi.string().required().max(3),
1266
+ bar: Joi.string().max(5)
1267
+ }))
1268
+ })
1269
+ }))
1270
+ })
1271
+ });
1272
+
1273
+ const input = {
1274
+ test: [{
1275
+ foo: 'test1',
1276
+ bar: 'testfailed'
1277
+ }],
1278
+ test2: {
1279
+ test3: [{
1280
+ foo: '123'
1281
+ }, {
1282
+ foo: 'test1',
1283
+ bar: 'testfailed'
1284
+ }, {
1285
+ foo: '123',
1286
+ baz: {
1287
+ test4: [{
1288
+ foo: 'test1',
1289
+ baz: '123'
1290
+ }]
1291
+ }
1292
+ }]
1293
+ }
1294
+ };
1295
+
1296
+ Joi.validate(input, schema, { abortEarly: false }, (err, value) => {
1297
+
1298
+ expect(err).to.exist();
1299
+ expect(err.details).to.have.length(6);
1300
+ expect(err.details).to.deep.equal([{
1301
+ message: '"foo" length must be less than or equal to 3 characters long',
1302
+ path: 'test.0.foo',
1303
+ type: 'string.max',
1304
+ context: { limit: 3, value: 'test1', key: 'foo', encoding: undefined }
1305
+ }, {
1306
+ message: '"bar" length must be less than or equal to 5 characters long',
1307
+ path: 'test.0.bar',
1308
+ type: 'string.max',
1309
+ context: { limit: 5, value: 'testfailed', key: 'bar', encoding: undefined }
1310
+ }, {
1311
+ message: '"foo" length must be less than or equal to 3 characters long',
1312
+ path: 'test2.test3.1.foo',
1313
+ type: 'string.max',
1314
+ context: { limit: 3, value: 'test1', key: 'foo', encoding: undefined }
1315
+ }, {
1316
+ message: '"bar" length must be less than or equal to 5 characters long',
1317
+ path: 'test2.test3.1.bar',
1318
+ type: 'string.max',
1319
+ context: { limit: 5, value: 'testfailed', key: 'bar', encoding: undefined }
1320
+ }, {
1321
+ message: '"foo" length must be less than or equal to 3 characters long',
1322
+ path: 'test2.test3.2.baz.test4.0.foo',
1323
+ type: 'string.max',
1324
+ context: { limit: 3, value: 'test1', key: 'foo', encoding: undefined }
1325
+ }, {
1326
+ message: '"baz" is not allowed',
1327
+ path: 'test2.test3.2.baz.test4.0.baz',
1328
+ type: 'object.allowUnknown',
1329
+ context: { key: 'baz' }
1330
+ }]);
1331
+ done();
1332
+ });
1333
+ });
1334
+
1335
+ it('validates using the root any object', (done) => {
1336
+
1337
+ const any = Joi;
1338
+ any.validate('abc', (err, value) => {
1339
+
1340
+ expect(err).to.not.exist();
1341
+ done();
1342
+ });
1343
+ });
1344
+
1345
+ it('validates using the root any object (no callback)', (done) => {
1346
+
1347
+ const any = Joi;
1348
+ const result = any.validate('abc');
1349
+ expect(result.error).to.not.exist();
1350
+ expect(result.value).to.equal('abc');
1351
+ done();
1352
+ });
1353
+
1354
+ it('accepts no options', (done) => {
1355
+
1356
+ Joi.validate('test', Joi.string(), (err, value) => {
1357
+
1358
+ expect(err).to.not.exist();
1359
+ done();
1360
+ });
1361
+ });
1362
+
1363
+ it('accepts no options (no callback)', (done) => {
1364
+
1365
+ const result = Joi.validate('test', Joi.string());
1366
+ expect(result.error).to.not.exist();
1367
+ expect(result.value).to.equal('test');
1368
+ done();
1369
+ });
1370
+
1371
+ it('accepts options', (done) => {
1372
+
1373
+ Joi.validate('5', Joi.number(), { convert: false }, (err, value) => {
1374
+
1375
+ expect(err).to.exist();
1376
+ done();
1377
+ });
1378
+ });
1379
+
1380
+ it('accepts options (no callback)', (done) => {
1381
+
1382
+ const result = Joi.validate('5', Joi.number(), { convert: false });
1383
+ expect(result.error).to.exist();
1384
+ done();
1385
+ });
1386
+
1387
+ it('accepts null options', (done) => {
1388
+
1389
+ Joi.validate('test', Joi.string(), null, (err, value) => {
1390
+
1391
+ expect(err).to.not.exist();
1392
+ done();
1393
+ });
1394
+ });
1395
+
1396
+ it('accepts undefined options', (done) => {
1397
+
1398
+ Joi.validate('test', Joi.string(), undefined, (err, value) => {
1399
+
1400
+ expect(err).to.not.exist();
1401
+ done();
1402
+ });
1403
+ });
1404
+
1405
+ describe('describe()', () => {
1406
+
1407
+ const defaultFn = function () {
1408
+
1409
+ return 'test';
1410
+ };
1411
+ defaultFn.description = 'testing';
1412
+
1413
+ const defaultDescribedFn = function () {
1414
+
1415
+ return 'test';
1416
+ };
1417
+
1418
+ const defaultRef = Joi.ref('xor');
1419
+
1420
+ const schema = Joi.object({
1421
+ sub: {
1422
+ email: Joi.string().email(),
1423
+ date: Joi.date(),
1424
+ child: Joi.object({
1425
+ alphanum: Joi.string().alphanum()
1426
+ })
1427
+ },
1428
+ min: [Joi.number(), Joi.string().min(3)],
1429
+ max: Joi.string().max(3).default(0),
1430
+ required: Joi.string().required(),
1431
+ xor: Joi.string(),
1432
+ renamed: Joi.string().valid('456'),
1433
+ notEmpty: Joi.string().required().description('a').notes('b').tags('c'),
1434
+ empty: Joi.string().empty('').strip(),
1435
+ defaultRef: Joi.string().default(defaultRef, 'not here'),
1436
+ defaultFn: Joi.string().default(defaultFn, 'not here'),
1437
+ defaultDescribedFn: Joi.string().default(defaultDescribedFn, 'described test')
1438
+ }).rename('renamed', 'required').without('required', 'xor').without('xor', 'required');
1439
+
1440
+ const result = {
1441
+ type: 'object',
1442
+ children: {
1443
+ sub: {
1444
+ type: 'object',
1445
+ children: {
1446
+ email: {
1447
+ type: 'string',
1448
+ invalids: [''],
1449
+ rules: [{ name: 'email' }]
1450
+ },
1451
+ date: {
1452
+ type: 'date'
1453
+ },
1454
+ child: {
1455
+ type: 'object',
1456
+ children: {
1457
+ alphanum: {
1458
+ type: 'string',
1459
+ invalids: [''],
1460
+ rules: [{ name: 'alphanum' }]
1461
+ }
1462
+ }
1463
+ }
1464
+ }
1465
+ },
1466
+ min: {
1467
+ type: 'alternatives',
1468
+ alternatives: [
1469
+ {
1470
+ type: 'number',
1471
+ invalids: [Infinity, -Infinity]
1472
+ },
1473
+ {
1474
+ type: 'string',
1475
+ invalids: [''],
1476
+ rules: [{ name: 'min', arg: 3 }]
1477
+ }
1478
+ ]
1479
+ },
1480
+ max: {
1481
+ type: 'string',
1482
+ flags: {
1483
+ default: 0
1484
+ },
1485
+ invalids: [''],
1486
+ rules: [{ name: 'max', arg: 3 }]
1487
+ },
1488
+ required: {
1489
+ type: 'string',
1490
+ flags: {
1491
+ presence: 'required'
1492
+ },
1493
+ invalids: ['']
1494
+ },
1495
+ xor: {
1496
+ type: 'string',
1497
+ invalids: ['']
1498
+ },
1499
+ renamed: {
1500
+ type: 'string',
1501
+ flags: {
1502
+ allowOnly: true
1503
+ },
1504
+ valids: ['456'],
1505
+ invalids: ['']
1506
+ },
1507
+ notEmpty: {
1508
+ type: 'string',
1509
+ flags: {
1510
+ presence: 'required'
1511
+ },
1512
+ description: 'a',
1513
+ notes: ['b'],
1514
+ tags: ['c'],
1515
+ invalids: ['']
1516
+ },
1517
+ empty: {
1518
+ type: 'string',
1519
+ flags: {
1520
+ empty: {
1521
+ type: 'string',
1522
+ flags: {
1523
+ allowOnly: true
1524
+ },
1525
+ valids: ['']
1526
+ },
1527
+ strip: true
1528
+ },
1529
+ invalids: ['']
1530
+ },
1531
+ defaultRef: {
1532
+ type: 'string',
1533
+ flags: {
1534
+ default: 'ref:xor'
1535
+ },
1536
+ invalids: ['']
1537
+ },
1538
+ defaultFn: {
1539
+ type: 'string',
1540
+ flags: {
1541
+ default: 'testing'
1542
+ },
1543
+ invalids: ['']
1544
+ },
1545
+ defaultDescribedFn: {
1546
+ type: 'string',
1547
+ flags: {
1548
+ default: 'described test'
1549
+ },
1550
+ invalids: ['']
1551
+ }
1552
+ },
1553
+ dependencies: [
1554
+ {
1555
+ type: 'without',
1556
+ key: 'required',
1557
+ peers: ['xor']
1558
+ },
1559
+ {
1560
+ type: 'without',
1561
+ key: 'xor',
1562
+ peers: ['required']
1563
+ }
1564
+ ]
1565
+ };
1566
+
1567
+ it('describes schema (direct)', (done) => {
1568
+
1569
+ const description = schema.describe();
1570
+ expect(description).to.deep.equal(result);
1571
+ expect(description.children.defaultRef.flags.default).to.equal('ref:xor');
1572
+ expect(description.children.defaultFn.flags.default).to.equal('testing');
1573
+ expect(description.children.defaultDescribedFn.flags.default).to.equal('described test');
1574
+ done();
1575
+ });
1576
+
1577
+ it('describes schema (root)', (done) => {
1578
+
1579
+ const description = Joi.describe(schema);
1580
+ expect(description).to.deep.equal(result);
1581
+ done();
1582
+ });
1583
+
1584
+ it('describes schema (any)', (done) => {
1585
+
1586
+ const any = Joi;
1587
+ const description = any.describe();
1588
+ expect(description).to.deep.equal({
1589
+ type: 'any'
1590
+ });
1591
+ done();
1592
+ });
1593
+
1594
+ it('describes schema without invalids', (done) => {
1595
+
1596
+ const description = Joi.allow(null).describe();
1597
+ expect(description.invalids).to.not.exist();
1598
+ done();
1599
+ });
1600
+ });
1601
+
1602
+ describe('assert()', () => {
1603
+
1604
+ it('does not have a return value', (done) => {
1605
+
1606
+ let result;
1607
+ expect(() => {
1608
+
1609
+ result = Joi.assert('4', Joi.number());
1610
+ }).to.not.throw();
1611
+ expect(result).to.not.exist();
1612
+ done();
1613
+ });
1614
+ });
1615
+
1616
+ describe('attempt()', () => {
1617
+
1618
+ it('throws on invalid value', (done) => {
1619
+
1620
+ expect(() => {
1621
+
1622
+ Joi.attempt('x', Joi.number());
1623
+ }).to.throw('"value" must be a number');
1624
+ done();
1625
+ });
1626
+
1627
+ it('does not throw on valid value', (done) => {
1628
+
1629
+ expect(() => {
1630
+
1631
+ Joi.attempt('4', Joi.number());
1632
+ }).to.not.throw();
1633
+ done();
1634
+ });
1635
+
1636
+ it('returns validated structure', (done) => {
1637
+
1638
+ let valid;
1639
+ expect(() => {
1640
+
1641
+ valid = Joi.attempt('4', Joi.number());
1642
+ }).to.not.throw();
1643
+ expect(valid).to.equal(4);
1644
+ done();
1645
+ });
1646
+
1647
+ it('throws on invalid value with message', (done) => {
1648
+
1649
+ expect(() => {
1650
+
1651
+ Joi.attempt('x', Joi.number(), 'the reason is');
1652
+ }).to.throw('the reason is "value" must be a number');
1653
+ done();
1654
+ });
1655
+
1656
+ it('throws on invalid value with message as error', (done) => {
1657
+
1658
+ expect(() => {
1659
+
1660
+ Joi.attempt('x', Joi.number(), new Error('invalid value'));
1661
+ }).to.throw('invalid value');
1662
+ done();
1663
+ });
1664
+ });
1665
+
1666
+ describe('compile()', () => {
1667
+
1668
+ it('throws an error on invalid value', (done) => {
1669
+
1670
+ expect(() => {
1671
+
1672
+ Joi.compile(undefined);
1673
+ }).to.throw(Error, 'Invalid schema content: ');
1674
+ done();
1675
+ });
1676
+
1677
+ it('shows path to errors in object', (done) => {
1678
+
1679
+ const schema = {
1680
+ a: {
1681
+ b: {
1682
+ c: {
1683
+ d: undefined
1684
+ }
1685
+ }
1686
+ }
1687
+ };
1688
+
1689
+ expect(() => {
1690
+
1691
+ Joi.compile(schema);
1692
+ }).to.throw(Error, 'Invalid schema content: (a.b.c.d)');
1693
+ done();
1694
+ });
1695
+ });
1696
+
1697
+ describe('reach()', () => {
1698
+
1699
+ it('should fail without any parameter', (done) => {
1700
+
1701
+ expect(() => Joi.reach()).to.throw('you must provide a joi schema');
1702
+ done();
1703
+ });
1704
+
1705
+ it('should fail when schema is not a joi object', (done) => {
1706
+
1707
+ expect(() => Joi.reach({ foo: 'bar' }, 'foo')).to.throw('you must provide a joi schema');
1708
+ done();
1709
+ });
1710
+
1711
+ it('should fail without a proper path', (done) => {
1712
+
1713
+ const schema = Joi.object();
1714
+ expect(() => Joi.reach(schema)).to.throw('path must be a string');
1715
+ expect(() => Joi.reach(schema, true)).to.throw('path must be a string');
1716
+ done();
1717
+ });
1718
+
1719
+ it('should return undefined when no keys are defined', (done) => {
1720
+
1721
+ const schema = Joi.object();
1722
+ expect(Joi.reach(schema, 'a')).to.be.undefined();
1723
+ done();
1724
+ });
1725
+
1726
+ it('should return undefined when key is not found', (done) => {
1727
+
1728
+ const schema = Joi.object().keys({ a: Joi.number() });
1729
+ expect(Joi.reach(schema, 'foo')).to.be.undefined();
1730
+ done();
1731
+ });
1732
+
1733
+ it('should return a schema when key is found', (done) => {
1734
+
1735
+ const a = Joi.number();
1736
+ const schema = Joi.object().keys({ a });
1737
+ expect(Joi.reach(schema, 'a')).to.equal(a);
1738
+ done();
1739
+ });
1740
+
1741
+ it('should return undefined on a schema that does not support reach', (done) => {
1742
+
1743
+ const schema = Joi.number();
1744
+ expect(Joi.reach(schema, 'a')).to.be.undefined();
1745
+ done();
1746
+ });
1747
+
1748
+ it('should return a schema when deep key is found', (done) => {
1749
+
1750
+ const bar = Joi.number();
1751
+ const schema = Joi.object({ foo: Joi.object({ bar }) });
1752
+ expect(Joi.reach(schema, 'foo.bar')).to.equal(bar);
1753
+ done();
1754
+ });
1755
+
1756
+ it('should return undefined when deep key is not found', (done) => {
1757
+
1758
+ const schema = Joi.object({ foo: Joi.object({ bar: Joi.number() }) });
1759
+ expect(Joi.reach(schema, 'foo.baz')).to.be.undefined();
1760
+ done();
1761
+ });
1762
+ });
1763
+
1764
+ describe('extend()', () => {
1765
+
1766
+ describe('parameters', () => {
1767
+
1768
+ it('must be an object or array of objects', (done) => {
1769
+
1770
+ expect(() => Joi.extend(true)).to.throw(/"0" must be an object/);
1771
+ expect(() => Joi.extend(null)).to.throw(/"0" must be an object/);
1772
+ expect(() => Joi.extend([{ name: 'foo' }, true])).to.throw(/"1" must be an object/);
1773
+ expect(() => Joi.extend([{ name: 'foo' }, null])).to.throw(/"1" must be an object/);
1774
+ expect(() => Joi.extend()).to.throw('You need to provide at least one extension');
1775
+ done();
1776
+ });
1777
+
1778
+ it('must have a valid string as name for the type', (done) => {
1779
+
1780
+ expect(() => Joi.extend({ base: Joi.number() })).to.throw(/"name" is required/);
1781
+ expect(() => Joi.extend({ name: 123 })).to.throw(/"name" must be a string/);
1782
+ expect(() => Joi.extend({ name: '' })).to.throw(/"name" is not allowed to be empty/);
1783
+ done();
1784
+ });
1785
+
1786
+ it('must have a Joi schema as base when present', (done) => {
1787
+
1788
+ expect(() => Joi.extend({ base: true })).to.throw(/"base" must be an object/);
1789
+ expect(() => Joi.extend({ base: { isJoi: true } })).to.throw(/"base" must be an instance of "Joi object"/);
1790
+ done();
1791
+ });
1792
+
1793
+ it('must have valid pre function', (done) => {
1794
+
1795
+ expect(() => Joi.extend({ name: 'a', pre: true })).to.throw(/"pre" must be a Function/);
1796
+ expect(() => Joi.extend({ name: 'a', pre() {} })).to.throw(/"pre" must have an arity of 3/);
1797
+ expect(() => Joi.extend({ name: 'a', pre(a, b) {} })).to.throw(/"pre" must have an arity of 3/);
1798
+ expect(() => Joi.extend({ name: 'a', pre(a, b, c, d) {} })).to.throw(/"pre" must have an arity of 3/);
1799
+ done();
1800
+ });
1801
+
1802
+ it('must have valid language object', (done) => {
1803
+
1804
+ expect(() => Joi.extend({ name: 'a', language: true })).to.throw(/"language" must be an object/);
1805
+ expect(() => Joi.extend({ name: 'a', language() {} })).to.throw(/"language" must be an object/);
1806
+ expect(() => Joi.extend({ name: 'a', language: null })).to.throw(/"language" must be an object/);
1807
+ done();
1808
+ });
1809
+
1810
+ it('must have valid rules', (done) => {
1811
+
1812
+ expect(() => Joi.extend({ name: 'a', rules: true })).to.throw(/"rules" must be an array/);
1813
+ expect(() => Joi.extend({ name: 'a', rules: [true] })).to.throw(/"0" must be an object/);
1814
+ expect(() => Joi.extend({ name: 'a', rules: [{}] })).to.throw(/"name" is required/);
1815
+ expect(() => Joi.extend({ name: 'a', rules: [{ name: true }] })).to.throw(/"name" must be a string/);
1816
+ expect(() => Joi.extend({ name: 'a', rules: [{ name: 'foo' }] })).to.throw(/must contain at least one of \[setup, validate\]/);
1817
+
1818
+ expect(() => {
1819
+
1820
+ Joi.extend({ name: 'a', rules: [{ name: 'foo', validate: true }] });
1821
+ }).to.throw(/"validate" must be a Function/);
1822
+
1823
+ expect(() => {
1824
+
1825
+ Joi.extend({
1826
+ name: 'a', rules: [{
1827
+ name: 'foo',
1828
+ validate() {}
1829
+ }]
1830
+ });
1831
+ }).to.throw(/"validate" must have an arity of 4/);
1832
+
1833
+ expect(() => {
1834
+
1835
+ Joi.extend({ name: 'a', rules: [{ name: 'foo', setup: true }] });
1836
+ }).to.throw(/"setup" must be a Function/);
1837
+
1838
+ expect(() => {
1839
+
1840
+ Joi.extend({
1841
+ name: 'a', rules: [{
1842
+ name: 'foo',
1843
+ setup() {}
1844
+ }]
1845
+ });
1846
+ }).to.throw(/"setup" must have an arity of 1/);
1847
+
1848
+ expect(() => {
1849
+
1850
+ Joi.extend({
1851
+ name: 'a', rules: [{
1852
+ name: 'foo',
1853
+ validate(a, b, c, d) {},
1854
+ params: {
1855
+ foo: true
1856
+ }
1857
+ }]
1858
+ });
1859
+ }).to.throw(/"foo" must be an object/);
1860
+
1861
+ expect(() => {
1862
+
1863
+ Joi.extend({
1864
+ name: 'a', rules: [{
1865
+ name: 'foo',
1866
+ validate(a, b, c, d) {},
1867
+ params: {
1868
+ foo: {}
1869
+ }
1870
+ }]
1871
+ });
1872
+ }).to.throw(/"foo" must be an instance of "Joi object"/);
1873
+
1874
+ expect(() => {
1875
+
1876
+ Joi.extend({
1877
+ name: 'a', rules: [{
1878
+ name: 'foo',
1879
+ validate(a, b, c, d) {},
1880
+ params: {
1881
+ foo: { isJoi: true }
1882
+ }
1883
+ }]
1884
+ });
1885
+ }).to.throw(/"foo" must be an instance of "Joi object"/);
1886
+
1887
+ expect(() => {
1888
+
1889
+ Joi.extend({
1890
+ name: 'a', rules: [{
1891
+ name: 'foo',
1892
+ validate(a, b, c, d) {},
1893
+ params: Joi.number()
1894
+ }]
1895
+ });
1896
+ }).to.throw(/"params" must be an instance of "Joi object"/);
1897
+
1898
+ done();
1899
+ });
1900
+ });
1901
+
1902
+ it('defines a custom type with a default base', (done) => {
1903
+
1904
+ const customJoi = Joi.extend({
1905
+ name: 'myType'
1906
+ });
1907
+
1908
+ expect(Joi.myType).to.not.exist();
1909
+ expect(customJoi.myType).to.be.a.function();
1910
+
1911
+ const schema = customJoi.myType();
1912
+ expect(schema._type).to.equal('myType');
1913
+ expect(schema.isJoi).to.be.true();
1914
+
1915
+ done();
1916
+ });
1917
+
1918
+ it('defines a custom type with a custom base', (done) => {
1919
+
1920
+ const customJoi = Joi.extend({
1921
+ base: Joi.string().min(2),
1922
+ name: 'myType'
1923
+ });
1924
+
1925
+ expect(Joi.myType).to.not.exist();
1926
+ expect(customJoi.myType).to.be.a.function();
1927
+
1928
+ const schema = customJoi.myType();
1929
+ Helper.validate(schema, [
1930
+ [123, false, null, '"value" must be a string'],
1931
+ ['a', false, null, '"value" length must be at least 2 characters long'],
1932
+ ['abc', true]
1933
+ ], done);
1934
+ });
1935
+
1936
+ it('defines a custom type with new rules', (done) => {
1937
+
1938
+ const customJoi = Joi.extend({
1939
+ name: 'myType',
1940
+ language: {
1941
+ bar: 'oh no bar !'
1942
+ },
1943
+ rules: [
1944
+ {
1945
+ name: 'foo',
1946
+ validate(params, value, state, options) {
1947
+
1948
+ return null; // Valid
1949
+ }
1950
+ },
1951
+ {
1952
+ name: 'bar',
1953
+ validate(params, value, state, options) {
1954
+
1955
+ return this.createError('myType.bar', null, state, options);
1956
+ }
1957
+ }
1958
+ ]
1959
+ });
1960
+
1961
+ const original = Joi.any();
1962
+ expect(original.foo).to.not.exist();
1963
+ expect(original.bar).to.not.exist();
1964
+
1965
+ const schema = customJoi.myType();
1966
+ const valid = schema.foo().validate({});
1967
+ const invalid = schema.bar().validate({});
1968
+
1969
+ expect(valid.error).to.be.null();
1970
+ expect(invalid.error).to.be.an.instanceof(Error);
1971
+ expect(invalid.error.toString()).to.equal('ValidationError: "value" oh no bar !');
1972
+
1973
+ done();
1974
+ });
1975
+
1976
+ it('defines a custom type with a rule with setup', (done) => {
1977
+
1978
+ const customJoi = Joi.extend({
1979
+ name: 'myType',
1980
+ pre(value, state, options) {
1981
+
1982
+ return this._flags.foo;
1983
+ },
1984
+ rules: [
1985
+ {
1986
+ name: 'foo',
1987
+ params: {
1988
+ first: Joi.string(),
1989
+ second: Joi.func().ref()
1990
+ },
1991
+ setup(params) {
1992
+
1993
+ this._flags.foo = params;
1994
+ }
1995
+ }
1996
+ ]
1997
+ });
1998
+
1999
+ const schema = customJoi.myType();
2000
+ expect(schema.foo('bar').validate(null).value).to.deep.equal({ first: 'bar', second: undefined });
2001
+ expect(schema.foo('bar', Joi.ref('a.b')).validate(null).value.first).to.equal('bar');
2002
+ expect(Joi.isRef(schema.foo('bar', Joi.ref('a.b')).validate(null).value.second)).to.be.true();
2003
+ done();
2004
+ });
2005
+
2006
+ it('defines a custom type with a rule with both setup and validate', (done) => {
2007
+
2008
+ const customJoi = Joi.extend({
2009
+ name: 'myType',
2010
+ pre(value, state, options) {
2011
+
2012
+ return value + this._flags.add;
2013
+ },
2014
+ rules: [
2015
+ {
2016
+ name: 'addTwice',
2017
+ params: {
2018
+ factor: Joi.number().required()
2019
+ },
2020
+ setup(params) {
2021
+
2022
+ this._flags.add = params.factor;
2023
+ },
2024
+ validate(params, value, state, options) {
2025
+
2026
+ return value + params.factor;
2027
+ }
2028
+ }
2029
+ ]
2030
+ });
2031
+
2032
+ const schema = customJoi.myType();
2033
+ expect(schema.addTwice(3).validate(0).value).to.equal(6);
2034
+ done();
2035
+ });
2036
+
2037
+ it('defines a rule that validates its parameters', (done) => {
2038
+
2039
+ const customJoi = Joi.extend({
2040
+ base: Joi.number(),
2041
+ name: 'number',
2042
+ rules: [
2043
+ {
2044
+ name: 'multiply',
2045
+ params: {
2046
+ q: Joi.number().required(),
2047
+ currency: Joi.string()
2048
+ },
2049
+ validate(params, value, state, options) {
2050
+
2051
+ const v = value * params.q;
2052
+ return params.currency ? params.currency + v : v;
2053
+ }
2054
+ }
2055
+ ]
2056
+ });
2057
+
2058
+ const original = Joi.number();
2059
+ expect(original.double).to.not.exist();
2060
+
2061
+ expect(customJoi.number().multiply(2).validate(3)).to.deep.equal({ error: null, value: 6 });
2062
+ expect(customJoi.number().multiply(5, '$').validate(7)).to.deep.equal({ error: null, value: '$35' });
2063
+ expect(() => customJoi.number().multiply(5, '$', 'oops')).to.throw('Unexpected number of arguments');
2064
+
2065
+ done();
2066
+ });
2067
+
2068
+ it('defines a rule that validates its parameters when provided as a Joi schema', (done) => {
2069
+
2070
+ const customJoi = Joi.extend({
2071
+ base: Joi.number(),
2072
+ name: 'number',
2073
+ rules: [
2074
+ {
2075
+ name: 'multiply',
2076
+ params: Joi.object({
2077
+ q: Joi.number().required(),
2078
+ currency: Joi.string()
2079
+ }),
2080
+ validate(params, value, state, options) {
2081
+
2082
+ const v = value * params.q;
2083
+ return params.currency ? params.currency + v : v;
2084
+ }
2085
+ }
2086
+ ]
2087
+ });
2088
+
2089
+ const original = Joi.number();
2090
+ expect(original.double).to.not.exist();
2091
+
2092
+ expect(customJoi.number().multiply(2).validate(3)).to.deep.equal({ error: null, value: 6 });
2093
+ expect(customJoi.number().multiply(5, '$').validate(7)).to.deep.equal({ error: null, value: '$35' });
2094
+ expect(() => customJoi.number().multiply(5, '$', 'oops')).to.throw('Unexpected number of arguments');
2095
+
2096
+ done();
2097
+ });
2098
+
2099
+ it('defines a rule that validates its parameters with references', (done) => {
2100
+
2101
+ const customJoi = Joi.extend({
2102
+ base: Joi.number(),
2103
+ name: 'number',
2104
+ rules: [
2105
+ {
2106
+ name: 'multiply',
2107
+ params: {
2108
+ q: Joi.func().ref(),
2109
+ currency: Joi.string()
2110
+ },
2111
+ validate(params, value, state, options) {
2112
+
2113
+ const q = params.q(state.parent, options) || 0;
2114
+ const v = value * q;
2115
+ return params.currency ? params.currency + v : v;
2116
+ }
2117
+ }
2118
+ ]
2119
+ });
2120
+
2121
+ const schema = customJoi.object({
2122
+ a: customJoi.number(),
2123
+ b: customJoi.number().multiply(customJoi.ref('a'))
2124
+ });
2125
+
2126
+ Helper.validate(schema, [
2127
+ [{ a: 3, b: 5 }, true, null, { a: 3, b: 15 }],
2128
+ [{ b: 42 }, true, null, { b: 0 }]
2129
+ ], done);
2130
+ });
2131
+
2132
+ it('defines a rule that can change the value', (done) => {
2133
+
2134
+ const customJoi = Joi.extend({
2135
+ base: Joi.number(),
2136
+ name: 'number',
2137
+ rules: [
2138
+ {
2139
+ name: 'double',
2140
+ validate(params, value, state, options) {
2141
+
2142
+ return value * 2;
2143
+ }
2144
+ }
2145
+ ]
2146
+ });
2147
+
2148
+ const original = Joi.number();
2149
+ expect(original.double).to.not.exist();
2150
+
2151
+ const schema = customJoi.number().double();
2152
+ expect(schema.validate(3)).to.deep.equal({ error: null, value: 6 });
2153
+
2154
+ done();
2155
+ });
2156
+
2157
+ it('overrides a predefined language', (done) => {
2158
+
2159
+ const base = Joi.any().options({
2160
+ language: {
2161
+ myType: {
2162
+ foo: 'original'
2163
+ }
2164
+ }
2165
+ });
2166
+
2167
+ const customJoi = Joi.extend({
2168
+ base,
2169
+ name: 'myType',
2170
+ language: {
2171
+ foo: 'modified'
2172
+ },
2173
+ rules: [
2174
+ {
2175
+ name: 'foo',
2176
+ validate(params, value, state, options) {
2177
+
2178
+ return this.createError('myType.foo', null, state, options);
2179
+ }
2180
+ }
2181
+ ]
2182
+ });
2183
+
2184
+ const schema = customJoi.myType().foo();
2185
+ const result = schema.validate({});
2186
+ expect(result.error).to.be.an.instanceof(Error);
2187
+ expect(result.error.toString()).to.equal('ValidationError: "value" modified');
2188
+
2189
+ done();
2190
+ });
2191
+
2192
+ it('defines a custom type casting its input value', (done) => {
2193
+
2194
+ const customJoi = Joi.extend({
2195
+ base: Joi.string(),
2196
+ pre(value, state, options) {
2197
+
2198
+ return Symbol(value);
2199
+ },
2200
+ name: 'myType'
2201
+ });
2202
+
2203
+ const schema = customJoi.myType();
2204
+ const result = schema.validate('foo');
2205
+ expect(result.error).to.be.null();
2206
+ expect(typeof result.value).to.equal('symbol');
2207
+ expect(result.value.toString()).to.equal('Symbol(foo)');
2208
+
2209
+ done();
2210
+ });
2211
+
2212
+ it('defines a custom type with a failing pre', (done) => {
2213
+
2214
+ const customJoi = Joi.extend({
2215
+ pre(value, state, options) {
2216
+
2217
+ return this.createError('any.invalid', null, state, options);
2218
+ },
2219
+ name: 'myType'
2220
+ });
2221
+
2222
+ const schema = customJoi.myType();
2223
+ const result = schema.validate('foo');
2224
+ expect(result.error).to.exist();
2225
+ expect(result.error.toString()).to.equal('ValidationError: "value" contains an invalid value');
2226
+
2227
+ done();
2228
+ });
2229
+
2230
+ it('defines a custom type with a non-modifying pre', (done) => {
2231
+
2232
+ const customJoi = Joi.extend({
2233
+ pre(value, state, options) {
2234
+
2235
+ return null;
2236
+ },
2237
+ name: 'myType'
2238
+ });
2239
+
2240
+ const schema = customJoi.myType();
2241
+ const result = schema.validate('foo');
2242
+ expect(result.error).to.not.exist();
2243
+ expect(result.value).to.equal('foo');
2244
+
2245
+ done();
2246
+ });
2247
+
2248
+ it('never reaches a pre if the base is failing', (done) => {
2249
+
2250
+ const customJoi = Joi.extend({
2251
+ base: Joi.number(),
2252
+ pre(value, state, options) {
2253
+
2254
+ throw new Error('should not reach here');
2255
+ },
2256
+ name: 'myType'
2257
+ });
2258
+
2259
+ const schema = customJoi.myType();
2260
+ const result = schema.validate('foo');
2261
+ expect(result.error).to.exist();
2262
+ expect(result.error.toString()).to.equal('ValidationError: "value" must be a number');
2263
+
2264
+ done();
2265
+ });
2266
+
2267
+ describe('describe()', () => {
2268
+
2269
+ it('should describe a basic schema', (done) => {
2270
+
2271
+ const customJoi = Joi.extend({
2272
+ name: 'myType'
2273
+ });
2274
+
2275
+ const schema = customJoi.myType();
2276
+ expect(schema.describe()).to.deep.equal({
2277
+ type: 'myType'
2278
+ });
2279
+
2280
+ done();
2281
+ });
2282
+
2283
+ it('should describe a schema with a base', (done) => {
2284
+
2285
+ const customJoi = Joi.extend({
2286
+ base: Joi.number(),
2287
+ name: 'myType'
2288
+ });
2289
+
2290
+ const schema = customJoi.myType();
2291
+ expect(schema.describe()).to.deep.equal({
2292
+ type: 'myType',
2293
+ invalids: [Infinity, -Infinity]
2294
+ });
2295
+
2296
+ done();
2297
+ });
2298
+
2299
+ it('should describe a schema with rules', (done) => {
2300
+
2301
+ const customJoi = Joi.extend({
2302
+ name: 'myType',
2303
+ rules: [
2304
+ {
2305
+ name: 'foo',
2306
+ validate(params, value, state, options) {}
2307
+ },
2308
+ {
2309
+ name: 'bar',
2310
+ validate(params, value, state, options) {}
2311
+ }
2312
+ ]
2313
+ });
2314
+
2315
+ const schema = customJoi.myType().foo().bar();
2316
+ expect(schema.describe()).to.deep.equal({
2317
+ type: 'myType',
2318
+ rules: [
2319
+ { name: 'foo', arg: {} },
2320
+ { name: 'bar', arg: {} }
2321
+ ]
2322
+ });
2323
+
2324
+ done();
2325
+ });
2326
+
2327
+ it('should describe a schema with rules and parameters', (done) => {
2328
+
2329
+ const customJoi = Joi.extend({
2330
+ name: 'myType',
2331
+ rules: [
2332
+ {
2333
+ name: 'foo',
2334
+ params: {
2335
+ bar: Joi.string(),
2336
+ baz: Joi.number(),
2337
+ qux: Joi.func().ref(),
2338
+ quux: Joi.func().ref()
2339
+ },
2340
+ validate(params, value, state, options) {}
2341
+ }
2342
+ ]
2343
+ });
2344
+
2345
+ const schema = customJoi.myType().foo('bar', 42, Joi.ref('a.b'), Joi.ref('$c.d'));
2346
+ expect(schema.describe()).to.deep.equal({
2347
+ type: 'myType',
2348
+ rules: [
2349
+ { name: 'foo', arg: { bar: 'bar', baz: 42, qux: 'ref:a.b', quux: 'context:c.d' } }
2350
+ ]
2351
+ });
2352
+
2353
+ done();
2354
+ });
2355
+
2356
+ it('should describe a schema with rules and parameters with custom description', (done) => {
2357
+
2358
+ const customJoi = Joi.extend({
2359
+ name: 'myType',
2360
+ rules: [
2361
+ {
2362
+ name: 'foo',
2363
+ params: {
2364
+ bar: Joi.string()
2365
+ },
2366
+ description: 'something',
2367
+ validate(params, value, state, options) {}
2368
+ },
2369
+ {
2370
+ name: 'bar',
2371
+ params: {
2372
+ baz: Joi.string()
2373
+ },
2374
+ description(params) {
2375
+
2376
+ expect(params).to.deep.equal({ baz: 'baz' });
2377
+ return 'whatever';
2378
+ },
2379
+ validate(params, value, state, options) {}
2380
+ }
2381
+ ]
2382
+ });
2383
+
2384
+ const schema = customJoi.myType().foo('bar').bar('baz');
2385
+ expect(schema.describe()).to.deep.equal({
2386
+ type: 'myType',
2387
+ rules: [
2388
+ { name: 'foo', description: 'something', arg: { bar: 'bar' } },
2389
+ { name: 'bar', description: 'whatever', arg: { baz: 'baz' } }
2390
+ ]
2391
+ });
2392
+
2393
+ done();
2394
+ });
2395
+
2396
+ it('should describe a schema with rules and parameters with custom description', (done) => {
2397
+
2398
+ const customJoi = Joi.extend({
2399
+ name: 'myType',
2400
+ describe(description) {
2401
+
2402
+ expect(description).to.deep.equal({
2403
+ type: 'myType',
2404
+ rules: [
2405
+ { name: 'foo', description: 'something', arg: { bar: 'bar' } },
2406
+ { name: 'bar', description: 'whatever', arg: { baz: 'baz' } }
2407
+ ]
2408
+ });
2409
+
2410
+ description.type = 'zalgo';
2411
+ return description;
2412
+ },
2413
+ rules: [
2414
+ {
2415
+ name: 'foo',
2416
+ params: {
2417
+ bar: Joi.string()
2418
+ },
2419
+ description: 'something',
2420
+ validate(params, value, state, options) {}
2421
+ },
2422
+ {
2423
+ name: 'bar',
2424
+ params: {
2425
+ baz: Joi.string()
2426
+ },
2427
+ description(params) {
2428
+
2429
+ expect(params).to.deep.equal({ baz: 'baz' });
2430
+ return 'whatever';
2431
+ },
2432
+ validate(params, value, state, options) {}
2433
+ }
2434
+ ]
2435
+ });
2436
+
2437
+ const schema = customJoi.myType().foo('bar').bar('baz');
2438
+ expect(schema.describe()).to.deep.equal({
2439
+ type: 'zalgo',
2440
+ rules: [
2441
+ { name: 'foo', description: 'something', arg: { bar: 'bar' } },
2442
+ { name: 'bar', description: 'whatever', arg: { baz: 'baz' } }
2443
+ ]
2444
+ });
2445
+
2446
+ done();
2447
+ });
2448
+ });
2449
+ });
2450
+ });