blob: 03fc05712249032df3076b84465dd74a22784a45 [file] [log] [blame]
// 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;