diff --git a/spec/issues.spec.js b/spec/issues.spec.js deleted file mode 100644 index 0a6e570..0000000 --- a/spec/issues.spec.js +++ /dev/null @@ -1,815 +0,0 @@ -'use strict'; - -var Ajv = require('./ajv') - , should = require('./chai').should(); - - -describe('issue #8: schema with shared references', function() { - it('should be supported by addSchema', spec('addSchema')); - - it('should be supported by compile', spec('compile')); - - function spec(method) { - return function() { - var ajv = new Ajv; - - var propertySchema = { - type: 'string', - maxLength: 4 - }; - - var schema = { - $id: 'obj.json#', - type: 'object', - properties: { - foo: propertySchema, - bar: propertySchema - } - }; - - ajv[method](schema); - - var result = ajv.validate('obj.json#', { foo: 'abc', bar: 'def' }); - result .should.equal(true); - - result = ajv.validate('obj.json#', { foo: 'abcde', bar: 'fghg' }); - result .should.equal(false); - ajv.errors .should.have.length(1); - }; - } -}); - -describe('issue #50: references with "definitions"', function () { - it('should be supported by addSchema', spec('addSchema')); - - it('should be supported by compile', spec('addSchema')); - - function spec(method) { - return function() { - var result; - - var ajv = new Ajv; - - ajv[method]({ - $id: 'http://example.com/test/person.json#', - definitions: { - name: { type: 'string' } - }, - type: 'object', - properties: { - name: { $ref: '#/definitions/name'} - } - }); - - ajv[method]({ - $id: 'http://example.com/test/employee.json#', - type: 'object', - properties: { - person: { $ref: '/test/person.json#' }, - role: { type: 'string' } - } - }); - - result = ajv.validate('http://example.com/test/employee.json#', { - person: { - name: 'Alice' - }, - role: 'Programmer' - }); - - result. should.equal(true); - should.equal(ajv.errors, null); - }; - } -}); - - -describe('issue #182, NaN validation', function() { - it('should not pass minimum/maximum validation', function() { - testNaN({ minimum: 1 }, false); - testNaN({ maximum: 1 }, false); - }); - - it('should pass type: number validation', function() { - testNaN({ type: 'number' }, true); - }); - - it('should not pass type: integer validation', function() { - testNaN({ type: 'integer' }, false); - }); - - function testNaN(schema, NaNisValid) { - var ajv = new Ajv; - var validate = ajv.compile(schema); - validate(NaN) .should.equal(NaNisValid); - } -}); - - -describe('issue #204, options schemas and $data used together', function() { - it('should use v5 metaschemas by default', function() { - var ajv = new Ajv({ - schemas: [{$id: 'str', type: 'string'}], - $data: true - }); - - var schema = { const: 42 }; - var validate = ajv.compile(schema); - - validate(42) .should.equal(true); - validate(43) .should.equal(false); - - ajv.validate('str', 'foo') .should.equal(true); - ajv.validate('str', 42) .should.equal(false); - }); -}); - - -describe('issue #181, custom keyword is not validated in allErrors mode if there were previous error', function() { - it('should validate custom keyword that doesn\'t create errors', function() { - testCustomKeywordErrors({ - type:'object', - errors: true, - validate: function v(/* value */) { - return false; - } - }); - }); - - it('should validate custom keyword that creates errors', function() { - testCustomKeywordErrors({ - type:'object', - errors: true, - validate: function v(/* value */) { - v.errors = v.errors || []; - v.errors.push({ - keyword: 'alwaysFails', - message: 'alwaysFails error', - params: { - keyword: 'alwaysFails' - } - }); - - return false; - } - }); - }); - - function testCustomKeywordErrors(def) { - var ajv = new Ajv({ allErrors: true }); - - ajv.addKeyword('alwaysFails', def); - - var schema = { - required: ['foo'], - alwaysFails: true - }; - - var validate = ajv.compile(schema); - - validate({ foo: 1 }) .should.equal(false); - validate.errors .should.have.length(1); - validate.errors[0].keyword .should.equal('alwaysFails'); - - validate({}) .should.equal(false); - validate.errors .should.have.length(2); - validate.errors[0].keyword .should.equal('required'); - validate.errors[1].keyword .should.equal('alwaysFails'); - } -}); - - -describe('issue #210, mutual recursive $refs that are schema fragments', function() { - it('should compile and validate schema when one ref is fragment', function() { - var ajv = new Ajv; - - ajv.addSchema({ - "$id" : "foo", - "definitions": { - "bar": { - "properties": { - "baz": { - "anyOf": [ - { "enum": [42] }, - { "$ref": "boo" } - ] - } - } - } - } - }); - - ajv.addSchema({ - "$id" : "boo", - "type": "object", - "required": ["quux"], - "properties": { - "quux": { "$ref": "foo#/definitions/bar" } - } - }); - - var validate = ajv.compile({ "$ref": "foo#/definitions/bar" }); - - validate({ baz: { quux: { baz: 42 } } }) .should.equal(true); - validate({ baz: { quux: { baz: "foo" } } }) .should.equal(false); - }); - - it('should compile and validate schema when both refs are fragments', function() { - var ajv = new Ajv; - - ajv.addSchema({ - "$id" : "foo", - "definitions": { - "bar": { - "properties": { - "baz": { - "anyOf": [ - { "enum": [42] }, - { "$ref": "boo#/definitions/buu" } - ] - } - } - } - } - }); - - ajv.addSchema({ - "$id" : "boo", - "definitions": { - "buu": { - "type": "object", - "required": ["quux"], - "properties": { - "quux": { "$ref": "foo#/definitions/bar" } - } - } - } - }); - - var validate = ajv.compile({ "$ref": "foo#/definitions/bar" }); - - validate({ baz: { quux: { baz: 42 } } }) .should.equal(true); - validate({ baz: { quux: { baz: "foo" } } }) .should.equal(false); - }); -}); - - -describe('issue #240, mutually recursive fragment refs reference a common schema', function() { - var apiSchema = { - $schema: 'http://json-schema.org/draft-07/schema#', - $id: 'schema://api.schema#', - resource: { - $id: '#resource', - properties: { - id: { type: 'string' } - } - }, - resourceIdentifier: { - $id: '#resource_identifier', - properties: { - id: { type: 'string' }, - type: { type: 'string' } - } - } - }; - - var domainSchema = { - $schema: 'http://json-schema.org/draft-07/schema#', - $id: 'schema://domain.schema#', - properties: { - data: { - oneOf: [ - { $ref: 'schema://library.schema#resource_identifier' }, - { $ref: 'schema://catalog_item.schema#resource_identifier' }, - ] - } - } - }; - - it('should compile and validate schema when one ref is fragment', function() { - var ajv = new Ajv; - - var librarySchema = { - $schema: 'http://json-schema.org/draft-07/schema#', - $id: 'schema://library.schema#', - properties: { - name: { type: 'string' }, - links: { - properties: { - catalogItems: { - type: 'array', - items: { $ref: 'schema://catalog_item_resource_identifier.schema#' } - } - } - } - }, - definitions: { - resource_identifier: { - $id: '#resource_identifier', - allOf: [ - { - properties: { - type: { - type: 'string', - 'enum': ['Library'] - } - } - }, - { $ref: 'schema://api.schema#resource_identifier' } - ] - } - } - }; - - var catalogItemSchema = { - $schema: 'http://json-schema.org/draft-07/schema#', - $id: 'schema://catalog_item.schema#', - properties: { - name: { type: 'string' }, - links: { - properties: { - library: { $ref: 'schema://library.schema#resource_identifier' } - } - } - }, - definitions: { - resource_identifier: { - $id: '#resource_identifier', - allOf: [ - { - properties: { - type: { - type: 'string', - 'enum': ['CatalogItem'] - } - } - }, - { $ref: 'schema://api.schema#resource_identifier' } - ] - } - } - }; - - var catalogItemResourceIdentifierSchema = { - $schema: 'http://json-schema.org/draft-07/schema#', - $id: 'schema://catalog_item_resource_identifier.schema#', - allOf: [ - { - properties: { - type: { - type: 'string', - enum: ['CatalogItem'] - } - } - }, - { - $ref: 'schema://api.schema#resource_identifier' - } - ] - }; - - ajv.addSchema(librarySchema); - ajv.addSchema(catalogItemSchema); - ajv.addSchema(catalogItemResourceIdentifierSchema); - ajv.addSchema(apiSchema); - - var validate = ajv.compile(domainSchema); - testSchema(validate); - }); - - it('should compile and validate schema when both refs are fragments', function() { - var ajv = new Ajv; - - var librarySchema = { - $schema: 'http://json-schema.org/draft-07/schema#', - $id: 'schema://library.schema#', - properties: { - name: { type: 'string' }, - links: { - properties: { - catalogItems: { - type: 'array', - items: { $ref: 'schema://catalog_item.schema#resource_identifier' } - } - } - } - }, - definitions: { - resource_identifier: { - $id: '#resource_identifier', - allOf: [ - { - properties: { - type: { - type: 'string', - 'enum': ['Library'] - } - } - }, - { $ref: 'schema://api.schema#resource_identifier' } - ] - } - } - }; - - var catalogItemSchema = { - $schema: 'http://json-schema.org/draft-07/schema#', - $id: 'schema://catalog_item.schema#', - properties: { - name: { type: 'string' }, - links: { - properties: { - library: { $ref: 'schema://library.schema#resource_identifier' } - } - } - }, - definitions: { - resource_identifier: { - $id: '#resource_identifier', - allOf: [ - { - properties: { - type: { - type: 'string', - 'enum': ['CatalogItem'] - } - } - }, - { $ref: 'schema://api.schema#resource_identifier' } - ] - } - } - }; - - ajv.addSchema(librarySchema); - ajv.addSchema(catalogItemSchema); - ajv.addSchema(apiSchema); - - var validate = ajv.compile(domainSchema); - testSchema(validate); - }); - - - function testSchema(validate) { - validate({ data: { type: 'Library', id: '123' } }) .should.equal(true); - validate({ data: { type: 'Library', id: 123 } }) .should.equal(false); - validate({ data: { type: 'CatalogItem', id: '123' } }) .should.equal(true); - validate({ data: { type: 'CatalogItem', id: 123 } }) .should.equal(false); - validate({ data: { type: 'Foo', id: '123' } }) .should.equal(false); - } -}); - - -describe('issue #259, support validating [meta-]schemas against themselves', function() { - it('should add schema before validation if "id" is the same as "$schema"', function() { - var ajv = new Ajv; - var hyperSchema = require('./remotes/hyper-schema.json'); - ajv.addMetaSchema(hyperSchema); - }); -}); - - -describe.skip('issue #273, schemaPath in error in referenced schema', function() { - it('should have canonic reference with hash after file name', function() { - test(new Ajv); - test(new Ajv({inlineRefs: false})); - - function test(ajv) { - var schema = { - "properties": { - "a": { "$ref": "int" } - } - }; - - var referencedSchema = { - "id": "int", - "type": "integer" - }; - - ajv.addSchema(referencedSchema); - var validate = ajv.compile(schema); - - validate({ "a": "foo" }) .should.equal(false); - validate.errors[0].schemaPath .should.equal('int#/type'); - } - }); -}); - - -describe('issue #342, support uniqueItems with some non-JSON objects', function() { - var validate; - - before(function() { - var ajv = new Ajv; - validate = ajv.compile({ uniqueItems: true }); - }); - - it('should allow different RegExps', function() { - validate([/foo/, /bar/]) .should.equal(true); - validate([/foo/ig, /foo/gi]) .should.equal(false); - validate([/foo/, {}]) .should.equal(true); - }); - - it('should allow different Dates', function() { - validate([new Date('2016-11-11'), new Date('2016-11-12')]) .should.equal(true); - validate([new Date('2016-11-11'), new Date('2016-11-11')]) .should.equal(false); - validate([new Date('2016-11-11'), {}]) .should.equal(true); - }); - - it('should allow undefined properties', function() { - validate([{}, {foo: undefined}]) .should.equal(true); - validate([{foo: undefined}, {}]) .should.equal(true); - validate([{foo: undefined}, {bar: undefined}]) .should.equal(true); - validate([{foo: undefined}, {foo: undefined}]) .should.equal(false); - }); -}); - - -describe('issue #388, code clean-up not working', function() { - it('should remove assignement to rootData if it is not used', function() { - var ajv = new Ajv; - var validate = ajv.compile({ - type: 'object', - properties: { - foo: { type: 'string' } - } - }); - var code = validate.toString(); - code.match(/rootData/g).length .should.equal(1); - }); - - it('should remove assignement to errors if they are not used', function() { - var ajv = new Ajv; - var validate = ajv.compile({ - type: 'object' - }); - var code = validate.toString(); - should.equal(code.match(/[^.]errors|vErrors/g), null); - }); -}); - - -describe('issue #485, order of type validation', function() { - it('should validate types befor keywords', function() { - var ajv = new Ajv({allErrors: true}); - var validate = ajv.compile({ - type: ['integer', 'string'], - required: ['foo'], - minimum: 2 - }); - - validate(2) .should.equal(true); - validate('foo') .should.equal(true); - - validate(1.5) .should.equal(false); - checkErrors(['type', 'minimum']); - - validate({}) .should.equal(false); - checkErrors(['type', 'required']); - - function checkErrors(expectedErrs) { - validate.errors .should.have.length(expectedErrs.length); - expectedErrs.forEach(function (keyword, i) { - validate.errors[i].keyword .should.equal(keyword); - }); - } - }); -}); - - -describe('issue #521, incorrect warning with "id" property', function() { - it('should not log warning', function() { - var ajv = new Ajv({schemaId: '$id'}); - var consoleWarn = console.warn; - console.warn = function() { - throw new Error('should not log warning'); - }; - - try { - ajv.compile({ - "$id": "http://example.com/schema.json", - "type": "object", - "properties": { - "id": {"type": "string"}, - }, - "required": [ "id"] - }); - } finally { - console.warn = consoleWarn; - } - }); -}); - - -describe('issue #533, throwing missing ref exception with option missingRefs: "ignore"', function() { - var schema = { - "type": "object", - "properties": { - "foo": {"$ref": "#/definitions/missing"}, - "bar": {"$ref": "#/definitions/missing"} - } - }; - - it('should pass validation without throwing exception', function() { - var ajv = new Ajv({missingRefs: 'ignore'}); - var validate = ajv.compile(schema); - validate({foo: 'anything'}) .should.equal(true); - validate({foo: 'anything', bar: 'whatever'}) .should.equal(true); - }); - - it('should throw exception during schema compilation with option missingRefs: true', function() { - var ajv = new Ajv; - should.throw(function() { - ajv.compile(schema); - }); - }); -}); - -describe('full date format validation should understand leap years', function () { - it('should handle non leap year affected dates with date-time', function() { - var ajv = new Ajv({ format: 'full' }); - - var schema = { format: 'date-time' }; - var validDateTime = '2016-01-31T00:00:00Z'; - - ajv.validate(schema, validDateTime).should.equal(true); - }); - - it('should handle non leap year affected dates with date', function () { - var ajv = new Ajv({ format: 'full' }); - - var schema = { format: 'date' }; - var validDate = '2016-11-30'; - - ajv.validate(schema, validDate).should.equal(true); - }); - - it('should handle year leaps as date-time', function() { - var ajv = new Ajv({ format: 'full' }); - - var schema = { format: 'date-time' }; - var validDateTime = '2016-02-29T00:00:00Z'; - var invalidDateTime = '2017-02-29T00:00:00Z'; - - ajv.validate(schema, validDateTime) .should.equal(true); - ajv.validate(schema, invalidDateTime) .should.equal(false); - }); - - it('should handle year leaps as date', function() { - var ajv = new Ajv({ format: 'full' }); - - var schema = { format: 'date' }; - var validDate = '2016-02-29'; - var invalidDate = '2017-02-29'; - - ajv.validate(schema, validDate) .should.equal(true); - ajv.validate(schema, invalidDate) .should.equal(false); - }); -}); - - -describe('property __proto__ should be removed with removeAdditional option, issue #743', function() { - it('should remove additional properties', function() { - var ajv = new Ajv({removeAdditional: true}); - - var schema = { - properties: { - obj: { - additionalProperties: false, - properties: { - a: { type: 'string' }, - b: { type: 'string' }, - c: { type: 'string' }, - d: { type: 'string' }, - e: { type: 'string' }, - f: { type: 'string' }, - g: { type: 'string' }, - h: { type: 'string' }, - i: { type: 'string' } - } - } - } - }; - - var obj= Object.create(null); - obj.__proto__ = null; // should be removed - obj.additional = 'will be removed'; - obj.a = 'valid'; - obj.b = 'valid'; - - var data = {obj: obj}; - - ajv.validate(schema, data) .should.equal(true); - Object.keys(data.obj) .should.eql(['a', 'b']); - }); -}); - - -describe('issue #768, fix passContext in recursive $ref', function() { - var ajv, contexts; - - beforeEach(function() { - contexts = []; - }); - - describe('passContext = true', function() { - it('should pass this value as context to custom keyword validation function', function() { - var validate = getValidate(true); - var self = {}; - validate.call(self, { bar: 'a', baz: { bar: 'b' } }); - contexts .should.have.length(2); - contexts.forEach(function(ctx) { - ctx .should.equal(self); - }); - }); - }); - - describe('passContext = false', function() { - it('should pass ajv instance as context to custom keyword validation function', function() { - var validate = getValidate(false); - validate({ bar: 'a', baz: { bar: 'b' } }); - contexts .should.have.length(2); - contexts.forEach(function(ctx) { - ctx .should.equal(ajv); - }); - }); - }); - - describe('ref is fragment and passContext = true', function() { - it('should pass this value as context to custom keyword validation function', function() { - var validate = getValidateFragments(true); - var self = {}; - validate.call(self, { baz: { corge: 'a', quux: { baz: { corge: 'b' } } } }); - contexts .should.have.length(2); - contexts.forEach(function(ctx) { - ctx .should.equal(self); - }); - }); - }); - - describe('ref is fragment and passContext = false', function() { - it('should pass ajv instance as context to custom keyword validation function', function() { - var validate = getValidateFragments(false); - validate({ baz: { corge: 'a', quux: { baz: { corge: 'b' } } } }); - contexts .should.have.length(2); - contexts.forEach(function(ctx) { - ctx .should.equal(ajv); - }); - }); - }); - - function getValidate(passContext) { - ajv = new Ajv({ passContext: passContext }); - ajv.addKeyword('testValidate', { validate: storeContext }); - - var schema = { - "$id" : "foo", - "type": "object", - "required": ["bar"], - "properties": { - "bar": { "testValidate": true }, - "baz": { - "$ref": "foo" - } - } - }; - - return ajv.compile(schema); - } - - - function getValidateFragments(passContext) { - ajv = new Ajv({ passContext: passContext }); - ajv.addKeyword('testValidate', { validate: storeContext }); - - ajv.addSchema({ - "$id" : "foo", - "definitions": { - "bar": { - "properties": { - "baz": { - "$ref": "boo" - } - } - } - } - }); - - ajv.addSchema({ - "$id" : "boo", - "type": "object", - "required": ["corge"], - "properties": { - "quux": { "$ref": "foo#/definitions/bar" }, - "corge": { "testValidate": true } - } - }); - - return ajv.compile({ "$ref": "foo#/definitions/bar" }); - } - - function storeContext() { - contexts.push(this); - return true; - } -}); diff --git a/spec/issues/181_allErrors_custom_keyword_skipped.spec.js b/spec/issues/181_allErrors_custom_keyword_skipped.spec.js new file mode 100644 index 0000000..aa734aa --- /dev/null +++ b/spec/issues/181_allErrors_custom_keyword_skipped.spec.js @@ -0,0 +1,58 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #181, custom keyword is not validated in allErrors mode if there were previous error', function() { + it('should validate custom keyword that doesn\'t create errors', function() { + testCustomKeywordErrors({ + type:'object', + errors: true, + validate: function v(/* value */) { + return false; + } + }); + }); + + it('should validate custom keyword that creates errors', function() { + testCustomKeywordErrors({ + type:'object', + errors: true, + validate: function v(/* value */) { + v.errors = v.errors || []; + v.errors.push({ + keyword: 'alwaysFails', + message: 'alwaysFails error', + params: { + keyword: 'alwaysFails' + } + }); + + return false; + } + }); + }); + + function testCustomKeywordErrors(def) { + var ajv = new Ajv({ allErrors: true }); + + ajv.addKeyword('alwaysFails', def); + + var schema = { + required: ['foo'], + alwaysFails: true + }; + + var validate = ajv.compile(schema); + + validate({ foo: 1 }) .should.equal(false); + validate.errors .should.have.length(1); + validate.errors[0].keyword .should.equal('alwaysFails'); + + validate({}) .should.equal(false); + validate.errors .should.have.length(2); + validate.errors[0].keyword .should.equal('required'); + validate.errors[1].keyword .should.equal('alwaysFails'); + } +}); diff --git a/spec/issues/182_nan_validation.spec.js b/spec/issues/182_nan_validation.spec.js new file mode 100644 index 0000000..4d341a3 --- /dev/null +++ b/spec/issues/182_nan_validation.spec.js @@ -0,0 +1,26 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #182, NaN validation', function() { + it('should not pass minimum/maximum validation', function() { + testNaN({ minimum: 1 }, false); + testNaN({ maximum: 1 }, false); + }); + + it('should pass type: number validation', function() { + testNaN({ type: 'number' }, true); + }); + + it('should not pass type: integer validation', function() { + testNaN({ type: 'integer' }, false); + }); + + function testNaN(schema, NaNisValid) { + var ajv = new Ajv; + var validate = ajv.compile(schema); + validate(NaN) .should.equal(NaNisValid); + } +}); diff --git a/spec/issues/204_options_schemas_data_together.spec.js b/spec/issues/204_options_schemas_data_together.spec.js new file mode 100644 index 0000000..73746c1 --- /dev/null +++ b/spec/issues/204_options_schemas_data_together.spec.js @@ -0,0 +1,23 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #204, options schemas and $data used together', function() { + it('should use v5 metaschemas by default', function() { + var ajv = new Ajv({ + schemas: [{$id: 'str', type: 'string'}], + $data: true + }); + + var schema = { const: 42 }; + var validate = ajv.compile(schema); + + validate(42) .should.equal(true); + validate(43) .should.equal(false); + + ajv.validate('str', 'foo') .should.equal(true); + ajv.validate('str', 42) .should.equal(false); + }); +}); diff --git a/spec/issues/210_mutual_recur_frags.spec.js b/spec/issues/210_mutual_recur_frags.spec.js new file mode 100644 index 0000000..58847f0 --- /dev/null +++ b/spec/issues/210_mutual_recur_frags.spec.js @@ -0,0 +1,79 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #210, mutual recursive $refs that are schema fragments', function() { + it('should compile and validate schema when one ref is fragment', function() { + var ajv = new Ajv; + + ajv.addSchema({ + "$id" : "foo", + "definitions": { + "bar": { + "properties": { + "baz": { + "anyOf": [ + { "enum": [42] }, + { "$ref": "boo" } + ] + } + } + } + } + }); + + ajv.addSchema({ + "$id" : "boo", + "type": "object", + "required": ["quux"], + "properties": { + "quux": { "$ref": "foo#/definitions/bar" } + } + }); + + var validate = ajv.compile({ "$ref": "foo#/definitions/bar" }); + + validate({ baz: { quux: { baz: 42 } } }) .should.equal(true); + validate({ baz: { quux: { baz: "foo" } } }) .should.equal(false); + }); + + it('should compile and validate schema when both refs are fragments', function() { + var ajv = new Ajv; + + ajv.addSchema({ + "$id" : "foo", + "definitions": { + "bar": { + "properties": { + "baz": { + "anyOf": [ + { "enum": [42] }, + { "$ref": "boo#/definitions/buu" } + ] + } + } + } + } + }); + + ajv.addSchema({ + "$id" : "boo", + "definitions": { + "buu": { + "type": "object", + "required": ["quux"], + "properties": { + "quux": { "$ref": "foo#/definitions/bar" } + } + } + } + }); + + var validate = ajv.compile({ "$ref": "foo#/definitions/bar" }); + + validate({ baz: { quux: { baz: 42 } } }) .should.equal(true); + validate({ baz: { quux: { baz: "foo" } } }) .should.equal(false); + }); +}); diff --git a/spec/issues/240_mutual_recur_frags_common_ref.spec.js b/spec/issues/240_mutual_recur_frags_common_ref.spec.js new file mode 100644 index 0000000..d9e4024 --- /dev/null +++ b/spec/issues/240_mutual_recur_frags_common_ref.spec.js @@ -0,0 +1,210 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #240, mutually recursive fragment refs reference a common schema', function() { + var apiSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'schema://api.schema#', + resource: { + $id: '#resource', + properties: { + id: { type: 'string' } + } + }, + resourceIdentifier: { + $id: '#resource_identifier', + properties: { + id: { type: 'string' }, + type: { type: 'string' } + } + } + }; + + var domainSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'schema://domain.schema#', + properties: { + data: { + oneOf: [ + { $ref: 'schema://library.schema#resource_identifier' }, + { $ref: 'schema://catalog_item.schema#resource_identifier' }, + ] + } + } + }; + + it('should compile and validate schema when one ref is fragment', function() { + var ajv = new Ajv; + + var librarySchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'schema://library.schema#', + properties: { + name: { type: 'string' }, + links: { + properties: { + catalogItems: { + type: 'array', + items: { $ref: 'schema://catalog_item_resource_identifier.schema#' } + } + } + } + }, + definitions: { + resource_identifier: { + $id: '#resource_identifier', + allOf: [ + { + properties: { + type: { + type: 'string', + 'enum': ['Library'] + } + } + }, + { $ref: 'schema://api.schema#resource_identifier' } + ] + } + } + }; + + var catalogItemSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'schema://catalog_item.schema#', + properties: { + name: { type: 'string' }, + links: { + properties: { + library: { $ref: 'schema://library.schema#resource_identifier' } + } + } + }, + definitions: { + resource_identifier: { + $id: '#resource_identifier', + allOf: [ + { + properties: { + type: { + type: 'string', + 'enum': ['CatalogItem'] + } + } + }, + { $ref: 'schema://api.schema#resource_identifier' } + ] + } + } + }; + + var catalogItemResourceIdentifierSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'schema://catalog_item_resource_identifier.schema#', + allOf: [ + { + properties: { + type: { + type: 'string', + enum: ['CatalogItem'] + } + } + }, + { + $ref: 'schema://api.schema#resource_identifier' + } + ] + }; + + ajv.addSchema(librarySchema); + ajv.addSchema(catalogItemSchema); + ajv.addSchema(catalogItemResourceIdentifierSchema); + ajv.addSchema(apiSchema); + + var validate = ajv.compile(domainSchema); + testSchema(validate); + }); + + it('should compile and validate schema when both refs are fragments', function() { + var ajv = new Ajv; + + var librarySchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'schema://library.schema#', + properties: { + name: { type: 'string' }, + links: { + properties: { + catalogItems: { + type: 'array', + items: { $ref: 'schema://catalog_item.schema#resource_identifier' } + } + } + } + }, + definitions: { + resource_identifier: { + $id: '#resource_identifier', + allOf: [ + { + properties: { + type: { + type: 'string', + 'enum': ['Library'] + } + } + }, + { $ref: 'schema://api.schema#resource_identifier' } + ] + } + } + }; + + var catalogItemSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'schema://catalog_item.schema#', + properties: { + name: { type: 'string' }, + links: { + properties: { + library: { $ref: 'schema://library.schema#resource_identifier' } + } + } + }, + definitions: { + resource_identifier: { + $id: '#resource_identifier', + allOf: [ + { + properties: { + type: { + type: 'string', + 'enum': ['CatalogItem'] + } + } + }, + { $ref: 'schema://api.schema#resource_identifier' } + ] + } + } + }; + + ajv.addSchema(librarySchema); + ajv.addSchema(catalogItemSchema); + ajv.addSchema(apiSchema); + + var validate = ajv.compile(domainSchema); + testSchema(validate); + }); + + + function testSchema(validate) { + validate({ data: { type: 'Library', id: '123' } }) .should.equal(true); + validate({ data: { type: 'Library', id: 123 } }) .should.equal(false); + validate({ data: { type: 'CatalogItem', id: '123' } }) .should.equal(true); + validate({ data: { type: 'CatalogItem', id: 123 } }) .should.equal(false); + validate({ data: { type: 'Foo', id: '123' } }) .should.equal(false); + } +}); diff --git a/spec/issues/259_validate_meta_against_itself.spec.js b/spec/issues/259_validate_meta_against_itself.spec.js new file mode 100644 index 0000000..d99fc21 --- /dev/null +++ b/spec/issues/259_validate_meta_against_itself.spec.js @@ -0,0 +1,13 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #259, support validating [meta-]schemas against themselves', function() { + it('should add schema before validation if "id" is the same as "$schema"', function() { + var ajv = new Ajv; + var hyperSchema = require('../remotes/hyper-schema.json'); + ajv.addMetaSchema(hyperSchema); + }); +}); diff --git a/spec/issues/273_error_schemaPath_refd_schema.spec.js b/spec/issues/273_error_schemaPath_refd_schema.spec.js new file mode 100644 index 0000000..9271640 --- /dev/null +++ b/spec/issues/273_error_schemaPath_refd_schema.spec.js @@ -0,0 +1,31 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe.skip('issue #273, schemaPath in error in referenced schema', function() { + it('should have canonic reference with hash after file name', function() { + test(new Ajv); + test(new Ajv({inlineRefs: false})); + + function test(ajv) { + var schema = { + "properties": { + "a": { "$ref": "int" } + } + }; + + var referencedSchema = { + "id": "int", + "type": "integer" + }; + + ajv.addSchema(referencedSchema); + var validate = ajv.compile(schema); + + validate({ "a": "foo" }) .should.equal(false); + validate.errors[0].schemaPath .should.equal('int#/type'); + } + }); +}); diff --git a/spec/issues/342_uniqueItems_non-json_objects.spec.js b/spec/issues/342_uniqueItems_non-json_objects.spec.js new file mode 100644 index 0000000..ae7eee0 --- /dev/null +++ b/spec/issues/342_uniqueItems_non-json_objects.spec.js @@ -0,0 +1,33 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #342, support uniqueItems with some non-JSON objects', function() { + var validate; + + before(function() { + var ajv = new Ajv; + validate = ajv.compile({ uniqueItems: true }); + }); + + it('should allow different RegExps', function() { + validate([/foo/, /bar/]) .should.equal(true); + validate([/foo/ig, /foo/gi]) .should.equal(false); + validate([/foo/, {}]) .should.equal(true); + }); + + it('should allow different Dates', function() { + validate([new Date('2016-11-11'), new Date('2016-11-12')]) .should.equal(true); + validate([new Date('2016-11-11'), new Date('2016-11-11')]) .should.equal(false); + validate([new Date('2016-11-11'), {}]) .should.equal(true); + }); + + it('should allow undefined properties', function() { + validate([{}, {foo: undefined}]) .should.equal(true); + validate([{foo: undefined}, {}]) .should.equal(true); + validate([{foo: undefined}, {bar: undefined}]) .should.equal(true); + validate([{foo: undefined}, {foo: undefined}]) .should.equal(false); + }); +}); diff --git a/spec/issues/388_code_clean-up not.spec.js b/spec/issues/388_code_clean-up not.spec.js new file mode 100644 index 0000000..9a02883 --- /dev/null +++ b/spec/issues/388_code_clean-up not.spec.js @@ -0,0 +1,28 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('issue #388, code clean-up not working', function() { + it('should remove assignement to rootData if it is not used', function() { + var ajv = new Ajv; + var validate = ajv.compile({ + type: 'object', + properties: { + foo: { type: 'string' } + } + }); + var code = validate.toString(); + code.match(/rootData/g).length .should.equal(1); + }); + + it('should remove assignement to errors if they are not used', function() { + var ajv = new Ajv; + var validate = ajv.compile({ + type: 'object' + }); + var code = validate.toString(); + should.equal(code.match(/[^.]errors|vErrors/g), null); + }); +}); diff --git a/spec/issues/485_type_validation_priority.spec.js b/spec/issues/485_type_validation_priority.spec.js new file mode 100644 index 0000000..eb2b1e9 --- /dev/null +++ b/spec/issues/485_type_validation_priority.spec.js @@ -0,0 +1,32 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #485, order of type validation', function() { + it('should validate types before keywords', function() { + var ajv = new Ajv({allErrors: true}); + var validate = ajv.compile({ + type: ['integer', 'string'], + required: ['foo'], + minimum: 2 + }); + + validate(2) .should.equal(true); + validate('foo') .should.equal(true); + + validate(1.5) .should.equal(false); + checkErrors(['type', 'minimum']); + + validate({}) .should.equal(false); + checkErrors(['type', 'required']); + + function checkErrors(expectedErrs) { + validate.errors .should.have.length(expectedErrs.length); + expectedErrs.forEach(function (keyword, i) { + validate.errors[i].keyword .should.equal(keyword); + }); + } + }); +}); diff --git a/spec/issues/50_refs_with_definitions.spec.js b/spec/issues/50_refs_with_definitions.spec.js new file mode 100644 index 0000000..26b84a8 --- /dev/null +++ b/spec/issues/50_refs_with_definitions.spec.js @@ -0,0 +1,49 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('issue #50: references with "definitions"', function () { + it('should be supported by addSchema', spec('addSchema')); + + it('should be supported by compile', spec('addSchema')); + + function spec(method) { + return function() { + var result; + + var ajv = new Ajv; + + ajv[method]({ + $id: 'http://example.com/test/person.json#', + definitions: { + name: { type: 'string' } + }, + type: 'object', + properties: { + name: { $ref: '#/definitions/name'} + } + }); + + ajv[method]({ + $id: 'http://example.com/test/employee.json#', + type: 'object', + properties: { + person: { $ref: '/test/person.json#' }, + role: { type: 'string' } + } + }); + + result = ajv.validate('http://example.com/test/employee.json#', { + person: { + name: 'Alice' + }, + role: 'Programmer' + }); + + result. should.equal(true); + should.equal(ajv.errors, null); + }; + } +}); diff --git a/spec/issues/521_wrong_warning_id_property.spec.js b/spec/issues/521_wrong_warning_id_property.spec.js new file mode 100644 index 0000000..79fbbce --- /dev/null +++ b/spec/issues/521_wrong_warning_id_property.spec.js @@ -0,0 +1,28 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #521, incorrect warning with "id" property', function() { + it('should not log warning', function() { + var ajv = new Ajv({schemaId: '$id'}); + var consoleWarn = console.warn; + console.warn = function() { + throw new Error('should not log warning'); + }; + + try { + ajv.compile({ + "$id": "http://example.com/schema.json", + "type": "object", + "properties": { + "id": {"type": "string"}, + }, + "required": [ "id"] + }); + } finally { + console.warn = consoleWarn; + } + }); +}); diff --git a/spec/issues/533_missing_ref_error_when_ignore.spec.js b/spec/issues/533_missing_ref_error_when_ignore.spec.js new file mode 100644 index 0000000..4e77d5f --- /dev/null +++ b/spec/issues/533_missing_ref_error_when_ignore.spec.js @@ -0,0 +1,29 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('issue #533, throwing missing ref exception with option missingRefs: "ignore"', function() { + var schema = { + "type": "object", + "properties": { + "foo": {"$ref": "#/definitions/missing"}, + "bar": {"$ref": "#/definitions/missing"} + } + }; + + it('should pass validation without throwing exception', function() { + var ajv = new Ajv({missingRefs: 'ignore'}); + var validate = ajv.compile(schema); + validate({foo: 'anything'}) .should.equal(true); + validate({foo: 'anything', bar: 'whatever'}) .should.equal(true); + }); + + it('should throw exception during schema compilation with option missingRefs: true', function() { + var ajv = new Ajv; + should.throw(function() { + ajv.compile(schema); + }); + }); +}); diff --git a/spec/issues/617_full_format_leap_year.spec.js b/spec/issues/617_full_format_leap_year.spec.js new file mode 100644 index 0000000..995c4ba --- /dev/null +++ b/spec/issues/617_full_format_leap_year.spec.js @@ -0,0 +1,47 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('PR #617, full date format validation should understand leap years', function () { + it('should handle non leap year affected dates with date-time', function() { + var ajv = new Ajv({ format: 'full' }); + + var schema = { format: 'date-time' }; + var validDateTime = '2016-01-31T00:00:00Z'; + + ajv.validate(schema, validDateTime).should.equal(true); + }); + + it('should handle non leap year affected dates with date', function () { + var ajv = new Ajv({ format: 'full' }); + + var schema = { format: 'date' }; + var validDate = '2016-11-30'; + + ajv.validate(schema, validDate).should.equal(true); + }); + + it('should handle year leaps as date-time', function() { + var ajv = new Ajv({ format: 'full' }); + + var schema = { format: 'date-time' }; + var validDateTime = '2016-02-29T00:00:00Z'; + var invalidDateTime = '2017-02-29T00:00:00Z'; + + ajv.validate(schema, validDateTime) .should.equal(true); + ajv.validate(schema, invalidDateTime) .should.equal(false); + }); + + it('should handle year leaps as date', function() { + var ajv = new Ajv({ format: 'full' }); + + var schema = { format: 'date' }; + var validDate = '2016-02-29'; + var invalidDate = '2017-02-29'; + + ajv.validate(schema, validDate) .should.equal(true); + ajv.validate(schema, invalidDate) .should.equal(false); + }); +}); diff --git a/spec/issues/743_removeAdditional_to_remove_proto.spec.js b/spec/issues/743_removeAdditional_to_remove_proto.spec.js new file mode 100644 index 0000000..f5a0192 --- /dev/null +++ b/spec/issues/743_removeAdditional_to_remove_proto.spec.js @@ -0,0 +1,41 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #743, property __proto__ should be removed with removeAdditional option', function() { + it('should remove additional properties', function() { + var ajv = new Ajv({removeAdditional: true}); + + var schema = { + properties: { + obj: { + additionalProperties: false, + properties: { + a: { type: 'string' }, + b: { type: 'string' }, + c: { type: 'string' }, + d: { type: 'string' }, + e: { type: 'string' }, + f: { type: 'string' }, + g: { type: 'string' }, + h: { type: 'string' }, + i: { type: 'string' } + } + } + } + }; + + var obj= Object.create(null); + obj.__proto__ = null; // should be removed + obj.additional = 'will be removed'; + obj.a = 'valid'; + obj.b = 'valid'; + + var data = {obj: obj}; + + ajv.validate(schema, data) .should.equal(true); + Object.keys(data.obj) .should.eql(['a', 'b']); + }); +}); diff --git a/spec/issues/768_passContext_recursive_ref.spec.js b/spec/issues/768_passContext_recursive_ref.spec.js new file mode 100644 index 0000000..3410f74 --- /dev/null +++ b/spec/issues/768_passContext_recursive_ref.spec.js @@ -0,0 +1,114 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #768, fix passContext in recursive $ref', function() { + var ajv, contexts; + + beforeEach(function() { + contexts = []; + }); + + describe('passContext = true', function() { + it('should pass this value as context to custom keyword validation function', function() { + var validate = getValidate(true); + var self = {}; + validate.call(self, { bar: 'a', baz: { bar: 'b' } }); + contexts .should.have.length(2); + contexts.forEach(function(ctx) { + ctx .should.equal(self); + }); + }); + }); + + describe('passContext = false', function() { + it('should pass ajv instance as context to custom keyword validation function', function() { + var validate = getValidate(false); + validate({ bar: 'a', baz: { bar: 'b' } }); + contexts .should.have.length(2); + contexts.forEach(function(ctx) { + ctx .should.equal(ajv); + }); + }); + }); + + describe('ref is fragment and passContext = true', function() { + it('should pass this value as context to custom keyword validation function', function() { + var validate = getValidateFragments(true); + var self = {}; + validate.call(self, { baz: { corge: 'a', quux: { baz: { corge: 'b' } } } }); + contexts .should.have.length(2); + contexts.forEach(function(ctx) { + ctx .should.equal(self); + }); + }); + }); + + describe('ref is fragment and passContext = false', function() { + it('should pass ajv instance as context to custom keyword validation function', function() { + var validate = getValidateFragments(false); + validate({ baz: { corge: 'a', quux: { baz: { corge: 'b' } } } }); + contexts .should.have.length(2); + contexts.forEach(function(ctx) { + ctx .should.equal(ajv); + }); + }); + }); + + function getValidate(passContext) { + ajv = new Ajv({ passContext: passContext }); + ajv.addKeyword('testValidate', { validate: storeContext }); + + var schema = { + "$id" : "foo", + "type": "object", + "required": ["bar"], + "properties": { + "bar": { "testValidate": true }, + "baz": { + "$ref": "foo" + } + } + }; + + return ajv.compile(schema); + } + + + function getValidateFragments(passContext) { + ajv = new Ajv({ passContext: passContext }); + ajv.addKeyword('testValidate', { validate: storeContext }); + + ajv.addSchema({ + "$id" : "foo", + "definitions": { + "bar": { + "properties": { + "baz": { + "$ref": "boo" + } + } + } + } + }); + + ajv.addSchema({ + "$id" : "boo", + "type": "object", + "required": ["corge"], + "properties": { + "quux": { "$ref": "foo#/definitions/bar" }, + "corge": { "testValidate": true } + } + }); + + return ajv.compile({ "$ref": "foo#/definitions/bar" }); + } + + function storeContext() { + contexts.push(this); + return true; + } +}); diff --git a/spec/issues/8_shared_refs.spec.js b/spec/issues/8_shared_refs.spec.js new file mode 100644 index 0000000..2208f45 --- /dev/null +++ b/spec/issues/8_shared_refs.spec.js @@ -0,0 +1,40 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('issue #8: schema with shared references', function() { + it('should be supported by addSchema', spec('addSchema')); + + it('should be supported by compile', spec('compile')); + + function spec(method) { + return function() { + var ajv = new Ajv; + + var propertySchema = { + type: 'string', + maxLength: 4 + }; + + var schema = { + $id: 'obj.json#', + type: 'object', + properties: { + foo: propertySchema, + bar: propertySchema + } + }; + + ajv[method](schema); + + var result = ajv.validate('obj.json#', { foo: 'abc', bar: 'def' }); + result .should.equal(true); + + result = ajv.validate('obj.json#', { foo: 'abcde', bar: 'fghg' }); + result .should.equal(false); + ajv.errors .should.have.length(1); + }; + } +});