1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5var AssertTrue = requireNative('assert').AssertTrue; 6var JSONSchemaValidator = require('json_schema').JSONSchemaValidator; 7var LOG = requireNative('logging').LOG; 8 9function assertValid(type, instance, schema, types) { 10 var validator = new JSONSchemaValidator(); 11 if (types) 12 validator.addTypes(types); 13 validator["validate" + type](instance, schema, ""); 14 var success = true; 15 if (validator.errors.length != 0) { 16 LOG("Got unexpected errors"); 17 for (var i = 0; i < validator.errors.length; i++) { 18 LOG(validator.errors[i].message + " path: " + validator.errors[i].path); 19 } 20 success = false; 21 } 22 AssertTrue(success); 23} 24 25function assertNotValid(type, instance, schema, errors, types) { 26 var validator = new JSONSchemaValidator(); 27 if (types) 28 validator.addTypes(types); 29 validator["validate" + type](instance, schema, ""); 30 AssertTrue(validator.errors.length === errors.length); 31 var success = true; 32 for (var i = 0; i < errors.length; i++) { 33 if (validator.errors[i].message == errors[i]) { 34 LOG("Got expected error: " + validator.errors[i].message + 35 " for path: " + validator.errors[i].path); 36 } else { 37 LOG("Missed expected error: " + errors[i] + ". Got: " + 38 validator.errors[i].message + " instead."); 39 success = false; 40 } 41 } 42 AssertTrue(success); 43} 44 45function assertListConsistsOfElements(list, elements) { 46 var success = true; 47 for (var li = 0; li < list.length; li++) { 48 for (var ei = 0; ei < elements.length && list[li] != elements[ei]; ei++) { } 49 if (ei == elements.length) { 50 LOG("Expected type not found: " + list[li]); 51 success = false; 52 } 53 } 54 AssertTrue(success); 55} 56 57function assertEqualSets(set1, set2) { 58 assertListConsistsOfElements(set1, set2); 59 assertListConsistsOfElements(set2, set1); 60} 61 62function formatError(key, replacements) { 63 return JSONSchemaValidator.formatError(key, replacements); 64} 65 66function testFormatError() { 67 AssertTrue(formatError("propertyRequired") == "Property is required."); 68 AssertTrue(formatError("invalidEnum", ["foo, bar"]) == 69 "Value must be one of: [foo, bar]."); 70 AssertTrue(formatError("invalidType", ["foo", "bar"]) == 71 "Expected 'foo' but got 'bar'."); 72} 73 74function testComplex() { 75 var schema = { 76 type: "array", 77 items: [ 78 { 79 type: "object", 80 properties: { 81 id: { 82 type: "integer", 83 minimum: 1 84 }, 85 url: { 86 type: "string", 87 pattern: /^https?\:/, 88 optional: true 89 }, 90 index: { 91 type: "integer", 92 minimum: 0, 93 optional: true 94 }, 95 selected: { 96 type: "boolean", 97 optional: true 98 } 99 } 100 }, 101 { type: "function", optional: true }, 102 { type: "function", optional: true } 103 ] 104 }; 105 106 var instance = [ 107 { 108 id: 42, 109 url: "http://www.google.com/", 110 index: 2, 111 selected: true 112 }, 113 function(){}, 114 function(){} 115 ]; 116 117 assertValid("", instance, schema); 118 instance.length = 2; 119 assertValid("", instance, schema); 120 instance.length = 1; 121 instance.push({}); 122 assertNotValid("", instance, schema, 123 [formatError("invalidType", ["function", "object"])]); 124 instance[1] = function(){}; 125 126 instance[0].url = "foo"; 127 assertNotValid("", instance, schema, 128 [formatError("stringPattern", 129 [schema.items[0].properties.url.pattern])]); 130 delete instance[0].url; 131 assertValid("", instance, schema); 132 133 instance[0].id = 0; 134 assertNotValid("", instance, schema, 135 [formatError("numberMinValue", 136 [schema.items[0].properties.id.minimum])]); 137} 138 139function testEnum() { 140 var schema = { 141 enum: ["foo", 42, false] 142 }; 143 144 assertValid("", "foo", schema); 145 assertValid("", 42, schema); 146 assertValid("", false, schema); 147 assertNotValid("", "42", schema, [formatError("invalidEnum", 148 [schema.enum.join(", ")])]); 149 assertNotValid("", null, schema, [formatError("invalidEnum", 150 [schema.enum.join(", ")])]); 151} 152 153function testChoices() { 154 var schema = { 155 choices: [ 156 { type: "null" }, 157 { type: "undefined" }, 158 { type: "integer", minimum:42, maximum:42 }, 159 { type: "object", properties: { foo: { type: "string" } } } 160 ] 161 } 162 assertValid("", null, schema); 163 assertValid("", undefined, schema); 164 assertValid("", 42, schema); 165 assertValid("", {foo: "bar"}, schema); 166 167 assertNotValid("", "foo", schema, [formatError("invalidChoice", [])]); 168 assertNotValid("", [], schema, [formatError("invalidChoice", [])]); 169 assertNotValid("", {foo: 42}, schema, [formatError("invalidChoice", [])]); 170} 171 172function testExtends() { 173 var parent = { 174 type: "number" 175 } 176 var schema = { 177 extends: parent 178 }; 179 180 assertValid("", 42, schema); 181 assertNotValid("", "42", schema, 182 [formatError("invalidType", ["number", "string"])]); 183 184 // Make the derived schema more restrictive 185 parent.minimum = 43; 186 assertNotValid("", 42, schema, [formatError("numberMinValue", [43])]); 187 assertValid("", 43, schema); 188} 189 190function testObject() { 191 var schema = { 192 properties: { 193 "foo": { 194 type: "string" 195 }, 196 "bar": { 197 type: "integer" 198 } 199 } 200 }; 201 202 assertValid("Object", {foo:"foo", bar:42}, schema); 203 assertNotValid("Object", {foo:"foo", bar:42,"extra":true}, schema, 204 [formatError("unexpectedProperty")]); 205 assertNotValid("Object", {foo:"foo"}, schema, 206 [formatError("propertyRequired")]); 207 assertNotValid("Object", {foo:"foo", bar:"42"}, schema, 208 [formatError("invalidType", ["integer", "string"])]); 209 210 schema.additionalProperties = { type: "any" }; 211 assertValid("Object", {foo:"foo", bar:42, "extra":true}, schema); 212 assertValid("Object", {foo:"foo", bar:42, "extra":"foo"}, schema); 213 214 schema.additionalProperties = { type: "boolean" }; 215 assertValid("Object", {foo:"foo", bar:42, "extra":true}, schema); 216 assertNotValid("Object", {foo:"foo", bar:42, "extra":"foo"}, schema, 217 [formatError("invalidType", ["boolean", "string"])]); 218 219 schema.properties.bar.optional = true; 220 assertValid("Object", {foo:"foo", bar:42}, schema); 221 assertValid("Object", {foo:"foo"}, schema); 222 assertValid("Object", {foo:"foo", bar:null}, schema); 223 assertValid("Object", {foo:"foo", bar:undefined}, schema); 224 assertNotValid("Object", {foo:"foo", bar:"42"}, schema, 225 [formatError("invalidType", ["integer", "string"])]); 226} 227 228function testTypeReference() { 229 var referencedTypes = [ 230 { 231 id: "MinLengthString", 232 type: "string", 233 minLength: 2 234 }, 235 { 236 id: "Max10Int", 237 type: "integer", 238 maximum: 10 239 } 240 ]; 241 242 var schema = { 243 type: "object", 244 properties: { 245 "foo": { 246 type: "string" 247 }, 248 "bar": { 249 $ref: "Max10Int" 250 }, 251 "baz": { 252 $ref: "MinLengthString" 253 } 254 } 255 }; 256 257 var schemaInlineReference = { 258 type: "object", 259 properties: { 260 "foo": { 261 type: "string" 262 }, 263 "bar": { 264 id: "NegativeInt", 265 type: "integer", 266 maximum: 0 267 }, 268 "baz": { 269 $ref: "NegativeInt" 270 } 271 } 272 } 273 274 // Valid type references externally added. 275 assertValid("", {foo:"foo",bar:4,baz:"ab"}, schema, referencedTypes); 276 277 // Valida type references internally defined. 278 assertValid("", {foo:"foo",bar:-4,baz:-2}, schemaInlineReference); 279 280 // Failures in validation, but succesful schema reference. 281 assertNotValid("", {foo:"foo",bar:4,baz:"a"}, schema, 282 [formatError("stringMinLength", [2])], referencedTypes); 283 assertNotValid("", {foo:"foo",bar:20,baz:"abc"}, schema, 284 [formatError("numberMaxValue", [10])], referencedTypes); 285 286 // Remove MinLengthString type. 287 referencedTypes.shift(); 288 assertNotValid("", {foo:"foo",bar:4,baz:"ab"}, schema, 289 [formatError("unknownSchemaReference", ["MinLengthString"])], 290 referencedTypes); 291 292 // Remove internal type "NegativeInt" 293 delete schemaInlineReference.properties.bar; 294 assertNotValid("", {foo:"foo",baz:-2}, schemaInlineReference, 295 [formatError("unknownSchemaReference", ["NegativeInt"])]); 296} 297 298function testArrayTuple() { 299 var schema = { 300 items: [ 301 { 302 type: "string" 303 }, 304 { 305 type: "integer" 306 } 307 ] 308 }; 309 310 assertValid("Array", ["42", 42], schema); 311 assertNotValid("Array", ["42", 42, "anything"], schema, 312 [formatError("arrayMaxItems", [schema.items.length])]); 313 assertNotValid("Array", ["42"], schema, [formatError("itemRequired")]); 314 assertNotValid("Array", [42, 42], schema, 315 [formatError("invalidType", ["string", "integer"])]); 316 317 schema.additionalProperties = { type: "any" }; 318 assertValid("Array", ["42", 42, "anything"], schema); 319 assertValid("Array", ["42", 42, []], schema); 320 321 schema.additionalProperties = { type: "boolean" }; 322 assertNotValid("Array", ["42", 42, "anything"], schema, 323 [formatError("invalidType", ["boolean", "string"])]); 324 assertValid("Array", ["42", 42, false], schema); 325 326 schema.items[0].optional = true; 327 assertValid("Array", ["42", 42], schema); 328 assertValid("Array", [, 42], schema); 329 assertValid("Array", [null, 42], schema); 330 assertValid("Array", [undefined, 42], schema); 331 assertNotValid("Array", [42, 42], schema, 332 [formatError("invalidType", ["string", "integer"])]); 333} 334 335function testArrayNonTuple() { 336 var schema = { 337 items: { 338 type: "string" 339 }, 340 minItems: 2, 341 maxItems: 3 342 }; 343 344 assertValid("Array", ["x", "x"], schema); 345 assertValid("Array", ["x", "x", "x"], schema); 346 347 assertNotValid("Array", ["x"], schema, 348 [formatError("arrayMinItems", [schema.minItems])]); 349 assertNotValid("Array", ["x", "x", "x", "x"], schema, 350 [formatError("arrayMaxItems", [schema.maxItems])]); 351 assertNotValid("Array", [42], schema, 352 [formatError("arrayMinItems", [schema.minItems]), 353 formatError("invalidType", ["string", "integer"])]); 354} 355 356function testString() { 357 var schema = { 358 minLength: 1, 359 maxLength: 10, 360 pattern: /^x/ 361 }; 362 363 assertValid("String", "x", schema); 364 assertValid("String", "xxxxxxxxxx", schema); 365 366 assertNotValid("String", "y", schema, 367 [formatError("stringPattern", [schema.pattern])]); 368 assertNotValid("String", "xxxxxxxxxxx", schema, 369 [formatError("stringMaxLength", [schema.maxLength])]); 370 assertNotValid("String", "", schema, 371 [formatError("stringMinLength", [schema.minLength]), 372 formatError("stringPattern", [schema.pattern])]); 373} 374 375function testNumber() { 376 var schema = { 377 minimum: 1, 378 maximum: 100, 379 maxDecimal: 2 380 }; 381 382 assertValid("Number", 1, schema); 383 assertValid("Number", 50, schema); 384 assertValid("Number", 100, schema); 385 assertValid("Number", 88.88, schema); 386 387 assertNotValid("Number", 0.5, schema, 388 [formatError("numberMinValue", [schema.minimum])]); 389 assertNotValid("Number", 100.1, schema, 390 [formatError("numberMaxValue", [schema.maximum])]); 391 assertNotValid("Number", 100.111, schema, 392 [formatError("numberMaxValue", [schema.maximum]), 393 formatError("numberMaxDecimal", [schema.maxDecimal])]); 394 395 var nan = 0/0; 396 AssertTrue(isNaN(nan)); 397 assertNotValid("Number", nan, schema, 398 [formatError("numberFiniteNotNan", ["NaN"])]); 399 400 assertNotValid("Number", Number.POSITIVE_INFINITY, schema, 401 [formatError("numberFiniteNotNan", ["Infinity"]), 402 formatError("numberMaxValue", [schema.maximum]) 403 ]); 404 405 assertNotValid("Number", Number.NEGATIVE_INFINITY, schema, 406 [formatError("numberFiniteNotNan", ["-Infinity"]), 407 formatError("numberMinValue", [schema.minimum]) 408 ]); 409} 410 411function testIntegerBounds() { 412 assertValid("Number", 0, {type:"integer"}); 413 assertValid("Number", -1, {type:"integer"}); 414 assertValid("Number", 2147483647, {type:"integer"}); 415 assertValid("Number", -2147483648, {type:"integer"}); 416 assertNotValid("Number", 0.5, {type:"integer"}, 417 [formatError("numberIntValue", [])]); 418 assertNotValid("Number", 10000000000, {type:"integer"}, 419 [formatError("numberIntValue", [])]); 420 assertNotValid("Number", 2147483647.5, {type:"integer"}, 421 [formatError("numberIntValue", [])]); 422 assertNotValid("Number", 2147483648, {type:"integer"}, 423 [formatError("numberIntValue", [])]); 424 assertNotValid("Number", 2147483649, {type:"integer"}, 425 [formatError("numberIntValue", [])]); 426 assertNotValid("Number", -2147483649, {type:"integer"}, 427 [formatError("numberIntValue", [])]); 428} 429 430function testType() { 431 // valid 432 assertValid("Type", {}, {type:"object"}); 433 assertValid("Type", [], {type:"array"}); 434 assertValid("Type", function(){}, {type:"function"}); 435 assertValid("Type", "foobar", {type:"string"}); 436 assertValid("Type", "", {type:"string"}); 437 assertValid("Type", 88.8, {type:"number"}); 438 assertValid("Type", 42, {type:"number"}); 439 assertValid("Type", 0, {type:"number"}); 440 assertValid("Type", 42, {type:"integer"}); 441 assertValid("Type", 0, {type:"integer"}); 442 assertValid("Type", true, {type:"boolean"}); 443 assertValid("Type", false, {type:"boolean"}); 444 assertValid("Type", null, {type:"null"}); 445 assertValid("Type", undefined, {type:"undefined"}); 446 447 // not valid 448 assertNotValid("Type", [], {type: "object"}, 449 [formatError("invalidType", ["object", "array"])]); 450 assertNotValid("Type", null, {type: "object"}, 451 [formatError("invalidType", ["object", "null"])]); 452 assertNotValid("Type", function(){}, {type: "object"}, 453 [formatError("invalidType", ["object", "function"])]); 454 assertNotValid("Type", 42, {type: "array"}, 455 [formatError("invalidType", ["array", "integer"])]); 456 assertNotValid("Type", 42, {type: "string"}, 457 [formatError("invalidType", ["string", "integer"])]); 458 assertNotValid("Type", "42", {type: "number"}, 459 [formatError("invalidType", ["number", "string"])]); 460 assertNotValid("Type", 88.8, {type: "integer"}, 461 [formatError("invalidTypeIntegerNumber")]); 462 assertNotValid("Type", 1, {type: "boolean"}, 463 [formatError("invalidType", ["boolean", "integer"])]); 464 assertNotValid("Type", false, {type: "null"}, 465 [formatError("invalidType", ["null", "boolean"])]); 466 assertNotValid("Type", undefined, {type: "null"}, 467 [formatError("invalidType", ["null", "undefined"])]); 468 assertNotValid("Type", {}, {type: "function"}, 469 [formatError("invalidType", ["function", "object"])]); 470} 471 472function testGetAllTypesForSchema() { 473 var referencedTypes = [ 474 { 475 id: "ChoicesRef", 476 choices: [ 477 { type: "integer" }, 478 { type: "string" } 479 ] 480 }, 481 { 482 id: "ObjectRef", 483 type: "object", 484 } 485 ]; 486 487 var arraySchema = { 488 type: "array" 489 }; 490 491 var choicesSchema = { 492 choices: [ 493 { type: "object" }, 494 { type: "function" } 495 ] 496 }; 497 498 var objectRefSchema = { 499 $ref: "ObjectRef" 500 }; 501 502 var complexSchema = { 503 choices: [ 504 { $ref: "ChoicesRef" }, 505 { type: "function" }, 506 { $ref: "ObjectRef" } 507 ] 508 }; 509 510 var validator = new JSONSchemaValidator(); 511 validator.addTypes(referencedTypes); 512 513 var arraySchemaTypes = validator.getAllTypesForSchema(arraySchema); 514 assertEqualSets(arraySchemaTypes, ["array"]); 515 516 var choicesSchemaTypes = validator.getAllTypesForSchema(choicesSchema); 517 assertEqualSets(choicesSchemaTypes, ["object", "function"]); 518 519 var objectRefSchemaTypes = validator.getAllTypesForSchema(objectRefSchema); 520 assertEqualSets(objectRefSchemaTypes, ["object"]); 521 522 var complexSchemaTypes = validator.getAllTypesForSchema(complexSchema); 523 assertEqualSets(complexSchemaTypes, 524 ["integer", "string", "function", "object"]); 525} 526 527function testIsValidSchemaType() { 528 var referencedTypes = [ 529 { 530 id: "ChoicesRef", 531 choices: [ 532 { type: "integer" }, 533 { type: "string" } 534 ] 535 } 536 ]; 537 538 var objectSchema = { 539 type: "object", 540 optional: true 541 }; 542 543 var complexSchema = { 544 choices: [ 545 { $ref: "ChoicesRef" }, 546 { type: "function" }, 547 ] 548 }; 549 550 var validator = new JSONSchemaValidator(); 551 validator.addTypes(referencedTypes); 552 553 AssertTrue(validator.isValidSchemaType("object", objectSchema)); 554 AssertTrue(!validator.isValidSchemaType("integer", objectSchema)); 555 AssertTrue(!validator.isValidSchemaType("array", objectSchema)); 556 AssertTrue(validator.isValidSchemaType("null", objectSchema)); 557 AssertTrue(validator.isValidSchemaType("undefined", objectSchema)); 558 559 AssertTrue(validator.isValidSchemaType("integer", complexSchema)); 560 AssertTrue(validator.isValidSchemaType("function", complexSchema)); 561 AssertTrue(validator.isValidSchemaType("string", complexSchema)); 562 AssertTrue(!validator.isValidSchemaType("object", complexSchema)); 563 AssertTrue(!validator.isValidSchemaType("null", complexSchema)); 564 AssertTrue(!validator.isValidSchemaType("undefined", complexSchema)); 565} 566 567function testCheckSchemaOverlap() { 568 var referencedTypes = [ 569 { 570 id: "ChoicesRef", 571 choices: [ 572 { type: "integer" }, 573 { type: "string" } 574 ] 575 }, 576 { 577 id: "ObjectRef", 578 type: "object", 579 } 580 ]; 581 582 var arraySchema = { 583 type: "array" 584 }; 585 586 var choicesSchema = { 587 choices: [ 588 { type: "object" }, 589 { type: "function" } 590 ] 591 }; 592 593 var objectRefSchema = { 594 $ref: "ObjectRef" 595 }; 596 597 var complexSchema = { 598 choices: [ 599 { $ref: "ChoicesRef" }, 600 { type: "function" }, 601 { $ref: "ObjectRef" } 602 ] 603 }; 604 605 var validator = new JSONSchemaValidator(); 606 validator.addTypes(referencedTypes); 607 608 AssertTrue(!validator.checkSchemaOverlap(arraySchema, choicesSchema)); 609 AssertTrue(!validator.checkSchemaOverlap(arraySchema, objectRefSchema)); 610 AssertTrue(!validator.checkSchemaOverlap(arraySchema, complexSchema)); 611 AssertTrue(validator.checkSchemaOverlap(choicesSchema, objectRefSchema)); 612 AssertTrue(validator.checkSchemaOverlap(choicesSchema, complexSchema)); 613 AssertTrue(validator.checkSchemaOverlap(objectRefSchema, complexSchema)); 614} 615 616function testInstanceOf() { 617 function Animal() {}; 618 function Cat() {}; 619 function Dog() {}; 620 Cat.prototype = new Animal; 621 Cat.prototype.constructor = Cat; 622 Dog.prototype = new Animal; 623 Dog.prototype.constructor = Dog; 624 var cat = new Cat(); 625 var dog = new Dog(); 626 var num = new Number(1); 627 628 // instanceOf should check type by walking up prototype chain. 629 assertValid("", cat, {type:"object", isInstanceOf:"Cat"}); 630 assertValid("", cat, {type:"object", isInstanceOf:"Animal"}); 631 assertValid("", cat, {type:"object", isInstanceOf:"Object"}); 632 assertValid("", dog, {type:"object", isInstanceOf:"Dog"}); 633 assertValid("", dog, {type:"object", isInstanceOf:"Animal"}); 634 assertValid("", dog, {type:"object", isInstanceOf:"Object"}); 635 assertValid("", num, {type:"object", isInstanceOf:"Number"}); 636 assertValid("", num, {type:"object", isInstanceOf:"Object"}); 637 638 assertNotValid("", cat, {type:"object", isInstanceOf:"Dog"}, 639 [formatError("notInstance", ["Dog"])]); 640 assertNotValid("", dog, {type:"object", isInstanceOf:"Cat"}, 641 [formatError("notInstance", ["Cat"])]); 642 assertNotValid("", cat, {type:"object", isInstanceOf:"String"}, 643 [formatError("notInstance", ["String"])]); 644 assertNotValid("", dog, {type:"object", isInstanceOf:"String"}, 645 [formatError("notInstance", ["String"])]); 646 assertNotValid("", num, {type:"object", isInstanceOf:"Array"}, 647 [formatError("notInstance", ["Array"])]); 648 assertNotValid("", num, {type:"object", isInstanceOf:"String"}, 649 [formatError("notInstance", ["String"])]); 650} 651 652// Tests exposed to schema_unittest.cc. 653exports.testFormatError = testFormatError; 654exports.testComplex = testComplex; 655exports.testEnum = testEnum; 656exports.testExtends = testExtends; 657exports.testObject = testObject; 658exports.testArrayTuple = testArrayTuple; 659exports.testArrayNonTuple = testArrayNonTuple; 660exports.testString = testString; 661exports.testNumber = testNumber; 662exports.testIntegerBounds = testIntegerBounds; 663exports.testType = testType; 664exports.testTypeReference = testTypeReference; 665exports.testGetAllTypesForSchema = testGetAllTypesForSchema; 666exports.testIsValidSchemaType = testIsValidSchemaType; 667exports.testCheckSchemaOverlap = testCheckSchemaOverlap; 668exports.testInstanceOf = testInstanceOf; 669