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