Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / extensions / test / data / json_schema_test.js
blob03fc05712249032df3076b84465dd74a22784a45
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.
5 var AssertTrue = requireNative('assert').AssertTrue;
6 var JSONSchemaValidator = require('json_schema').JSONSchemaValidator;
7 var LOG = requireNative('logging').LOG;
9 function 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);
25 function 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);
45 function 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);
57 function assertEqualSets(set1, set2) {
58   assertListConsistsOfElements(set1, set2);
59   assertListConsistsOfElements(set2, set1);
62 function formatError(key, replacements) {
63   return JSONSchemaValidator.formatError(key, replacements);
66 function 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'.");
74 function 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   };
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   ];
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(){};
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);
133   instance[0].id = 0;
134   assertNotValid("", instance, schema,
135                  [formatError("numberMinValue",
136                               [schema.items[0].properties.id.minimum])]);
139 function testEnum() {
140   var schema = {
141     enum: ["foo", 42, false]
142   };
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(", ")])]);
153 function 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);
167     assertNotValid("", "foo", schema, [formatError("invalidChoice", [])]);
168     assertNotValid("", [], schema, [formatError("invalidChoice", [])]);
169     assertNotValid("", {foo: 42}, schema, [formatError("invalidChoice", [])]);
172 function testExtends() {
173   var parent = {
174     type: "number"
175   }
176   var schema = {
177     extends: parent
178   };
180   assertValid("", 42, schema);
181   assertNotValid("", "42", schema,
182                  [formatError("invalidType", ["number", "string"])]);
184   // Make the derived schema more restrictive
185   parent.minimum = 43;
186   assertNotValid("", 42, schema, [formatError("numberMinValue", [43])]);
187   assertValid("", 43, schema);
190 function testObject() {
191   var schema = {
192     properties: {
193       "foo": {
194         type: "string"
195       },
196       "bar": {
197         type: "integer"
198       }
199     }
200   };
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"])]);
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);
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"])]);
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"])]);
228 function 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   ];
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   };
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   }
274   // Valid type references externally added.
275   assertValid("", {foo:"foo",bar:4,baz:"ab"}, schema, referencedTypes);
277   // Valida type references internally defined.
278   assertValid("", {foo:"foo",bar:-4,baz:-2}, schemaInlineReference);
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);
286   // Remove MinLengthString type.
287   referencedTypes.shift();
288   assertNotValid("", {foo:"foo",bar:4,baz:"ab"}, schema,
289                  [formatError("unknownSchemaReference", ["MinLengthString"])],
290                  referencedTypes);
292   // Remove internal type "NegativeInt"
293   delete schemaInlineReference.properties.bar;
294   assertNotValid("", {foo:"foo",baz:-2}, schemaInlineReference,
295                  [formatError("unknownSchemaReference", ["NegativeInt"])]);
298 function testArrayTuple() {
299   var schema = {
300     items: [
301       {
302         type: "string"
303       },
304       {
305         type: "integer"
306       }
307     ]
308   };
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"])]);
317   schema.additionalProperties = { type: "any" };
318   assertValid("Array", ["42", 42, "anything"], schema);
319   assertValid("Array", ["42", 42, []], schema);
321   schema.additionalProperties = { type: "boolean" };
322   assertNotValid("Array", ["42", 42, "anything"], schema,
323                  [formatError("invalidType", ["boolean", "string"])]);
324   assertValid("Array", ["42", 42, false], schema);
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"])]);
335 function testArrayNonTuple() {
336   var schema = {
337     items: {
338       type: "string"
339     },
340     minItems: 2,
341     maxItems: 3
342   };
344   assertValid("Array", ["x", "x"], schema);
345   assertValid("Array", ["x", "x", "x"], schema);
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"])]);
356 function testString() {
357   var schema = {
358     minLength: 1,
359     maxLength: 10,
360     pattern: /^x/
361   };
363   assertValid("String", "x", schema);
364   assertValid("String", "xxxxxxxxxx", schema);
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])]);
375 function testNumber() {
376   var schema = {
377     minimum: 1,
378     maximum: 100,
379     maxDecimal: 2
380   };
382   assertValid("Number", 1, schema);
383   assertValid("Number", 50, schema);
384   assertValid("Number", 100, schema);
385   assertValid("Number", 88.88, schema);
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])]);
395   var nan = 0/0;
396   AssertTrue(isNaN(nan));
397   assertNotValid("Number", nan, schema,
398                  [formatError("numberFiniteNotNan", ["NaN"])]);
400   assertNotValid("Number", Number.POSITIVE_INFINITY, schema,
401                  [formatError("numberFiniteNotNan", ["Infinity"]),
402                   formatError("numberMaxValue", [schema.maximum])
403                  ]);
405   assertNotValid("Number", Number.NEGATIVE_INFINITY, schema,
406                  [formatError("numberFiniteNotNan", ["-Infinity"]),
407                   formatError("numberMinValue", [schema.minimum])
408                  ]);
411 function 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", [])]);
430 function 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   assertValid("Type", new ArrayBuffer(1), {type:"binary"});
447   assertValid("Type", otherContextArrayBufferContainer.value, {type:"binary"});
449   // not valid
450   assertNotValid("Type", [], {type: "object"},
451                  [formatError("invalidType", ["object", "array"])]);
452   assertNotValid("Type", null, {type: "object"},
453                  [formatError("invalidType", ["object", "null"])]);
454   assertNotValid("Type", function(){}, {type: "object"},
455                  [formatError("invalidType", ["object", "function"])]);
456   assertNotValid("Type", 42, {type: "array"},
457                  [formatError("invalidType", ["array", "integer"])]);
458   assertNotValid("Type", 42, {type: "string"},
459                  [formatError("invalidType", ["string", "integer"])]);
460   assertNotValid("Type", "42", {type: "number"},
461                  [formatError("invalidType", ["number", "string"])]);
462   assertNotValid("Type", 88.8, {type: "integer"},
463                  [formatError("invalidTypeIntegerNumber")]);
464   assertNotValid("Type", 1, {type: "boolean"},
465                  [formatError("invalidType", ["boolean", "integer"])]);
466   assertNotValid("Type", false, {type: "null"},
467                  [formatError("invalidType", ["null", "boolean"])]);
468   assertNotValid("Type", undefined, {type: "null"},
469                  [formatError("invalidType", ["null", "undefined"])]);
470   assertNotValid("Type", {}, {type: "function"},
471                  [formatError("invalidType", ["function", "object"])]);
474 function testGetAllTypesForSchema() {
475   var referencedTypes = [
476     {
477       id: "ChoicesRef",
478       choices: [
479         { type: "integer" },
480         { type: "string" }
481       ]
482     },
483     {
484       id: "ObjectRef",
485       type: "object",
486     }
487   ];
489   var arraySchema = {
490     type: "array"
491   };
493   var choicesSchema = {
494     choices: [
495       { type: "object" },
496       { type: "function" }
497     ]
498   };
500   var objectRefSchema = {
501     $ref: "ObjectRef"
502   };
504   var complexSchema = {
505     choices: [
506       { $ref: "ChoicesRef" },
507       { type: "function" },
508       { $ref: "ObjectRef" }
509     ]
510   };
512   var validator = new JSONSchemaValidator();
513   validator.addTypes(referencedTypes);
515   var arraySchemaTypes = validator.getAllTypesForSchema(arraySchema);
516   assertEqualSets(arraySchemaTypes, ["array"]);
518   var choicesSchemaTypes = validator.getAllTypesForSchema(choicesSchema);
519   assertEqualSets(choicesSchemaTypes, ["object", "function"]);
521   var objectRefSchemaTypes = validator.getAllTypesForSchema(objectRefSchema);
522   assertEqualSets(objectRefSchemaTypes, ["object"]);
524   var complexSchemaTypes = validator.getAllTypesForSchema(complexSchema);
525   assertEqualSets(complexSchemaTypes,
526       ["integer", "string", "function", "object"]);
529 function testIsValidSchemaType() {
530   var referencedTypes = [
531     {
532       id: "ChoicesRef",
533       choices: [
534         { type: "integer" },
535         { type: "string" }
536       ]
537     }
538   ];
540   var objectSchema = {
541     type: "object",
542     optional: true
543   };
545   var complexSchema = {
546     choices: [
547       { $ref: "ChoicesRef" },
548       { type: "function" },
549     ]
550   };
552   var validator = new JSONSchemaValidator();
553   validator.addTypes(referencedTypes);
555   AssertTrue(validator.isValidSchemaType("object", objectSchema));
556   AssertTrue(!validator.isValidSchemaType("integer", objectSchema));
557   AssertTrue(!validator.isValidSchemaType("array", objectSchema));
558   AssertTrue(validator.isValidSchemaType("null", objectSchema));
559   AssertTrue(validator.isValidSchemaType("undefined", objectSchema));
561   AssertTrue(validator.isValidSchemaType("integer", complexSchema));
562   AssertTrue(validator.isValidSchemaType("function", complexSchema));
563   AssertTrue(validator.isValidSchemaType("string", complexSchema));
564   AssertTrue(!validator.isValidSchemaType("object", complexSchema));
565   AssertTrue(!validator.isValidSchemaType("null", complexSchema));
566   AssertTrue(!validator.isValidSchemaType("undefined", complexSchema));
569 function testCheckSchemaOverlap() {
570   var referencedTypes = [
571     {
572       id: "ChoicesRef",
573       choices: [
574         { type: "integer" },
575         { type: "string" }
576       ]
577     },
578     {
579       id: "ObjectRef",
580       type: "object",
581     }
582   ];
584   var arraySchema = {
585     type: "array"
586   };
588   var choicesSchema = {
589     choices: [
590       { type: "object" },
591       { type: "function" }
592     ]
593   };
595   var objectRefSchema = {
596     $ref: "ObjectRef"
597   };
599   var complexSchema = {
600     choices: [
601       { $ref: "ChoicesRef" },
602       { type: "function" },
603       { $ref: "ObjectRef" }
604     ]
605   };
607   var validator = new JSONSchemaValidator();
608   validator.addTypes(referencedTypes);
610   AssertTrue(!validator.checkSchemaOverlap(arraySchema, choicesSchema));
611   AssertTrue(!validator.checkSchemaOverlap(arraySchema, objectRefSchema));
612   AssertTrue(!validator.checkSchemaOverlap(arraySchema, complexSchema));
613   AssertTrue(validator.checkSchemaOverlap(choicesSchema, objectRefSchema));
614   AssertTrue(validator.checkSchemaOverlap(choicesSchema, complexSchema));
615   AssertTrue(validator.checkSchemaOverlap(objectRefSchema, complexSchema));
618 function testInstanceOf() {
619   function Animal() {};
620   function Cat() {};
621   function Dog() {};
622   Cat.prototype = new Animal;
623   Cat.prototype.constructor = Cat;
624   Dog.prototype = new Animal;
625   Dog.prototype.constructor = Dog;
626   var cat = new Cat();
627   var dog = new Dog();
628   var num = new Number(1);
630   // instanceOf should check type by walking up prototype chain.
631   assertValid("", cat, {type:"object", isInstanceOf:"Cat"});
632   assertValid("", cat, {type:"object", isInstanceOf:"Animal"});
633   assertValid("", cat, {type:"object", isInstanceOf:"Object"});
634   assertValid("", dog, {type:"object", isInstanceOf:"Dog"});
635   assertValid("", dog, {type:"object", isInstanceOf:"Animal"});
636   assertValid("", dog, {type:"object", isInstanceOf:"Object"});
637   assertValid("", num, {type:"object", isInstanceOf:"Number"});
638   assertValid("", num, {type:"object", isInstanceOf:"Object"});
640   assertNotValid("", cat, {type:"object", isInstanceOf:"Dog"},
641                  [formatError("notInstance", ["Dog"])]);
642   assertNotValid("", dog, {type:"object", isInstanceOf:"Cat"},
643                  [formatError("notInstance", ["Cat"])]);
644   assertNotValid("", cat, {type:"object", isInstanceOf:"String"},
645                  [formatError("notInstance", ["String"])]);
646   assertNotValid("", dog, {type:"object", isInstanceOf:"String"},
647                  [formatError("notInstance", ["String"])]);
648   assertNotValid("", num, {type:"object", isInstanceOf:"Array"},
649                 [formatError("notInstance", ["Array"])]);
650   assertNotValid("", num, {type:"object", isInstanceOf:"String"},
651                 [formatError("notInstance", ["String"])]);
654 // Tests exposed to schema_unittest.cc.
655 exports.testFormatError = testFormatError;
656 exports.testComplex = testComplex;
657 exports.testEnum = testEnum;
658 exports.testExtends = testExtends;
659 exports.testObject = testObject;
660 exports.testArrayTuple = testArrayTuple;
661 exports.testArrayNonTuple = testArrayNonTuple;
662 exports.testString = testString;
663 exports.testNumber = testNumber;
664 exports.testIntegerBounds = testIntegerBounds;
665 exports.testType = testType;
666 exports.testTypeReference = testTypeReference;
667 exports.testGetAllTypesForSchema = testGetAllTypesForSchema;
668 exports.testIsValidSchemaType = testIsValidSchemaType;
669 exports.testCheckSchemaOverlap = testCheckSchemaOverlap;
670 exports.testInstanceOf = testInstanceOf;