// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var AssertTrue = requireNative('assert').AssertTrue; var JSONSchemaValidator = require('json_schema').JSONSchemaValidator; var LOG = requireNative('logging').LOG; function assertValid(type, instance, schema, types) { var validator = new JSONSchemaValidator(); if (types) validator.addTypes(types); validator["validate" + type](instance, schema, ""); var success = true; if (validator.errors.length != 0) { LOG("Got unexpected errors"); for (var i = 0; i < validator.errors.length; i++) { LOG(validator.errors[i].message + " path: " + validator.errors[i].path); } success = false; } AssertTrue(success); } function assertNotValid(type, instance, schema, errors, types) { var validator = new JSONSchemaValidator(); if (types) validator.addTypes(types); validator["validate" + type](instance, schema, ""); AssertTrue(validator.errors.length === errors.length); var success = true; for (var i = 0; i < errors.length; i++) { if (validator.errors[i].message == errors[i]) { LOG("Got expected error: " + validator.errors[i].message + " for path: " + validator.errors[i].path); } else { LOG("Missed expected error: " + errors[i] + ". Got: " + validator.errors[i].message + " instead."); success = false; } } AssertTrue(success); } function assertListConsistsOfElements(list, elements) { var success = true; for (var li = 0; li < list.length; li++) { for (var ei = 0; ei < elements.length && list[li] != elements[ei]; ei++) { } if (ei == elements.length) { LOG("Expected type not found: " + list[li]); success = false; } } AssertTrue(success); } function assertEqualSets(set1, set2) { assertListConsistsOfElements(set1, set2); assertListConsistsOfElements(set2, set1); } function formatError(key, replacements) { return JSONSchemaValidator.formatError(key, replacements); } function testFormatError() { AssertTrue(formatError("propertyRequired") == "Property is required."); AssertTrue(formatError("invalidEnum", ["foo, bar"]) == "Value must be one of: [foo, bar]."); AssertTrue(formatError("invalidType", ["foo", "bar"]) == "Expected 'foo' but got 'bar'."); } function testComplex() { var schema = { type: "array", items: [ { type: "object", properties: { id: { type: "integer", minimum: 1 }, url: { type: "string", pattern: /^https?\:/, optional: true }, index: { type: "integer", minimum: 0, optional: true }, selected: { type: "boolean", optional: true } } }, { type: "function", optional: true }, { type: "function", optional: true } ] }; var instance = [ { id: 42, url: "http://www.google.com/", index: 2, selected: true }, function(){}, function(){} ]; assertValid("", instance, schema); instance.length = 2; assertValid("", instance, schema); instance.length = 1; instance.push({}); assertNotValid("", instance, schema, [formatError("invalidType", ["function", "object"])]); instance[1] = function(){}; instance[0].url = "foo"; assertNotValid("", instance, schema, [formatError("stringPattern", [schema.items[0].properties.url.pattern])]); delete instance[0].url; assertValid("", instance, schema); instance[0].id = 0; assertNotValid("", instance, schema, [formatError("numberMinValue", [schema.items[0].properties.id.minimum])]); } function testEnum() { var schema = { enum: ["foo", 42, false] }; assertValid("", "foo", schema); assertValid("", 42, schema); assertValid("", false, schema); assertNotValid("", "42", schema, [formatError("invalidEnum", [schema.enum.join(", ")])]); assertNotValid("", null, schema, [formatError("invalidEnum", [schema.enum.join(", ")])]); } function testChoices() { var schema = { choices: [ { type: "null" }, { type: "undefined" }, { type: "integer", minimum:42, maximum:42 }, { type: "object", properties: { foo: { type: "string" } } } ] } assertValid("", null, schema); assertValid("", undefined, schema); assertValid("", 42, schema); assertValid("", {foo: "bar"}, schema); assertNotValid("", "foo", schema, [formatError("invalidChoice", [])]); assertNotValid("", [], schema, [formatError("invalidChoice", [])]); assertNotValid("", {foo: 42}, schema, [formatError("invalidChoice", [])]); } function testExtends() { var parent = { type: "number" } var schema = { extends: parent }; assertValid("", 42, schema); assertNotValid("", "42", schema, [formatError("invalidType", ["number", "string"])]); // Make the derived schema more restrictive parent.minimum = 43; assertNotValid("", 42, schema, [formatError("numberMinValue", [43])]); assertValid("", 43, schema); } function testObject() { var schema = { properties: { "foo": { type: "string" }, "bar": { type: "integer" } } }; assertValid("Object", {foo:"foo", bar:42}, schema); assertNotValid("Object", {foo:"foo", bar:42,"extra":true}, schema, [formatError("unexpectedProperty")]); assertNotValid("Object", {foo:"foo"}, schema, [formatError("propertyRequired")]); assertNotValid("Object", {foo:"foo", bar:"42"}, schema, [formatError("invalidType", ["integer", "string"])]); schema.additionalProperties = { type: "any" }; assertValid("Object", {foo:"foo", bar:42, "extra":true}, schema); assertValid("Object", {foo:"foo", bar:42, "extra":"foo"}, schema); schema.additionalProperties = { type: "boolean" }; assertValid("Object", {foo:"foo", bar:42, "extra":true}, schema); assertNotValid("Object", {foo:"foo", bar:42, "extra":"foo"}, schema, [formatError("invalidType", ["boolean", "string"])]); schema.properties.bar.optional = true; assertValid("Object", {foo:"foo", bar:42}, schema); assertValid("Object", {foo:"foo"}, schema); assertValid("Object", {foo:"foo", bar:null}, schema); assertValid("Object", {foo:"foo", bar:undefined}, schema); assertNotValid("Object", {foo:"foo", bar:"42"}, schema, [formatError("invalidType", ["integer", "string"])]); } function testTypeReference() { var referencedTypes = [ { id: "MinLengthString", type: "string", minLength: 2 }, { id: "Max10Int", type: "integer", maximum: 10 } ]; var schema = { type: "object", properties: { "foo": { type: "string" }, "bar": { $ref: "Max10Int" }, "baz": { $ref: "MinLengthString" } } }; var schemaInlineReference = { type: "object", properties: { "foo": { type: "string" }, "bar": { id: "NegativeInt", type: "integer", maximum: 0 }, "baz": { $ref: "NegativeInt" } } } // Valid type references externally added. assertValid("", {foo:"foo",bar:4,baz:"ab"}, schema, referencedTypes); // Valida type references internally defined. assertValid("", {foo:"foo",bar:-4,baz:-2}, schemaInlineReference); // Failures in validation, but succesful schema reference. assertNotValid("", {foo:"foo",bar:4,baz:"a"}, schema, [formatError("stringMinLength", [2])], referencedTypes); assertNotValid("", {foo:"foo",bar:20,baz:"abc"}, schema, [formatError("numberMaxValue", [10])], referencedTypes); // Remove MinLengthString type. referencedTypes.shift(); assertNotValid("", {foo:"foo",bar:4,baz:"ab"}, schema, [formatError("unknownSchemaReference", ["MinLengthString"])], referencedTypes); // Remove internal type "NegativeInt" delete schemaInlineReference.properties.bar; assertNotValid("", {foo:"foo",baz:-2}, schemaInlineReference, [formatError("unknownSchemaReference", ["NegativeInt"])]); } function testArrayTuple() { var schema = { items: [ { type: "string" }, { type: "integer" } ] }; assertValid("Array", ["42", 42], schema); assertNotValid("Array", ["42", 42, "anything"], schema, [formatError("arrayMaxItems", [schema.items.length])]); assertNotValid("Array", ["42"], schema, [formatError("itemRequired")]); assertNotValid("Array", [42, 42], schema, [formatError("invalidType", ["string", "integer"])]); schema.additionalProperties = { type: "any" }; assertValid("Array", ["42", 42, "anything"], schema); assertValid("Array", ["42", 42, []], schema); schema.additionalProperties = { type: "boolean" }; assertNotValid("Array", ["42", 42, "anything"], schema, [formatError("invalidType", ["boolean", "string"])]); assertValid("Array", ["42", 42, false], schema); schema.items[0].optional = true; assertValid("Array", ["42", 42], schema); assertValid("Array", [, 42], schema); assertValid("Array", [null, 42], schema); assertValid("Array", [undefined, 42], schema); assertNotValid("Array", [42, 42], schema, [formatError("invalidType", ["string", "integer"])]); } function testArrayNonTuple() { var schema = { items: { type: "string" }, minItems: 2, maxItems: 3 }; assertValid("Array", ["x", "x"], schema); assertValid("Array", ["x", "x", "x"], schema); assertNotValid("Array", ["x"], schema, [formatError("arrayMinItems", [schema.minItems])]); assertNotValid("Array", ["x", "x", "x", "x"], schema, [formatError("arrayMaxItems", [schema.maxItems])]); assertNotValid("Array", [42], schema, [formatError("arrayMinItems", [schema.minItems]), formatError("invalidType", ["string", "integer"])]); } function testString() { var schema = { minLength: 1, maxLength: 10, pattern: /^x/ }; assertValid("String", "x", schema); assertValid("String", "xxxxxxxxxx", schema); assertNotValid("String", "y", schema, [formatError("stringPattern", [schema.pattern])]); assertNotValid("String", "xxxxxxxxxxx", schema, [formatError("stringMaxLength", [schema.maxLength])]); assertNotValid("String", "", schema, [formatError("stringMinLength", [schema.minLength]), formatError("stringPattern", [schema.pattern])]); } function testNumber() { var schema = { minimum: 1, maximum: 100, maxDecimal: 2 }; assertValid("Number", 1, schema); assertValid("Number", 50, schema); assertValid("Number", 100, schema); assertValid("Number", 88.88, schema); assertNotValid("Number", 0.5, schema, [formatError("numberMinValue", [schema.minimum])]); assertNotValid("Number", 100.1, schema, [formatError("numberMaxValue", [schema.maximum])]); assertNotValid("Number", 100.111, schema, [formatError("numberMaxValue", [schema.maximum]), formatError("numberMaxDecimal", [schema.maxDecimal])]); var nan = 0/0; AssertTrue(isNaN(nan)); assertNotValid("Number", nan, schema, [formatError("numberFiniteNotNan", ["NaN"])]); assertNotValid("Number", Number.POSITIVE_INFINITY, schema, [formatError("numberFiniteNotNan", ["Infinity"]), formatError("numberMaxValue", [schema.maximum]) ]); assertNotValid("Number", Number.NEGATIVE_INFINITY, schema, [formatError("numberFiniteNotNan", ["-Infinity"]), formatError("numberMinValue", [schema.minimum]) ]); } function testIntegerBounds() { assertValid("Number", 0, {type:"integer"}); assertValid("Number", -1, {type:"integer"}); assertValid("Number", 2147483647, {type:"integer"}); assertValid("Number", -2147483648, {type:"integer"}); assertNotValid("Number", 0.5, {type:"integer"}, [formatError("numberIntValue", [])]); assertNotValid("Number", 10000000000, {type:"integer"}, [formatError("numberIntValue", [])]); assertNotValid("Number", 2147483647.5, {type:"integer"}, [formatError("numberIntValue", [])]); assertNotValid("Number", 2147483648, {type:"integer"}, [formatError("numberIntValue", [])]); assertNotValid("Number", 2147483649, {type:"integer"}, [formatError("numberIntValue", [])]); assertNotValid("Number", -2147483649, {type:"integer"}, [formatError("numberIntValue", [])]); } function testType() { // valid assertValid("Type", {}, {type:"object"}); assertValid("Type", [], {type:"array"}); assertValid("Type", function(){}, {type:"function"}); assertValid("Type", "foobar", {type:"string"}); assertValid("Type", "", {type:"string"}); assertValid("Type", 88.8, {type:"number"}); assertValid("Type", 42, {type:"number"}); assertValid("Type", 0, {type:"number"}); assertValid("Type", 42, {type:"integer"}); assertValid("Type", 0, {type:"integer"}); assertValid("Type", true, {type:"boolean"}); assertValid("Type", false, {type:"boolean"}); assertValid("Type", null, {type:"null"}); assertValid("Type", undefined, {type:"undefined"}); assertValid("Type", new ArrayBuffer(1), {type:"binary"}); assertValid("Type", otherContextArrayBufferContainer.value, {type:"binary"}); // not valid assertNotValid("Type", [], {type: "object"}, [formatError("invalidType", ["object", "array"])]); assertNotValid("Type", null, {type: "object"}, [formatError("invalidType", ["object", "null"])]); assertNotValid("Type", function(){}, {type: "object"}, [formatError("invalidType", ["object", "function"])]); assertNotValid("Type", 42, {type: "array"}, [formatError("invalidType", ["array", "integer"])]); assertNotValid("Type", 42, {type: "string"}, [formatError("invalidType", ["string", "integer"])]); assertNotValid("Type", "42", {type: "number"}, [formatError("invalidType", ["number", "string"])]); assertNotValid("Type", 88.8, {type: "integer"}, [formatError("invalidTypeIntegerNumber")]); assertNotValid("Type", 1, {type: "boolean"}, [formatError("invalidType", ["boolean", "integer"])]); assertNotValid("Type", false, {type: "null"}, [formatError("invalidType", ["null", "boolean"])]); assertNotValid("Type", undefined, {type: "null"}, [formatError("invalidType", ["null", "undefined"])]); assertNotValid("Type", {}, {type: "function"}, [formatError("invalidType", ["function", "object"])]); } function testGetAllTypesForSchema() { var referencedTypes = [ { id: "ChoicesRef", choices: [ { type: "integer" }, { type: "string" } ] }, { id: "ObjectRef", type: "object", } ]; var arraySchema = { type: "array" }; var choicesSchema = { choices: [ { type: "object" }, { type: "function" } ] }; var objectRefSchema = { $ref: "ObjectRef" }; var complexSchema = { choices: [ { $ref: "ChoicesRef" }, { type: "function" }, { $ref: "ObjectRef" } ] }; var validator = new JSONSchemaValidator(); validator.addTypes(referencedTypes); var arraySchemaTypes = validator.getAllTypesForSchema(arraySchema); assertEqualSets(arraySchemaTypes, ["array"]); var choicesSchemaTypes = validator.getAllTypesForSchema(choicesSchema); assertEqualSets(choicesSchemaTypes, ["object", "function"]); var objectRefSchemaTypes = validator.getAllTypesForSchema(objectRefSchema); assertEqualSets(objectRefSchemaTypes, ["object"]); var complexSchemaTypes = validator.getAllTypesForSchema(complexSchema); assertEqualSets(complexSchemaTypes, ["integer", "string", "function", "object"]); } function testIsValidSchemaType() { var referencedTypes = [ { id: "ChoicesRef", choices: [ { type: "integer" }, { type: "string" } ] } ]; var objectSchema = { type: "object", optional: true }; var complexSchema = { choices: [ { $ref: "ChoicesRef" }, { type: "function" }, ] }; var validator = new JSONSchemaValidator(); validator.addTypes(referencedTypes); AssertTrue(validator.isValidSchemaType("object", objectSchema)); AssertTrue(!validator.isValidSchemaType("integer", objectSchema)); AssertTrue(!validator.isValidSchemaType("array", objectSchema)); AssertTrue(validator.isValidSchemaType("null", objectSchema)); AssertTrue(validator.isValidSchemaType("undefined", objectSchema)); AssertTrue(validator.isValidSchemaType("integer", complexSchema)); AssertTrue(validator.isValidSchemaType("function", complexSchema)); AssertTrue(validator.isValidSchemaType("string", complexSchema)); AssertTrue(!validator.isValidSchemaType("object", complexSchema)); AssertTrue(!validator.isValidSchemaType("null", complexSchema)); AssertTrue(!validator.isValidSchemaType("undefined", complexSchema)); } function testCheckSchemaOverlap() { var referencedTypes = [ { id: "ChoicesRef", choices: [ { type: "integer" }, { type: "string" } ] }, { id: "ObjectRef", type: "object", } ]; var arraySchema = { type: "array" }; var choicesSchema = { choices: [ { type: "object" }, { type: "function" } ] }; var objectRefSchema = { $ref: "ObjectRef" }; var complexSchema = { choices: [ { $ref: "ChoicesRef" }, { type: "function" }, { $ref: "ObjectRef" } ] }; var validator = new JSONSchemaValidator(); validator.addTypes(referencedTypes); AssertTrue(!validator.checkSchemaOverlap(arraySchema, choicesSchema)); AssertTrue(!validator.checkSchemaOverlap(arraySchema, objectRefSchema)); AssertTrue(!validator.checkSchemaOverlap(arraySchema, complexSchema)); AssertTrue(validator.checkSchemaOverlap(choicesSchema, objectRefSchema)); AssertTrue(validator.checkSchemaOverlap(choicesSchema, complexSchema)); AssertTrue(validator.checkSchemaOverlap(objectRefSchema, complexSchema)); } function testInstanceOf() { function Animal() {}; function Cat() {}; function Dog() {}; Cat.prototype = new Animal; Cat.prototype.constructor = Cat; Dog.prototype = new Animal; Dog.prototype.constructor = Dog; var cat = new Cat(); var dog = new Dog(); var num = new Number(1); // instanceOf should check type by walking up prototype chain. assertValid("", cat, {type:"object", isInstanceOf:"Cat"}); assertValid("", cat, {type:"object", isInstanceOf:"Animal"}); assertValid("", cat, {type:"object", isInstanceOf:"Object"}); assertValid("", dog, {type:"object", isInstanceOf:"Dog"}); assertValid("", dog, {type:"object", isInstanceOf:"Animal"}); assertValid("", dog, {type:"object", isInstanceOf:"Object"}); assertValid("", num, {type:"object", isInstanceOf:"Number"}); assertValid("", num, {type:"object", isInstanceOf:"Object"}); assertNotValid("", cat, {type:"object", isInstanceOf:"Dog"}, [formatError("notInstance", ["Dog"])]); assertNotValid("", dog, {type:"object", isInstanceOf:"Cat"}, [formatError("notInstance", ["Cat"])]); assertNotValid("", cat, {type:"object", isInstanceOf:"String"}, [formatError("notInstance", ["String"])]); assertNotValid("", dog, {type:"object", isInstanceOf:"String"}, [formatError("notInstance", ["String"])]); assertNotValid("", num, {type:"object", isInstanceOf:"Array"}, [formatError("notInstance", ["Array"])]); assertNotValid("", num, {type:"object", isInstanceOf:"String"}, [formatError("notInstance", ["String"])]); } // Tests exposed to schema_unittest.cc. exports.testFormatError = testFormatError; exports.testComplex = testComplex; exports.testEnum = testEnum; exports.testExtends = testExtends; exports.testObject = testObject; exports.testArrayTuple = testArrayTuple; exports.testArrayNonTuple = testArrayNonTuple; exports.testString = testString; exports.testNumber = testNumber; exports.testIntegerBounds = testIntegerBounds; exports.testType = testType; exports.testTypeReference = testTypeReference; exports.testGetAllTypesForSchema = testGetAllTypesForSchema; exports.testIsValidSchemaType = testIsValidSchemaType; exports.testCheckSchemaOverlap = testCheckSchemaOverlap; exports.testInstanceOf = testInstanceOf;