From 71dc5dc27d954b76f81e4b9c3d7ce827acd6e6e3 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 10 Feb 2019 12:05:05 +0000 Subject: [PATCH] refactor: split options.spec.js file --- package.json | 6 +- spec/options.spec.js | 1579 ---------------------- spec/options/comment.spec.js | 92 ++ spec/options/meta_validateSchema.spec.js | 79 ++ spec/options/nullable.spec.js | 97 ++ spec/options/options_add_schemas.spec.js | 130 ++ spec/options/options_code.spec.js | 115 ++ spec/options/options_refs.spec.js | 167 +++ spec/options/options_reporting.spec.js | 158 +++ spec/options/options_validation.spec.js | 94 ++ spec/options/ownProperties.spec.js | 178 +++ spec/options/removeAdditional.spec.js | 122 ++ spec/options/schemaId.spec.js | 72 + spec/options/unknownFormats.spec.js | 108 ++ spec/options/useDefaults.spec.js | 223 +++ 15 files changed, 1638 insertions(+), 1582 deletions(-) delete mode 100644 spec/options.spec.js create mode 100644 spec/options/comment.spec.js create mode 100644 spec/options/meta_validateSchema.spec.js create mode 100644 spec/options/nullable.spec.js create mode 100644 spec/options/options_add_schemas.spec.js create mode 100644 spec/options/options_code.spec.js create mode 100644 spec/options/options_refs.spec.js create mode 100644 spec/options/options_reporting.spec.js create mode 100644 spec/options/options_validation.spec.js create mode 100644 spec/options/ownProperties.spec.js create mode 100644 spec/options/removeAdditional.spec.js create mode 100644 spec/options/schemaId.spec.js create mode 100644 spec/options/unknownFormats.spec.js create mode 100644 spec/options/useDefaults.spec.js diff --git a/package.json b/package.json index f4d0050..7767cfc 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ ".tonic_example.js" ], "scripts": { - "eslint": "eslint lib/*.js lib/compile/*.js spec/*.js scripts", - "jshint": "jshint lib/*.js lib/**/*.js --exclude lib/dotjs/**/*", - "test-spec": "mocha spec/*.spec.js -R spec", + "eslint": "eslint lib/{compile/,}*.js spec/{**/,}*.js scripts --ignore-pattern spec/JSON-Schema-Test-Suite", + "jshint": "jshint lib/{compile/,}*.js", + "test-spec": "mocha spec/{**/,}*.spec.js -R spec", "test-fast": "AJV_FAST_TEST=true npm run test-spec", "test-debug": "mocha spec/*.spec.js --debug-brk -R spec", "test-cov": "nyc npm run test-spec", diff --git a/spec/options.spec.js b/spec/options.spec.js deleted file mode 100644 index 2ca1b4e..0000000 --- a/spec/options.spec.js +++ /dev/null @@ -1,1579 +0,0 @@ -'use strict'; - -var Ajv = require('./ajv') - , getAjvInstances = require('./ajv_instances') - , should = require('./chai').should(); - - -describe('Ajv Options', function () { - describe('removeAdditional', function() { - it('should remove all additional properties', function() { - var ajv = new Ajv({ removeAdditional: 'all' }); - - ajv.addSchema({ - $id: '//test/fooBar', - properties: { foo: { type: 'string' }, bar: { type: 'string' } } - }); - - var object = { - foo: 'foo', bar: 'bar', baz: 'baz-to-be-removed' - }; - - ajv.validate('//test/fooBar', object).should.equal(true); - object.should.have.property('foo'); - object.should.have.property('bar'); - object.should.not.have.property('baz'); - }); - - - it('should remove properties that would error when `additionalProperties = false`', function() { - var ajv = new Ajv({ removeAdditional: true }); - - ajv.addSchema({ - $id: '//test/fooBar', - properties: { foo: { type: 'string' }, bar: { type: 'string' } }, - additionalProperties: false - }); - - var object = { - foo: 'foo', bar: 'bar', baz: 'baz-to-be-removed' - }; - - ajv.validate('//test/fooBar', object).should.equal(true); - object.should.have.property('foo'); - object.should.have.property('bar'); - object.should.not.have.property('baz'); - }); - - - it('should remove properties that would error when `additionalProperties = false` (many properties, boolean schema)', function() { - var ajv = new Ajv({removeAdditional: true}); - - var schema = { - properties: { - obj: { - additionalProperties: false, - properties: { - a: { type: 'string' }, - b: false, - c: { type: 'string' }, - d: { type: 'string' }, - e: { type: 'string' }, - f: { type: 'string' }, - g: { type: 'string' }, - h: { type: 'string' }, - i: { type: 'string' } - } - } - } - }; - - var data = { - obj: { - a: 'valid', - b: 'should not be removed', - additional: 'will be removed' - } - }; - - ajv.validate(schema, data) .should.equal(false); - data .should.eql({ - obj: { - a: 'valid', - b: 'should not be removed' - } - }); - }); - - - it('should remove properties that would error when `additionalProperties` is a schema', function() { - var ajv = new Ajv({ removeAdditional: 'failing' }); - - ajv.addSchema({ - $id: '//test/fooBar', - properties: { foo: { type: 'string' }, bar: { type: 'string' } }, - additionalProperties: { type: 'string' } - }); - - var object = { - foo: 'foo', bar: 'bar', baz: 'baz-to-be-kept', fizz: 1000 - }; - - ajv.validate('//test/fooBar', object).should.equal(true); - object.should.have.property('foo'); - object.should.have.property('bar'); - object.should.have.property('baz'); - object.should.not.have.property('fizz'); - - ajv.addSchema({ - $id: '//test/fooBar2', - properties: { foo: { type: 'string' }, bar: { type: 'string' } }, - additionalProperties: { type: 'string', pattern: '^to-be-', maxLength: 10 } - }); - - object = { - foo: 'foo', bar: 'bar', baz: 'to-be-kept', quux: 'to-be-removed', fizz: 1000 - }; - - ajv.validate('//test/fooBar2', object).should.equal(true); - object.should.have.property('foo'); - object.should.have.property('bar'); - object.should.have.property('baz'); - object.should.not.have.property('fizz'); - }); - }); - - - describe('ownProperties', function() { - var ajv, ajvOP, ajvOP1; - - beforeEach(function() { - ajv = new Ajv({ allErrors: true }); - ajvOP = new Ajv({ ownProperties: true, allErrors: true }); - ajvOP1 = new Ajv({ ownProperties: true }); - }); - - it('should only validate own properties with additionalProperties', function() { - var schema = { - properties: { a: { type: 'number' } }, - additionalProperties: false - }; - - var obj = { a: 1 }; - var proto = { b: 2 }; - test(schema, obj, proto); - }); - - it('should only validate own properties with properties keyword', function() { - var schema = { - properties: { - a: { type: 'number' }, - b: { type: 'number' } - } - }; - - var obj = { a: 1 }; - var proto = { b: 'not a number' }; - test(schema, obj, proto); - }); - - it('should only validate own properties with required keyword', function() { - var schema = { - required: ['a', 'b'] - }; - - var obj = { a: 1 }; - var proto = { b: 2 }; - test(schema, obj, proto, 1, true); - }); - - it('should only validate own properties with required keyword - many properties', function() { - ajv = new Ajv({ allErrors: true, loopRequired: 1 }); - ajvOP = new Ajv({ ownProperties: true, allErrors: true, loopRequired: 1 }); - ajvOP1 = new Ajv({ ownProperties: true, loopRequired: 1 }); - - var schema = { - required: ['a', 'b', 'c', 'd'] - }; - - var obj = { a: 1, b: 2 }; - var proto = { c: 3, d: 4 }; - test(schema, obj, proto, 2, true); - }); - - it('should only validate own properties with required keyword as $data', function() { - ajv = new Ajv({ allErrors: true, $data: true }); - ajvOP = new Ajv({ ownProperties: true, allErrors: true, $data: true }); - ajvOP1 = new Ajv({ ownProperties: true, $data: true }); - - var schema = { - required: { $data: '0/req' }, - properties: { - req: { - type: 'array', - items: { type: 'string' } - } - } - }; - - var obj = { - req: ['a', 'b'], - a: 1 - }; - var proto = { b: 2 }; - test(schema, obj, proto, 1, true); - }); - - it('should only validate own properties with properties and required keyword', function() { - var schema = { - properties: { - a: { type: 'number' }, - b: { type: 'number' } - }, - required: ['a', 'b'] - }; - - var obj = { a: 1 }; - var proto = { b: 2 }; - test(schema, obj, proto, 1, true); - }); - - it('should only validate own properties with dependencies keyword', function() { - var schema = { - dependencies: { - a: ['c'], - b: ['d'] - } - }; - - var obj = { a: 1, c: 3 }; - var proto = { b: 2 }; - test(schema, obj, proto); - - obj = { a: 1, b: 2, c: 3 }; - proto = { d: 4 }; - test(schema, obj, proto, 1, true); - }); - - it('should only validate own properties with schema dependencies', function() { - var schema = { - dependencies: { - a: { not: { required: ['c'] } }, - b: { not: { required: ['d'] } } - } - }; - - var obj = { a: 1, d: 3 }; - var proto = { b: 2 }; - test(schema, obj, proto); - - obj = { a: 1, b: 2 }; - proto = { d: 4 }; - test(schema, obj, proto); - }); - - it('should only validate own properties with patternProperties', function() { - var schema = { - patternProperties: { 'f.*o': { type: 'integer' } }, - }; - - var obj = { fooo: 1 }; - var proto = { foo: 'not a number' }; - test(schema, obj, proto); - }); - - it('should only validate own properties with propertyNames', function() { - var schema = { - propertyNames: { - format: 'email' - } - }; - - var obj = { 'e@example.com': 2 }; - var proto = { 'not email': 1 }; - test(schema, obj, proto, 2); - }); - - function test(schema, obj, proto, errors, reverse) { - errors = errors || 1; - var validate = ajv.compile(schema); - var validateOP = ajvOP.compile(schema); - var validateOP1 = ajvOP1.compile(schema); - var data = Object.create(proto); - for (var key in obj) data[key] = obj[key]; - - if (reverse) { - validate(data) .should.equal(true); - validateOP(data) .should.equal(false); - validateOP.errors .should.have.length(errors); - validateOP1(data) .should.equal(false); - validateOP1.errors .should.have.length(1); - } else { - validate(data) .should.equal(false); - validate.errors .should.have.length(errors); - validateOP(data) .should.equal(true); - validateOP1(data) .should.equal(true); - } - } - }); - - describe('meta and validateSchema', function() { - it('should add draft-7 meta schema by default', function() { - testOptionMeta(new Ajv); - testOptionMeta(new Ajv({ meta: true })); - - function testOptionMeta(ajv) { - ajv.getSchema('http://json-schema.org/draft-07/schema') .should.be.a('function'); - ajv.validateSchema({ type: 'integer' }) .should.equal(true); - ajv.validateSchema({ type: 123 }) .should.equal(false); - should.not.throw(function() { ajv.addSchema({ type: 'integer' }); }); - should.throw(function() { ajv.addSchema({ type: 123 }); }); - } - }); - - it('should throw if meta: false and validateSchema: true', function() { - var ajv = new Ajv({ meta: false }); - should.not.exist(ajv.getSchema('http://json-schema.org/draft-07/schema')); - should.not.throw(function() { ajv.addSchema({ type: 'wrong_type' }, 'integer'); }); - }); - - it('should skip schema validation with validateSchema: false', function() { - var ajv = new Ajv; - should.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); - - ajv = new Ajv({ validateSchema: false }); - should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); - - ajv = new Ajv({ validateSchema: false, meta: false }); - should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); - }); - - it('should not throw on invalid schema with validateSchema: "log"', function() { - var logError = console.error; - var loggedError = false; - console.error = function() { loggedError = true; logError.apply(console, arguments); }; - - var ajv = new Ajv({ validateSchema: 'log' }); - should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); - loggedError .should.equal(true); - - loggedError = false; - ajv = new Ajv({ validateSchema: 'log', meta: false }); - should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); - loggedError .should.equal(false); - console.error = logError; - }); - - it('should validate v6 schema', function() { - var ajv = new Ajv; - ajv.validateSchema({ contains: { minimum: 2 } }) .should.equal(true); - ajv.validateSchema({ contains: 2 }). should.equal(false); - }); - - it('should use option meta as default meta schema', function() { - var meta = { - $schema: 'http://json-schema.org/draft-07/schema', - properties: { - myKeyword: { type: 'boolean' } - } - }; - var ajv = new Ajv({ meta: meta }); - ajv.validateSchema({ myKeyword: true }) .should.equal(true); - ajv.validateSchema({ myKeyword: 2 }) .should.equal(false); - ajv.validateSchema({ - $schema: 'http://json-schema.org/draft-07/schema', - myKeyword: 2 - }) .should.equal(true); - - ajv = new Ajv; - ajv.validateSchema({ myKeyword: true }) .should.equal(true); - ajv.validateSchema({ myKeyword: 2 }) .should.equal(true); - }); - }); - - - describe('schemas', function() { - it('should add schemas from object', function() { - var ajv = new Ajv({ schemas: { - int: { type: 'integer' }, - str: { type: 'string' } - }}); - - ajv.validate('int', 123) .should.equal(true); - ajv.validate('int', 'foo') .should.equal(false); - ajv.validate('str', 'foo') .should.equal(true); - ajv.validate('str', 123) .should.equal(false); - }); - - it('should add schemas from array', function() { - var ajv = new Ajv({ schemas: [ - { $id: 'int', type: 'integer' }, - { $id: 'str', type: 'string' }, - { $id: 'obj', properties: { int: { $ref: 'int' }, str: { $ref: 'str' } } } - ]}); - - ajv.validate('obj', { int: 123, str: 'foo' }) .should.equal(true); - ajv.validate('obj', { int: 'foo', str: 'bar' }) .should.equal(false); - ajv.validate('obj', { int: 123, str: 456 }) .should.equal(false); - }); - }); - - - describe('format', function() { - it('should not validate formats if option format == false', function() { - var ajv = new Ajv - , ajvFF = new Ajv({ format: false }); - - var schema = { format: 'date-time' }; - var invalideDateTime = '06/19/1963 08:30:06 PST'; - - ajv.validate(schema, invalideDateTime) .should.equal(false); - ajvFF.validate(schema, invalideDateTime) .should.equal(true); - }); - }); - - - describe('formats', function() { - it('should add formats from options', function() { - var ajv = new Ajv({ formats: { - identifier: /^[a-z_$][a-z0-9_$]*$/i - }}); - - var validate = ajv.compile({ format: 'identifier' }); - validate('Abc1') .should.equal(true); - validate('123') .should.equal(false); - validate(123) .should.equal(true); - }); - }); - - - describe('missingRefs', function() { - it('should throw if ref is missing without this option', function() { - var ajv = new Ajv; - should.throw(function() { - ajv.compile({ $ref: 'missing_reference' }); - }); - }); - - it('should not throw and pass validation with missingRef == "ignore"', function() { - testMissingRefsIgnore(new Ajv({ missingRefs: 'ignore' })); - testMissingRefsIgnore(new Ajv({ missingRefs: 'ignore', allErrors: true })); - - function testMissingRefsIgnore(ajv) { - var validate = ajv.compile({ $ref: 'missing_reference' }); - validate({}) .should.equal(true); - } - }); - - it('should not throw and fail validation with missingRef == "fail" if the ref is used', function() { - testMissingRefsFail(new Ajv({ missingRefs: 'fail' })); - testMissingRefsFail(new Ajv({ missingRefs: 'fail', verbose: true })); - testMissingRefsFail(new Ajv({ missingRefs: 'fail', allErrors: true })); - testMissingRefsFail(new Ajv({ missingRefs: 'fail', allErrors: true, verbose: true })); - - function testMissingRefsFail(ajv) { - var validate = ajv.compile({ - anyOf: [ - { type: 'number' }, - { $ref: 'missing_reference' } - ] - }); - validate(123) .should.equal(true); - validate('foo') .should.equal(false); - - validate = ajv.compile({ $ref: 'missing_reference' }); - validate({}) .should.equal(false); - } - }); - }); - - - describe('uniqueItems', function() { - it('should not validate uniqueItems with uniqueItems option == false', function() { - testUniqueItems(new Ajv({ uniqueItems: false })); - testUniqueItems(new Ajv({ uniqueItems: false, allErrors: true })); - - function testUniqueItems(ajv) { - var validate = ajv.compile({ uniqueItems: true }); - validate([1,2,3]) .should.equal(true); - validate([1,1,1]) .should.equal(true); - } - }); - }); - - - describe('unicode', function() { - it('should use String.prototype.length with unicode option == false', function() { - var ajvUnicode = new Ajv; - testUnicode(new Ajv({ unicode: false })); - testUnicode(new Ajv({ unicode: false, allErrors: true })); - - function testUnicode(ajv) { - var validateWithUnicode = ajvUnicode.compile({ minLength: 2 }); - var validate = ajv.compile({ minLength: 2 }); - - validateWithUnicode('😀') .should.equal(false); - validate('😀') .should.equal(true); - - validateWithUnicode = ajvUnicode.compile({ maxLength: 1 }); - validate = ajv.compile({ maxLength: 1 }); - - validateWithUnicode('😀') .should.equal(true); - validate('😀') .should.equal(false); - } - }); - }); - - - describe('verbose', function() { - it('should add schema, parentSchema and data to errors with verbose option == true', function() { - testVerbose(new Ajv({ verbose: true })); - testVerbose(new Ajv({ verbose: true, allErrors: true })); - - function testVerbose(ajv) { - var schema = { properties: { foo: { minimum: 5 } } }; - var validate = ajv.compile(schema); - - var data = { foo: 3 }; - validate(data) .should.equal(false); - validate.errors .should.have.length(1); - var err = validate.errors[0]; - - should.equal(err.schema, 5); - err.parentSchema .should.eql({ minimum: 5 }); - err.parentSchema .should.equal(schema.properties.foo); // by reference - should.equal(err.data, 3); - } - }); - }); - - - describe('multipleOfPrecision', function() { - it('should allow for some deviation from 0 when validating multipleOf with value < 1', function() { - test(new Ajv({ multipleOfPrecision: 7 })); - test(new Ajv({ multipleOfPrecision: 7, allErrors: true })); - - function test(ajv) { - var schema = { multipleOf: 0.01 }; - var validate = ajv.compile(schema); - - validate(4.18) .should.equal(true); - validate(4.181) .should.equal(false); - - schema = { multipleOf: 0.0000001 }; - validate = ajv.compile(schema); - - validate(53.198098) .should.equal(true); - validate(53.1980981) .should.equal(true); - validate(53.19809811) .should.equal(false); - } - }); - }); - - - describe('useDefaults', function() { - it('should replace undefined property with default value', function() { - var instances = getAjvInstances({ - allErrors: true, - loopRequired: 3 - }, { useDefaults: true }); - - instances.forEach(test); - - - function test(ajv) { - var schema = { - properties: { - foo: { type: 'string', default: 'abc' }, - bar: { type: 'number', default: 1 }, - baz: { type: 'boolean', default: false }, - nil: { type: 'null', default: null }, - obj: { type: 'object', default: {} }, - arr: { type: 'array', default: [] } - }, - required: ['foo', 'bar', 'baz', 'nil', 'obj', 'arr'], - minProperties: 6 - }; - - var validate = ajv.compile(schema); - - var data = {}; - validate(data) .should.equal(true); - data .should.eql({ foo: 'abc', bar: 1, baz: false, nil: null, obj: {}, arr:[] }); - - data = { foo: 'foo', bar: 2, obj: { test: true } }; - validate(data) .should.equal(true); - data .should.eql({ foo: 'foo', bar: 2, baz: false, nil: null, obj: { test: true }, arr:[] }); - } - }); - - it('should replace undefined item with default value', function() { - test(new Ajv({ useDefaults: true })); - test(new Ajv({ useDefaults: true, allErrors: true })); - - function test(ajv) { - var schema = { - items: [ - { type: 'string', default: 'abc' }, - { type: 'number', default: 1 }, - { type: 'boolean', default: false } - ], - minItems: 3 - }; - - var validate = ajv.compile(schema); - - var data = []; - validate(data) .should.equal(true); - data .should.eql([ 'abc', 1, false ]); - - data = [ 'foo' ]; - validate(data) .should.equal(true); - data .should.eql([ 'foo', 1, false ]); - - data = ['foo', 2,'false']; - validate(data) .should.equal(false); - validate.errors .should.have.length(1); - data .should.eql([ 'foo', 2, 'false' ]); - } - }); - - it('should apply default in "then" subschema (issue #635)', function() { - test(new Ajv({ useDefaults: true })); - test(new Ajv({ useDefaults: true, allErrors: true })); - - function test(ajv) { - var schema = { - if: { required: ['foo'] }, - then: { - properties: { - bar: { default: 2 } - } - }, - else: { - properties: { - foo: { default: 1 } - } - } - }; - - var validate = ajv.compile(schema); - - var data = {}; - validate(data) .should.equal(true); - data .should.eql({foo: 1}); - - data = {foo: 1}; - validate(data) .should.equal(true); - data .should.eql({foo: 1, bar: 2}); - } - }); - - - describe('useDefaults: by value / by reference', function() { - describe('using by value', function() { - it('should NOT modify underlying defaults when modifying validated data', function() { - test('value', new Ajv({ useDefaults: true })); - test('value', new Ajv({ useDefaults: true, allErrors: true })); - }); - }); - - describe('using by reference', function() { - it('should modify underlying defaults when modifying validated data', function() { - test('reference', new Ajv({ useDefaults: 'shared' })); - test('reference', new Ajv({ useDefaults: 'shared', allErrors: true })); - }); - }); - - function test(useDefaultsMode, ajv) { - var schema = { - properties: { - items: { - type: 'array', - default: ['a-default'] - } - } - }; - - var validate = ajv.compile(schema); - - var data = {}; - validate(data) .should.equal(true); - data.items .should.eql([ 'a-default' ]); - - data.items.push('another-value'); - data.items .should.eql([ 'a-default', 'another-value' ]); - - var data2 = {}; - validate(data2) .should.equal(true); - - if (useDefaultsMode == 'reference') - data2.items .should.eql([ 'a-default', 'another-value' ]); - else if (useDefaultsMode == 'value') - data2.items .should.eql([ 'a-default' ]); - else - throw new Error('unknown useDefaults mode'); - } - }); - - - describe('defaults with "empty" values', function() { - var schema, data; - - beforeEach(function() { - schema = { - properties: { - obj: { - properties: { - str: {default: 'foo'}, - n1: {default: 1}, - n2: {default: 2}, - n3: {default: 3} - } - }, - arr: { - items: [ - {default: 'foo'}, - {default: 1}, - {default: 2}, - {default: 3} - ] - } - } - }; - - data = { - obj: { - str: '', - n1: null, - n2: undefined - }, - arr: ['', null, undefined] - }; - }); - - it('should NOT assign defaults when useDefaults is true/"shared"', function() { - test(new Ajv({useDefaults: true})); - test(new Ajv({useDefaults: 'shared'})); - - function test(ajv) { - var validate = ajv.compile(schema); - validate(data) .should.equal(true); - data .should.eql({ - obj: { - str: '', - n1: null, - n2: 2, - n3: 3 - }, - arr: ['', null, 2, 3] - }); - } - }); - - it('should assign defaults when useDefaults = "empty"', function() { - var ajv = new Ajv({useDefaults: 'empty'}); - var validate = ajv.compile(schema); - validate(data) .should.equal(true); - data .should.eql({ - obj: { - str: 'foo', - n1: 1, - n2: 2, - n3: 3 - }, - arr: ['foo', 1, 2, 3] - }); - }); - }); - }); - - - describe('addUsedSchema', function() { - [true, undefined].forEach(function (optionValue) { - describe('= ' + optionValue, function() { - var ajv; - - beforeEach(function() { - ajv = new Ajv({ addUsedSchema: optionValue }); - }); - - describe('compile and validate', function() { - it('should add schema', function() { - var schema = { $id: 'str', type: 'string' }; - var validate = ajv.compile(schema); - validate('abc') .should.equal(true); - validate(1) .should.equal(false); - ajv.getSchema('str') .should.equal(validate); - - schema = { $id: 'int', type: 'integer' }; - ajv.validate(schema, 1) .should.equal(true); - ajv.validate(schema, 'abc') .should.equal(false); - ajv.getSchema('int') .should.be.a('function'); - }); - - it('should throw with duplicate ID', function() { - ajv.compile({ $id: 'str', type: 'string' }); - should.throw(function() { - ajv.compile({ $id: 'str', minLength: 2 }); - }); - - var schema = { $id: 'int', type: 'integer' }; - var schema2 = { $id: 'int', minimum: 0 }; - ajv.validate(schema, 1) .should.equal(true); - should.throw(function() { - ajv.validate(schema2, 1); - }); - }); - }); - }); - }); - - describe('= false', function() { - var ajv; - - beforeEach(function() { - ajv = new Ajv({ addUsedSchema: false }); - }); - - - describe('compile and validate', function() { - it('should NOT add schema', function() { - var schema = { $id: 'str', type: 'string' }; - var validate = ajv.compile(schema); - validate('abc') .should.equal(true); - validate(1) .should.equal(false); - should.equal(ajv.getSchema('str'), undefined); - - schema = { $id: 'int', type: 'integer' }; - ajv.validate(schema, 1) .should.equal(true); - ajv.validate(schema, 'abc') .should.equal(false); - should.equal(ajv.getSchema('int'), undefined); - }); - - it('should NOT throw with duplicate ID', function() { - ajv.compile({ $id: 'str', type: 'string' }); - should.not.throw(function() { - ajv.compile({ $id: 'str', minLength: 2 }); - }); - - var schema = { $id: 'int', type: 'integer' }; - var schema2 = { $id: 'int', minimum: 0 }; - ajv.validate(schema, 1) .should.equal(true); - should.not.throw(function() { - ajv.validate(schema2, 1) .should.equal(true); - }); - }); - }); - }); - }); - - - describe('passContext', function() { - var ajv, contexts; - - beforeEach(function() { - contexts = []; - }); - - describe('= true', function() { - it('should pass this value as context to custom keyword validation function', function() { - var validate = getValidate(true); - var self = {}; - validate.call(self, {}); - contexts .should.have.length(4); - contexts.forEach(function(ctx) { - ctx .should.equal(self); - }); - }); - }); - - describe('= false', function() { - it('should pass ajv instance as context to custom keyword validation function', function() { - var validate = getValidate(false); - var self = {}; - validate.call(self, {}); - contexts .should.have.length(4); - contexts.forEach(function(ctx) { - ctx .should.equal(ajv); - }); - }); - }); - - function getValidate(passContext) { - ajv = new Ajv({ passContext: passContext, inlineRefs: false }); - ajv.addKeyword('testValidate', { validate: storeContext }); - ajv.addKeyword('testCompile', { compile: compileTestValidate }); - - var schema = { - definitions: { - test1: { - testValidate: true, - testCompile: true, - }, - test2: { - allOf: [ { $ref: '#/definitions/test1' } ] - } - }, - allOf: [ - { $ref: '#/definitions/test1' }, - { $ref: '#/definitions/test2' } - ] - }; - - return ajv.compile(schema); - } - - function storeContext() { - contexts.push(this); - return true; - } - - function compileTestValidate() { - return storeContext; - } - }); - - - describe('allErrors', function() { - it('should be disabled inside "not" keyword', function() { - test(new Ajv, false); - test(new Ajv({ allErrors: true }), true); - - function test(ajv, allErrors) { - var format1called = false - , format2called = false; - - ajv.addFormat('format1', function() { - format1called = true; - return false; - }); - - ajv.addFormat('format2', function() { - format2called = true; - return false; - }); - - var schema1 = { - allOf: [ - { format: 'format1' }, - { format: 'format2' } - ] - }; - - ajv.validate(schema1, 'abc') .should.equal(false); - ajv.errors .should.have.length(allErrors ? 2 : 1); - format1called .should.equal(true); - format2called .should.equal(allErrors); - - var schema2 = { - not: schema1 - }; - - format1called = format2called = false; - ajv.validate(schema2, 'abc') .should.equal(true); - should.equal(ajv.errors, null); - format1called .should.equal(true); - format2called .should.equal(false); - } - }); - }); - - - describe('extendRefs', function() { - describe('= true', function() { - it('should allow extending $ref with other keywords', function() { - test(new Ajv({ extendRefs: true }), true); - }); - - it('should NOT log warning if extendRefs is true', function() { - testWarning(new Ajv({ extendRefs: true })); - }); - }); - - describe('= "ignore" and default', function() { - it('should ignore other keywords when $ref is used', function() { - test(new Ajv); - test(new Ajv({ extendRefs: 'ignore' }), false); - }); - - it('should log warning when other keywords are used with $ref', function() { - testWarning(new Ajv, /keywords\signored/); - testWarning(new Ajv({ extendRefs: 'ignore' }), /keywords\signored/); - }); - }); - - describe('= "fail"', function() { - it('should fail schema compilation if other keywords are used with $ref', function() { - testFail(new Ajv({ extendRefs: 'fail' })); - - function testFail(ajv) { - should.throw(function() { - var schema = { - "definitions": { - "int": { "type": "integer" } - }, - "$ref": "#/definitions/int", - "minimum": 10 - }; - ajv.compile(schema); - }); - - should.not.throw(function() { - var schema = { - "definitions": { - "int": { "type": "integer" } - }, - "allOf": [ - { "$ref": "#/definitions/int" }, - { "minimum": 10 } - ] - }; - ajv.compile(schema); - }); - } - }); - }); - - function test(ajv, shouldExtendRef) { - var schema = { - "definitions": { - "int": { "type": "integer" } - }, - "$ref": "#/definitions/int", - "minimum": 10 - }; - - var validate = ajv.compile(schema); - validate(10) .should.equal(true); - validate(1) .should.equal(!shouldExtendRef); - - schema = { - "definitions": { - "int": { "type": "integer" } - }, - "type": "object", - "properties": { - "foo": { - "$ref": "#/definitions/int", - "minimum": 10 - }, - "bar": { - "allOf": [ - { "$ref": "#/definitions/int" }, - { "minimum": 10 } - ] - } - } - }; - - validate = ajv.compile(schema); - validate({ foo: 10, bar: 10 }) .should.equal(true); - validate({ foo: 1, bar: 10 }) .should.equal(!shouldExtendRef); - validate({ foo: 10, bar: 1 }) .should.equal(false); - } - - function testWarning(ajv, msgPattern) { - var oldConsole; - try { - oldConsole = console.warn; - var consoleMsg; - console.warn = function() { - consoleMsg = Array.prototype.join.call(arguments, ' '); - }; - - var schema = { - "definitions": { - "int": { "type": "integer" } - }, - "$ref": "#/definitions/int", - "minimum": 10 - }; - - ajv.compile(schema); - if (msgPattern) consoleMsg .should.match(msgPattern); - else should.not.exist(consoleMsg); - } finally { - console.warn = oldConsole; - } - } - }); - - - describe('sourceCode', function() { - describe('= true', function() { - it('should add source.code property', function() { - test(new Ajv({sourceCode: true})); - - function test(ajv) { - var validate = ajv.compile({ "type": "number" }); - validate.source.code .should.be.a('string'); - } - }); - }); - - describe('= false and default', function() { - it('should not add source and sourceCode properties', function() { - test(new Ajv); - test(new Ajv({sourceCode: false})); - - function test(ajv) { - var validate = ajv.compile({ "type": "number" }); - should.not.exist(validate.source); - should.not.exist(validate.sourceCode); - } - }); - }); - }); - - - describe('unknownFormats', function() { - describe('= true (default)', function() { - it('should fail schema compilation if unknown format is used', function() { - test(new Ajv); - test(new Ajv({unknownFormats: true})); - - function test(ajv) { - should.throw(function() { - ajv.compile({ format: 'unknown' }); - }); - } - }); - - it('should fail validation if unknown format is used via $data', function() { - test(new Ajv({$data: true})); - test(new Ajv({$data: true, unknownFormats: true})); - - function test(ajv) { - var validate = ajv.compile({ - properties: { - foo: { format: { $data: '1/bar' } }, - bar: { type: 'string' } - } - }); - - validate({foo: 1, bar: 'unknown'}) .should.equal(false); - validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true); - validate({foo: '20161016', bar: 'date'}) .should.equal(false); - validate({foo: '20161016'}) .should.equal(true); - - validate({foo: '2016-10-16', bar: 'unknown'}) .should.equal(false); - } - }); - }); - - describe('= "ignore (default before 5.0.0)"', function() { - it('should pass schema compilation and be valid if unknown format is used', function() { - test(new Ajv({unknownFormats: 'ignore'})); - - function test(ajv) { - var validate = ajv.compile({ format: 'unknown' }); - validate('anything') .should.equal(true); - } - }); - - it('should be valid if unknown format is used via $data', function() { - test(new Ajv({$data: true, unknownFormats: 'ignore'})); - - function test(ajv) { - var validate = ajv.compile({ - properties: { - foo: { format: { $data: '1/bar' } }, - bar: { type: 'string' } - } - }); - - validate({foo: 1, bar: 'unknown'}) .should.equal(true); - validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true); - validate({foo: '20161016', bar: 'date'}) .should.equal(false); - validate({foo: '20161016'}) .should.equal(true); - validate({foo: '2016-10-16', bar: 'unknown'}) .should.equal(true); - } - }); - }); - - describe('= [String]', function() { - it('should pass schema compilation and be valid if whitelisted unknown format is used', function() { - test(new Ajv({unknownFormats: ['allowed']})); - - function test(ajv) { - var validate = ajv.compile({ format: 'allowed' }); - validate('anything') .should.equal(true); - - should.throw(function() { - ajv.compile({ format: 'unknown' }); - }); - } - }); - - it('should be valid if whitelisted unknown format is used via $data', function() { - test(new Ajv({$data: true, unknownFormats: ['allowed']})); - - function test(ajv) { - var validate = ajv.compile({ - properties: { - foo: { format: { $data: '1/bar' } }, - bar: { type: 'string' } - } - }); - - validate({foo: 1, bar: 'allowed'}) .should.equal(true); - validate({foo: 1, bar: 'unknown'}) .should.equal(false); - validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true); - validate({foo: '20161016', bar: 'date'}) .should.equal(false); - validate({foo: '20161016'}) .should.equal(true); - - validate({foo: '2016-10-16', bar: 'allowed'}) .should.equal(true); - validate({foo: '2016-10-16', bar: 'unknown'}) .should.equal(false); - } - }); - }); - }); - - - describe('processCode', function() { - it('should process generated code', function() { - var ajv = new Ajv; - var validate = ajv.compile({type: 'string'}); - validate.toString().split('\n').length .should.equal(1); - - var beautify = require('js-beautify').js_beautify; - var ajvPC = new Ajv({processCode: beautify}); - validate = ajvPC.compile({type: 'string'}); - validate.toString().split('\n').length .should.be.above(1); - validate('foo') .should.equal(true); - validate(1) .should.equal(false); - }); - }); - - - describe('serialize', function() { - var serializeCalled; - - it('should use custom function to serialize schema to string', function() { - serializeCalled = undefined; - var ajv = new Ajv({ serialize: serialize }); - ajv.addSchema({ type: 'string' }); - should.equal(serializeCalled, true); - }); - - function serialize(schema) { - serializeCalled = true; - return JSON.stringify(schema); - } - }); - - - describe('schemaId', function() { - describe('= "$id" (default)', function() { - it('should use $id and ignore id', function() { - test(new Ajv); - test(new Ajv({schemaId: '$id'})); - - function test(ajv) { - ajv.addSchema({ $id: 'mySchema1', type: 'string' }); - var validate = ajv.getSchema('mySchema1'); - validate('foo') .should.equal(true); - validate(1) .should.equal(false); - - validate = ajv.compile({ id: 'mySchema2', type: 'string' }); - should.not.exist(ajv.getSchema('mySchema2')); - } - }); - }); - - describe('= "id"', function() { - it('should use id and ignore $id', function() { - var ajv = new Ajv({schemaId: 'id', meta: false}); - ajv.addMetaSchema(require('../lib/refs/json-schema-draft-04.json')); - ajv._opts.defaultMeta = 'http://json-schema.org/draft-04/schema#'; - - ajv.addSchema({ id: 'mySchema1', type: 'string' }); - var validate = ajv.getSchema('mySchema1'); - validate('foo') .should.equal(true); - validate(1) .should.equal(false); - - validate = ajv.compile({ $id: 'mySchema2', type: 'string' }); - should.not.exist(ajv.getSchema('mySchema2')); - }); - }); - - describe('= "auto"', function() { - it('should use both id and $id', function() { - var ajv = new Ajv({schemaId: 'auto'}); - - ajv.addSchema({ $id: 'mySchema1', type: 'string' }); - var validate = ajv.getSchema('mySchema1'); - validate('foo') .should.equal(true); - validate(1) .should.equal(false); - - ajv.addSchema({ id: 'mySchema2', type: 'string' }); - validate = ajv.getSchema('mySchema2'); - validate('foo') .should.equal(true); - validate(1) .should.equal(false); - }); - - it('should throw if both id and $id are available and different', function() { - var ajv = new Ajv({schemaId: 'auto'}); - - ajv.compile({ - id: 'mySchema', - $id: 'mySchema' - }); - - should.throw(function() { - ajv.compile({ - id: 'mySchema1', - $id: 'mySchema2' - }); - }); - }); - }); - }); - - - describe('$comment', function() { - describe('= true', function() { - var logCalls, consoleLog; - - beforeEach(function () { - consoleLog = console.log; - console.log = log; - }); - - afterEach(function () { - console.log = consoleLog; - }); - - function log() { - logCalls.push(Array.prototype.slice.call(arguments)); - } - - it('should log the text from $comment keyword', function() { - var schema = { - properties: { - foo: {$comment: 'property foo'}, - bar: {$comment: 'property bar', type: 'integer'} - } - }; - - var ajv = new Ajv({$comment: true}); - var fullAjv = new Ajv({allErrors: true, $comment: true}); - - [ajv, fullAjv].forEach(function (_ajv) { - var validate = _ajv.compile(schema); - - test({}, true, []); - test({foo: 1}, true, [['property foo']]); - test({foo: 1, bar: 2}, true, [['property foo'], ['property bar']]); - test({foo: 1, bar: 'baz'}, false, [['property foo'], ['property bar']]); - - function test(data, valid, expectedLogCalls) { - logCalls = []; - validate(data) .should.equal(valid); - logCalls .should.eql(expectedLogCalls); - } - }); - - console.log = consoleLog; - }); - }); - - describe('function hook', function() { - var hookCalls; - - function hook() { - hookCalls.push(Array.prototype.slice.call(arguments)); - } - - it('should pass the text from $comment keyword to the hook', function() { - var schema = { - properties: { - foo: {$comment: 'property foo'}, - bar: {$comment: 'property bar', type: 'integer'} - } - }; - - var ajv = new Ajv({$comment: hook}); - var fullAjv = new Ajv({allErrors: true, $comment: hook}); - - [ajv, fullAjv].forEach(function (_ajv) { - var validate = _ajv.compile(schema); - - test({}, true, []); - test({foo: 1}, true, [['property foo', '#/properties/foo/$comment', schema]]); - test({foo: 1, bar: 2}, true, - [['property foo', '#/properties/foo/$comment', schema], - ['property bar', '#/properties/bar/$comment', schema]]); - test({foo: 1, bar: 'baz'}, false, - [['property foo', '#/properties/foo/$comment', schema], - ['property bar', '#/properties/bar/$comment', schema]]); - - function test(data, valid, expectedHookCalls) { - hookCalls = []; - validate(data) .should.equal(valid); - hookCalls .should.eql(expectedHookCalls); - } - }); - }); - }); - }); - - - describe('logger', function() { - - /** - * The logger option tests are based on the meta scenario which writes into the logger.warn - */ - - var origConsoleWarn = console.warn; - var consoleCalled; - - beforeEach(function() { - consoleCalled = false; - console.warn = function() { - consoleCalled = true; - }; - }); - - afterEach(function() { - console.warn = origConsoleWarn; - }); - - it('no custom logger is given - global console should be used', function() { - var ajv = new Ajv({ - meta: false - }); - - ajv.compile({ - type: 'number', - minimum: 1 - }); - - should.equal(consoleCalled, true); - }); - - it('custom logger is an object - logs should only report to it', function() { - var loggerCalled = false; - - var logger = { - warn: log, - log: log, - error: log - }; - - var ajv = new Ajv({ - meta: false, - logger: logger - }); - - ajv.compile({ - type: 'number', - minimum: 1 - }); - - should.equal(loggerCalled, true); - should.equal(consoleCalled, false); - - function log() { - loggerCalled = true; - } - }); - - it('logger option is false - no logs should be reported', function() { - var ajv = new Ajv({ - meta: false, - logger: false - }); - - ajv.compile({ - type: 'number', - minimum: 1 - }); - - should.equal(consoleCalled, false); - }); - - it('logger option is an object without required methods - an error should be thrown', function() { - (function(){ - new Ajv({ - meta: false, - logger: {} - }); - }).should.throw(Error, /logger must implement log, warn and error methods/); - }); - }); - - - describe('nullable', function() { - var ajv; - - describe('= true', function() { - beforeEach(function () { - ajv = new Ajv({ - nullable: true - }); - }); - - it('should add keyword "nullable"', function() { - testNullable({ - type: 'number', - nullable: true - }); - - testNullable({ - type: ['number'], - nullable: true - }); - - testNullable({ - type: ['number', 'null'] - }); - - testNullable({ - type: ['number', 'null'], - nullable: true - }); - - testNotNullable({type: 'number'}); - - testNotNullable({type: ['number']}); - }); - - it('should respect "nullable" == false with opts.nullable == true', function() { - testNotNullable({ - type: 'number', - nullable: false - }); - - testNotNullable({ - type: ['number'], - nullable: false - }); - }); - }); - - describe('without option "nullable"', function() { - it('should ignore keyword nullable', function() { - ajv = new Ajv; - - testNotNullable({ - type: 'number', - nullable: true - }); - - testNotNullable({ - type: ['number'], - nullable: true - }); - - testNullable({ - type: ['number', 'null'], - }); - - testNullable({ - type: ['number', 'null'], - nullable: true - }); - - should.not.throw(function () { - ajv.compile({nullable: false}); - }); - }); - }); - - function testNullable(schema) { - var validate = ajv.compile(schema); - validate(1) .should.equal(true); - validate(null) .should.equal(true); - validate('1') .should.equal(false); - } - - function testNotNullable(schema) { - var validate = ajv.compile(schema); - validate(1) .should.equal(true); - validate(null) .should.equal(false); - validate('1') .should.equal(false); - } - }); -}); diff --git a/spec/options/comment.spec.js b/spec/options/comment.spec.js new file mode 100644 index 0000000..efdc98b --- /dev/null +++ b/spec/options/comment.spec.js @@ -0,0 +1,92 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('$comment option', function() { + describe('= true', function() { + var logCalls, consoleLog; + + beforeEach(function () { + consoleLog = console.log; + console.log = log; + }); + + afterEach(function () { + console.log = consoleLog; + }); + + function log() { + logCalls.push(Array.prototype.slice.call(arguments)); + } + + it('should log the text from $comment keyword', function() { + var schema = { + properties: { + foo: {$comment: 'property foo'}, + bar: {$comment: 'property bar', type: 'integer'} + } + }; + + var ajv = new Ajv({$comment: true}); + var fullAjv = new Ajv({allErrors: true, $comment: true}); + + [ajv, fullAjv].forEach(function (_ajv) { + var validate = _ajv.compile(schema); + + test({}, true, []); + test({foo: 1}, true, [['property foo']]); + test({foo: 1, bar: 2}, true, [['property foo'], ['property bar']]); + test({foo: 1, bar: 'baz'}, false, [['property foo'], ['property bar']]); + + function test(data, valid, expectedLogCalls) { + logCalls = []; + validate(data) .should.equal(valid); + logCalls .should.eql(expectedLogCalls); + } + }); + + console.log = consoleLog; + }); + }); + + describe('function hook', function() { + var hookCalls; + + function hook() { + hookCalls.push(Array.prototype.slice.call(arguments)); + } + + it('should pass the text from $comment keyword to the hook', function() { + var schema = { + properties: { + foo: {$comment: 'property foo'}, + bar: {$comment: 'property bar', type: 'integer'} + } + }; + + var ajv = new Ajv({$comment: hook}); + var fullAjv = new Ajv({allErrors: true, $comment: hook}); + + [ajv, fullAjv].forEach(function (_ajv) { + var validate = _ajv.compile(schema); + + test({}, true, []); + test({foo: 1}, true, [['property foo', '#/properties/foo/$comment', schema]]); + test({foo: 1, bar: 2}, true, + [['property foo', '#/properties/foo/$comment', schema], + ['property bar', '#/properties/bar/$comment', schema]]); + test({foo: 1, bar: 'baz'}, false, + [['property foo', '#/properties/foo/$comment', schema], + ['property bar', '#/properties/bar/$comment', schema]]); + + function test(data, valid, expectedHookCalls) { + hookCalls = []; + validate(data) .should.equal(valid); + hookCalls .should.eql(expectedHookCalls); + } + }); + }); + }); +}); diff --git a/spec/options/meta_validateSchema.spec.js b/spec/options/meta_validateSchema.spec.js new file mode 100644 index 0000000..2e287da --- /dev/null +++ b/spec/options/meta_validateSchema.spec.js @@ -0,0 +1,79 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('meta and validateSchema options', function() { + it('should add draft-7 meta schema by default', function() { + testOptionMeta(new Ajv); + testOptionMeta(new Ajv({ meta: true })); + + function testOptionMeta(ajv) { + ajv.getSchema('http://json-schema.org/draft-07/schema') .should.be.a('function'); + ajv.validateSchema({ type: 'integer' }) .should.equal(true); + ajv.validateSchema({ type: 123 }) .should.equal(false); + should.not.throw(function() { ajv.addSchema({ type: 'integer' }); }); + should.throw(function() { ajv.addSchema({ type: 123 }); }); + } + }); + + it('should throw if meta: false and validateSchema: true', function() { + var ajv = new Ajv({ meta: false }); + should.not.exist(ajv.getSchema('http://json-schema.org/draft-07/schema')); + should.not.throw(function() { ajv.addSchema({ type: 'wrong_type' }, 'integer'); }); + }); + + it('should skip schema validation with validateSchema: false', function() { + var ajv = new Ajv; + should.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); + + ajv = new Ajv({ validateSchema: false }); + should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); + + ajv = new Ajv({ validateSchema: false, meta: false }); + should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); + }); + + it('should not throw on invalid schema with validateSchema: "log"', function() { + var logError = console.error; + var loggedError = false; + console.error = function() { loggedError = true; logError.apply(console, arguments); }; + + var ajv = new Ajv({ validateSchema: 'log' }); + should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); + loggedError .should.equal(true); + + loggedError = false; + ajv = new Ajv({ validateSchema: 'log', meta: false }); + should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); }); + loggedError .should.equal(false); + console.error = logError; + }); + + it('should validate v6 schema', function() { + var ajv = new Ajv; + ajv.validateSchema({ contains: { minimum: 2 } }) .should.equal(true); + ajv.validateSchema({ contains: 2 }). should.equal(false); + }); + + it('should use option meta as default meta schema', function() { + var meta = { + $schema: 'http://json-schema.org/draft-07/schema', + properties: { + myKeyword: { type: 'boolean' } + } + }; + var ajv = new Ajv({ meta: meta }); + ajv.validateSchema({ myKeyword: true }) .should.equal(true); + ajv.validateSchema({ myKeyword: 2 }) .should.equal(false); + ajv.validateSchema({ + $schema: 'http://json-schema.org/draft-07/schema', + myKeyword: 2 + }) .should.equal(true); + + ajv = new Ajv; + ajv.validateSchema({ myKeyword: true }) .should.equal(true); + ajv.validateSchema({ myKeyword: 2 }) .should.equal(true); + }); +}); diff --git a/spec/options/nullable.spec.js b/spec/options/nullable.spec.js new file mode 100644 index 0000000..df1bda5 --- /dev/null +++ b/spec/options/nullable.spec.js @@ -0,0 +1,97 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('nullable option', function() { + var ajv; + + describe('= true', function() { + beforeEach(function () { + ajv = new Ajv({ + nullable: true + }); + }); + + it('should add keyword "nullable"', function() { + testNullable({ + type: 'number', + nullable: true + }); + + testNullable({ + type: ['number'], + nullable: true + }); + + testNullable({ + type: ['number', 'null'] + }); + + testNullable({ + type: ['number', 'null'], + nullable: true + }); + + testNotNullable({type: 'number'}); + + testNotNullable({type: ['number']}); + }); + + it('should respect "nullable" == false with opts.nullable == true', function() { + testNotNullable({ + type: 'number', + nullable: false + }); + + testNotNullable({ + type: ['number'], + nullable: false + }); + }); + }); + + describe('without option "nullable"', function() { + it('should ignore keyword nullable', function() { + ajv = new Ajv; + + testNotNullable({ + type: 'number', + nullable: true + }); + + testNotNullable({ + type: ['number'], + nullable: true + }); + + testNullable({ + type: ['number', 'null'], + }); + + testNullable({ + type: ['number', 'null'], + nullable: true + }); + + should.not.throw(function () { + ajv.compile({nullable: false}); + }); + }); + }); + + function testNullable(schema) { + var validate = ajv.compile(schema); + validate(1) .should.equal(true); + validate(null) .should.equal(true); + validate('1') .should.equal(false); + } + + function testNotNullable(schema) { + var validate = ajv.compile(schema); + validate(1) .should.equal(true); + validate(null) .should.equal(false); + validate('1') .should.equal(false); + } +}); diff --git a/spec/options/options_add_schemas.spec.js b/spec/options/options_add_schemas.spec.js new file mode 100644 index 0000000..e1a59f2 --- /dev/null +++ b/spec/options/options_add_schemas.spec.js @@ -0,0 +1,130 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('options to add schemas', function() { + describe('schemas', function() { + it('should add schemas from object', function() { + var ajv = new Ajv({ schemas: { + int: { type: 'integer' }, + str: { type: 'string' } + }}); + + ajv.validate('int', 123) .should.equal(true); + ajv.validate('int', 'foo') .should.equal(false); + ajv.validate('str', 'foo') .should.equal(true); + ajv.validate('str', 123) .should.equal(false); + }); + + it('should add schemas from array', function() { + var ajv = new Ajv({ schemas: [ + { $id: 'int', type: 'integer' }, + { $id: 'str', type: 'string' }, + { $id: 'obj', properties: { int: { $ref: 'int' }, str: { $ref: 'str' } } } + ]}); + + ajv.validate('obj', { int: 123, str: 'foo' }) .should.equal(true); + ajv.validate('obj', { int: 'foo', str: 'bar' }) .should.equal(false); + ajv.validate('obj', { int: 123, str: 456 }) .should.equal(false); + }); + }); + + + describe('addUsedSchema', function() { + [true, undefined].forEach(function (optionValue) { + describe('= ' + optionValue, function() { + var ajv; + + beforeEach(function() { + ajv = new Ajv({ addUsedSchema: optionValue }); + }); + + describe('compile and validate', function() { + it('should add schema', function() { + var schema = { $id: 'str', type: 'string' }; + var validate = ajv.compile(schema); + validate('abc') .should.equal(true); + validate(1) .should.equal(false); + ajv.getSchema('str') .should.equal(validate); + + schema = { $id: 'int', type: 'integer' }; + ajv.validate(schema, 1) .should.equal(true); + ajv.validate(schema, 'abc') .should.equal(false); + ajv.getSchema('int') .should.be.a('function'); + }); + + it('should throw with duplicate ID', function() { + ajv.compile({ $id: 'str', type: 'string' }); + should.throw(function() { + ajv.compile({ $id: 'str', minLength: 2 }); + }); + + var schema = { $id: 'int', type: 'integer' }; + var schema2 = { $id: 'int', minimum: 0 }; + ajv.validate(schema, 1) .should.equal(true); + should.throw(function() { + ajv.validate(schema2, 1); + }); + }); + }); + }); + }); + + describe('= false', function() { + var ajv; + + beforeEach(function() { + ajv = new Ajv({ addUsedSchema: false }); + }); + + + describe('compile and validate', function() { + it('should NOT add schema', function() { + var schema = { $id: 'str', type: 'string' }; + var validate = ajv.compile(schema); + validate('abc') .should.equal(true); + validate(1) .should.equal(false); + should.equal(ajv.getSchema('str'), undefined); + + schema = { $id: 'int', type: 'integer' }; + ajv.validate(schema, 1) .should.equal(true); + ajv.validate(schema, 'abc') .should.equal(false); + should.equal(ajv.getSchema('int'), undefined); + }); + + it('should NOT throw with duplicate ID', function() { + ajv.compile({ $id: 'str', type: 'string' }); + should.not.throw(function() { + ajv.compile({ $id: 'str', minLength: 2 }); + }); + + var schema = { $id: 'int', type: 'integer' }; + var schema2 = { $id: 'int', minimum: 0 }; + ajv.validate(schema, 1) .should.equal(true); + should.not.throw(function() { + ajv.validate(schema2, 1) .should.equal(true); + }); + }); + }); + }); + }); + + + describe('serialize', function() { + var serializeCalled; + + it('should use custom function to serialize schema to string', function() { + serializeCalled = undefined; + var ajv = new Ajv({ serialize: serialize }); + ajv.addSchema({ type: 'string' }); + should.equal(serializeCalled, true); + }); + + function serialize(schema) { + serializeCalled = true; + return JSON.stringify(schema); + } + }); +}); diff --git a/spec/options/options_code.spec.js b/spec/options/options_code.spec.js new file mode 100644 index 0000000..8884c8c --- /dev/null +++ b/spec/options/options_code.spec.js @@ -0,0 +1,115 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('code generation options', function () { + describe('sourceCode', function() { + describe('= true', function() { + it('should add source.code property', function() { + test(new Ajv({sourceCode: true})); + + function test(ajv) { + var validate = ajv.compile({ "type": "number" }); + validate.source.code .should.be.a('string'); + } + }); + }); + + describe('= false and default', function() { + it('should not add source and sourceCode properties', function() { + test(new Ajv); + test(new Ajv({sourceCode: false})); + + function test(ajv) { + var validate = ajv.compile({ "type": "number" }); + should.not.exist(validate.source); + should.not.exist(validate.sourceCode); + } + }); + }); + }); + + + describe('processCode', function() { + it('should process generated code', function() { + var ajv = new Ajv; + var validate = ajv.compile({type: 'string'}); + validate.toString().split('\n').length .should.equal(1); + + var beautify = require('js-beautify').js_beautify; + var ajvPC = new Ajv({processCode: beautify}); + validate = ajvPC.compile({type: 'string'}); + validate.toString().split('\n').length .should.be.above(1); + validate('foo') .should.equal(true); + validate(1) .should.equal(false); + }); + }); + + + describe('passContext option', function() { + var ajv, contexts; + + beforeEach(function() { + contexts = []; + }); + + describe('= true', function() { + it('should pass this value as context to custom keyword validation function', function() { + var validate = getValidate(true); + var self = {}; + validate.call(self, {}); + contexts .should.have.length(4); + contexts.forEach(function(ctx) { + ctx .should.equal(self); + }); + }); + }); + + describe('= false', function() { + it('should pass ajv instance as context to custom keyword validation function', function() { + var validate = getValidate(false); + var self = {}; + validate.call(self, {}); + contexts .should.have.length(4); + contexts.forEach(function(ctx) { + ctx .should.equal(ajv); + }); + }); + }); + + function getValidate(passContext) { + ajv = new Ajv({ passContext: passContext, inlineRefs: false }); + ajv.addKeyword('testValidate', { validate: storeContext }); + ajv.addKeyword('testCompile', { compile: compileTestValidate }); + + var schema = { + definitions: { + test1: { + testValidate: true, + testCompile: true, + }, + test2: { + allOf: [ { $ref: '#/definitions/test1' } ] + } + }, + allOf: [ + { $ref: '#/definitions/test1' }, + { $ref: '#/definitions/test2' } + ] + }; + + return ajv.compile(schema); + } + + function storeContext() { + contexts.push(this); + return true; + } + + function compileTestValidate() { + return storeContext; + } + }); +}); diff --git a/spec/options/options_refs.spec.js b/spec/options/options_refs.spec.js new file mode 100644 index 0000000..1f1d20a --- /dev/null +++ b/spec/options/options_refs.spec.js @@ -0,0 +1,167 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('referenced schema options', function() { + describe('extendRefs', function() { + describe('= true', function() { + it('should allow extending $ref with other keywords', function() { + test(new Ajv({ extendRefs: true }), true); + }); + + it('should NOT log warning if extendRefs is true', function() { + testWarning(new Ajv({ extendRefs: true })); + }); + }); + + describe('= "ignore" and default', function() { + it('should ignore other keywords when $ref is used', function() { + test(new Ajv); + test(new Ajv({ extendRefs: 'ignore' }), false); + }); + + it('should log warning when other keywords are used with $ref', function() { + testWarning(new Ajv, /keywords\signored/); + testWarning(new Ajv({ extendRefs: 'ignore' }), /keywords\signored/); + }); + }); + + describe('= "fail"', function() { + it('should fail schema compilation if other keywords are used with $ref', function() { + testFail(new Ajv({ extendRefs: 'fail' })); + + function testFail(ajv) { + should.throw(function() { + var schema = { + "definitions": { + "int": { "type": "integer" } + }, + "$ref": "#/definitions/int", + "minimum": 10 + }; + ajv.compile(schema); + }); + + should.not.throw(function() { + var schema = { + "definitions": { + "int": { "type": "integer" } + }, + "allOf": [ + { "$ref": "#/definitions/int" }, + { "minimum": 10 } + ] + }; + ajv.compile(schema); + }); + } + }); + }); + + function test(ajv, shouldExtendRef) { + var schema = { + "definitions": { + "int": { "type": "integer" } + }, + "$ref": "#/definitions/int", + "minimum": 10 + }; + + var validate = ajv.compile(schema); + validate(10) .should.equal(true); + validate(1) .should.equal(!shouldExtendRef); + + schema = { + "definitions": { + "int": { "type": "integer" } + }, + "type": "object", + "properties": { + "foo": { + "$ref": "#/definitions/int", + "minimum": 10 + }, + "bar": { + "allOf": [ + { "$ref": "#/definitions/int" }, + { "minimum": 10 } + ] + } + } + }; + + validate = ajv.compile(schema); + validate({ foo: 10, bar: 10 }) .should.equal(true); + validate({ foo: 1, bar: 10 }) .should.equal(!shouldExtendRef); + validate({ foo: 10, bar: 1 }) .should.equal(false); + } + + function testWarning(ajv, msgPattern) { + var oldConsole; + try { + oldConsole = console.warn; + var consoleMsg; + console.warn = function() { + consoleMsg = Array.prototype.join.call(arguments, ' '); + }; + + var schema = { + "definitions": { + "int": { "type": "integer" } + }, + "$ref": "#/definitions/int", + "minimum": 10 + }; + + ajv.compile(schema); + if (msgPattern) consoleMsg .should.match(msgPattern); + else should.not.exist(consoleMsg); + } finally { + console.warn = oldConsole; + } + } + }); + + + describe('missingRefs', function() { + it('should throw if ref is missing without this option', function() { + var ajv = new Ajv; + should.throw(function() { + ajv.compile({ $ref: 'missing_reference' }); + }); + }); + + it('should not throw and pass validation with missingRef == "ignore"', function() { + testMissingRefsIgnore(new Ajv({ missingRefs: 'ignore' })); + testMissingRefsIgnore(new Ajv({ missingRefs: 'ignore', allErrors: true })); + + function testMissingRefsIgnore(ajv) { + var validate = ajv.compile({ $ref: 'missing_reference' }); + validate({}) .should.equal(true); + } + }); + + it('should not throw and fail validation with missingRef == "fail" if the ref is used', function() { + testMissingRefsFail(new Ajv({ missingRefs: 'fail' })); + testMissingRefsFail(new Ajv({ missingRefs: 'fail', verbose: true })); + testMissingRefsFail(new Ajv({ missingRefs: 'fail', allErrors: true })); + testMissingRefsFail(new Ajv({ missingRefs: 'fail', allErrors: true, verbose: true })); + + function testMissingRefsFail(ajv) { + var validate = ajv.compile({ + anyOf: [ + { type: 'number' }, + { $ref: 'missing_reference' } + ] + }); + validate(123) .should.equal(true); + validate('foo') .should.equal(false); + + validate = ajv.compile({ $ref: 'missing_reference' }); + validate({}) .should.equal(false); + } + }); + }); +}); diff --git a/spec/options/options_reporting.spec.js b/spec/options/options_reporting.spec.js new file mode 100644 index 0000000..578611e --- /dev/null +++ b/spec/options/options_reporting.spec.js @@ -0,0 +1,158 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('reporting options', function () { + describe('verbose', function() { + it('should add schema, parentSchema and data to errors with verbose option == true', function() { + testVerbose(new Ajv({ verbose: true })); + testVerbose(new Ajv({ verbose: true, allErrors: true })); + + function testVerbose(ajv) { + var schema = { properties: { foo: { minimum: 5 } } }; + var validate = ajv.compile(schema); + + var data = { foo: 3 }; + validate(data) .should.equal(false); + validate.errors .should.have.length(1); + var err = validate.errors[0]; + + should.equal(err.schema, 5); + err.parentSchema .should.eql({ minimum: 5 }); + err.parentSchema .should.equal(schema.properties.foo); // by reference + should.equal(err.data, 3); + } + }); + }); + + + describe('allErrors', function() { + it('should be disabled inside "not" keyword', function() { + test(new Ajv, false); + test(new Ajv({ allErrors: true }), true); + + function test(ajv, allErrors) { + var format1called = false + , format2called = false; + + ajv.addFormat('format1', function() { + format1called = true; + return false; + }); + + ajv.addFormat('format2', function() { + format2called = true; + return false; + }); + + var schema1 = { + allOf: [ + { format: 'format1' }, + { format: 'format2' } + ] + }; + + ajv.validate(schema1, 'abc') .should.equal(false); + ajv.errors .should.have.length(allErrors ? 2 : 1); + format1called .should.equal(true); + format2called .should.equal(allErrors); + + var schema2 = { + not: schema1 + }; + + format1called = format2called = false; + ajv.validate(schema2, 'abc') .should.equal(true); + should.equal(ajv.errors, null); + format1called .should.equal(true); + format2called .should.equal(false); + } + }); + }); + + + describe('logger', function() { + /** + * The logger option tests are based on the meta scenario which writes into the logger.warn + */ + + var origConsoleWarn = console.warn; + var consoleCalled; + + beforeEach(function() { + consoleCalled = false; + console.warn = function() { + consoleCalled = true; + }; + }); + + afterEach(function() { + console.warn = origConsoleWarn; + }); + + it('no custom logger is given - global console should be used', function() { + var ajv = new Ajv({ + meta: false + }); + + ajv.compile({ + type: 'number', + minimum: 1 + }); + + should.equal(consoleCalled, true); + }); + + it('custom logger is an object - logs should only report to it', function() { + var loggerCalled = false; + + var logger = { + warn: log, + log: log, + error: log + }; + + var ajv = new Ajv({ + meta: false, + logger: logger + }); + + ajv.compile({ + type: 'number', + minimum: 1 + }); + + should.equal(loggerCalled, true); + should.equal(consoleCalled, false); + + function log() { + loggerCalled = true; + } + }); + + it('logger option is false - no logs should be reported', function() { + var ajv = new Ajv({ + meta: false, + logger: false + }); + + ajv.compile({ + type: 'number', + minimum: 1 + }); + + should.equal(consoleCalled, false); + }); + + it('logger option is an object without required methods - an error should be thrown', function() { + (function(){ + new Ajv({ + meta: false, + logger: {} + }); + }).should.throw(Error, /logger must implement log, warn and error methods/); + }); + }); +}); diff --git a/spec/options/options_validation.spec.js b/spec/options/options_validation.spec.js new file mode 100644 index 0000000..e362826 --- /dev/null +++ b/spec/options/options_validation.spec.js @@ -0,0 +1,94 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('validation options', function() { + describe('format', function() { + it('should not validate formats if option format == false', function() { + var ajv = new Ajv + , ajvFF = new Ajv({ format: false }); + + var schema = { format: 'date-time' }; + var invalideDateTime = '06/19/1963 08:30:06 PST'; + + ajv.validate(schema, invalideDateTime) .should.equal(false); + ajvFF.validate(schema, invalideDateTime) .should.equal(true); + }); + }); + + + describe('formats', function() { + it('should add formats from options', function() { + var ajv = new Ajv({ formats: { + identifier: /^[a-z_$][a-z0-9_$]*$/i + }}); + + var validate = ajv.compile({ format: 'identifier' }); + validate('Abc1') .should.equal(true); + validate('123') .should.equal(false); + validate(123) .should.equal(true); + }); + }); + + + describe('uniqueItems', function() { + it('should not validate uniqueItems with uniqueItems option == false', function() { + testUniqueItems(new Ajv({ uniqueItems: false })); + testUniqueItems(new Ajv({ uniqueItems: false, allErrors: true })); + + function testUniqueItems(ajv) { + var validate = ajv.compile({ uniqueItems: true }); + validate([1,2,3]) .should.equal(true); + validate([1,1,1]) .should.equal(true); + } + }); + }); + + + describe('unicode', function() { + it('should use String.prototype.length with unicode option == false', function() { + var ajvUnicode = new Ajv; + testUnicode(new Ajv({ unicode: false })); + testUnicode(new Ajv({ unicode: false, allErrors: true })); + + function testUnicode(ajv) { + var validateWithUnicode = ajvUnicode.compile({ minLength: 2 }); + var validate = ajv.compile({ minLength: 2 }); + + validateWithUnicode('😀') .should.equal(false); + validate('😀') .should.equal(true); + + validateWithUnicode = ajvUnicode.compile({ maxLength: 1 }); + validate = ajv.compile({ maxLength: 1 }); + + validateWithUnicode('😀') .should.equal(true); + validate('😀') .should.equal(false); + } + }); + }); + + + describe('multipleOfPrecision', function() { + it('should allow for some deviation from 0 when validating multipleOf with value < 1', function() { + test(new Ajv({ multipleOfPrecision: 7 })); + test(new Ajv({ multipleOfPrecision: 7, allErrors: true })); + + function test(ajv) { + var schema = { multipleOf: 0.01 }; + var validate = ajv.compile(schema); + + validate(4.18) .should.equal(true); + validate(4.181) .should.equal(false); + + schema = { multipleOf: 0.0000001 }; + validate = ajv.compile(schema); + + validate(53.198098) .should.equal(true); + validate(53.1980981) .should.equal(true); + validate(53.19809811) .should.equal(false); + } + }); + }); +}); diff --git a/spec/options/ownProperties.spec.js b/spec/options/ownProperties.spec.js new file mode 100644 index 0000000..312579f --- /dev/null +++ b/spec/options/ownProperties.spec.js @@ -0,0 +1,178 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('ownProperties option', function() { + var ajv, ajvOP, ajvOP1; + + beforeEach(function() { + ajv = new Ajv({ allErrors: true }); + ajvOP = new Ajv({ ownProperties: true, allErrors: true }); + ajvOP1 = new Ajv({ ownProperties: true }); + }); + + it('should only validate own properties with additionalProperties', function() { + var schema = { + properties: { a: { type: 'number' } }, + additionalProperties: false + }; + + var obj = { a: 1 }; + var proto = { b: 2 }; + test(schema, obj, proto); + }); + + it('should only validate own properties with properties keyword', function() { + var schema = { + properties: { + a: { type: 'number' }, + b: { type: 'number' } + } + }; + + var obj = { a: 1 }; + var proto = { b: 'not a number' }; + test(schema, obj, proto); + }); + + it('should only validate own properties with required keyword', function() { + var schema = { + required: ['a', 'b'] + }; + + var obj = { a: 1 }; + var proto = { b: 2 }; + test(schema, obj, proto, 1, true); + }); + + it('should only validate own properties with required keyword - many properties', function() { + ajv = new Ajv({ allErrors: true, loopRequired: 1 }); + ajvOP = new Ajv({ ownProperties: true, allErrors: true, loopRequired: 1 }); + ajvOP1 = new Ajv({ ownProperties: true, loopRequired: 1 }); + + var schema = { + required: ['a', 'b', 'c', 'd'] + }; + + var obj = { a: 1, b: 2 }; + var proto = { c: 3, d: 4 }; + test(schema, obj, proto, 2, true); + }); + + it('should only validate own properties with required keyword as $data', function() { + ajv = new Ajv({ allErrors: true, $data: true }); + ajvOP = new Ajv({ ownProperties: true, allErrors: true, $data: true }); + ajvOP1 = new Ajv({ ownProperties: true, $data: true }); + + var schema = { + required: { $data: '0/req' }, + properties: { + req: { + type: 'array', + items: { type: 'string' } + } + } + }; + + var obj = { + req: ['a', 'b'], + a: 1 + }; + var proto = { b: 2 }; + test(schema, obj, proto, 1, true); + }); + + it('should only validate own properties with properties and required keyword', function() { + var schema = { + properties: { + a: { type: 'number' }, + b: { type: 'number' } + }, + required: ['a', 'b'] + }; + + var obj = { a: 1 }; + var proto = { b: 2 }; + test(schema, obj, proto, 1, true); + }); + + it('should only validate own properties with dependencies keyword', function() { + var schema = { + dependencies: { + a: ['c'], + b: ['d'] + } + }; + + var obj = { a: 1, c: 3 }; + var proto = { b: 2 }; + test(schema, obj, proto); + + obj = { a: 1, b: 2, c: 3 }; + proto = { d: 4 }; + test(schema, obj, proto, 1, true); + }); + + it('should only validate own properties with schema dependencies', function() { + var schema = { + dependencies: { + a: { not: { required: ['c'] } }, + b: { not: { required: ['d'] } } + } + }; + + var obj = { a: 1, d: 3 }; + var proto = { b: 2 }; + test(schema, obj, proto); + + obj = { a: 1, b: 2 }; + proto = { d: 4 }; + test(schema, obj, proto); + }); + + it('should only validate own properties with patternProperties', function() { + var schema = { + patternProperties: { 'f.*o': { type: 'integer' } }, + }; + + var obj = { fooo: 1 }; + var proto = { foo: 'not a number' }; + test(schema, obj, proto); + }); + + it('should only validate own properties with propertyNames', function() { + var schema = { + propertyNames: { + format: 'email' + } + }; + + var obj = { 'e@example.com': 2 }; + var proto = { 'not email': 1 }; + test(schema, obj, proto, 2); + }); + + function test(schema, obj, proto, errors, reverse) { + errors = errors || 1; + var validate = ajv.compile(schema); + var validateOP = ajvOP.compile(schema); + var validateOP1 = ajvOP1.compile(schema); + var data = Object.create(proto); + for (var key in obj) data[key] = obj[key]; + + if (reverse) { + validate(data) .should.equal(true); + validateOP(data) .should.equal(false); + validateOP.errors .should.have.length(errors); + validateOP1(data) .should.equal(false); + validateOP1.errors .should.have.length(1); + } else { + validate(data) .should.equal(false); + validate.errors .should.have.length(errors); + validateOP(data) .should.equal(true); + validateOP1(data) .should.equal(true); + } + } +}); diff --git a/spec/options/removeAdditional.spec.js b/spec/options/removeAdditional.spec.js new file mode 100644 index 0000000..1eef0b7 --- /dev/null +++ b/spec/options/removeAdditional.spec.js @@ -0,0 +1,122 @@ +'use strict'; + +var Ajv = require('../ajv'); +require('../chai').should(); + + +describe('removeAdditional option', function() { + it('should remove all additional properties', function() { + var ajv = new Ajv({ removeAdditional: 'all' }); + + ajv.addSchema({ + $id: '//test/fooBar', + properties: { foo: { type: 'string' }, bar: { type: 'string' } } + }); + + var object = { + foo: 'foo', bar: 'bar', baz: 'baz-to-be-removed' + }; + + ajv.validate('//test/fooBar', object).should.equal(true); + object.should.have.property('foo'); + object.should.have.property('bar'); + object.should.not.have.property('baz'); + }); + + + it('should remove properties that would error when `additionalProperties = false`', function() { + var ajv = new Ajv({ removeAdditional: true }); + + ajv.addSchema({ + $id: '//test/fooBar', + properties: { foo: { type: 'string' }, bar: { type: 'string' } }, + additionalProperties: false + }); + + var object = { + foo: 'foo', bar: 'bar', baz: 'baz-to-be-removed' + }; + + ajv.validate('//test/fooBar', object).should.equal(true); + object.should.have.property('foo'); + object.should.have.property('bar'); + object.should.not.have.property('baz'); + }); + + + it('should remove properties that would error when `additionalProperties = false` (many properties, boolean schema)', function() { + var ajv = new Ajv({removeAdditional: true}); + + var schema = { + properties: { + obj: { + additionalProperties: false, + properties: { + a: { type: 'string' }, + b: false, + c: { type: 'string' }, + d: { type: 'string' }, + e: { type: 'string' }, + f: { type: 'string' }, + g: { type: 'string' }, + h: { type: 'string' }, + i: { type: 'string' } + } + } + } + }; + + var data = { + obj: { + a: 'valid', + b: 'should not be removed', + additional: 'will be removed' + } + }; + + ajv.validate(schema, data) .should.equal(false); + data .should.eql({ + obj: { + a: 'valid', + b: 'should not be removed' + } + }); + }); + + + it('should remove properties that would error when `additionalProperties` is a schema', function() { + var ajv = new Ajv({ removeAdditional: 'failing' }); + + ajv.addSchema({ + $id: '//test/fooBar', + properties: { foo: { type: 'string' }, bar: { type: 'string' } }, + additionalProperties: { type: 'string' } + }); + + var object = { + foo: 'foo', bar: 'bar', baz: 'baz-to-be-kept', fizz: 1000 + }; + + ajv.validate('//test/fooBar', object).should.equal(true); + object.should.have.property('foo'); + object.should.have.property('bar'); + object.should.have.property('baz'); + object.should.not.have.property('fizz'); + + ajv.addSchema({ + $id: '//test/fooBar2', + properties: { foo: { type: 'string' }, bar: { type: 'string' } }, + additionalProperties: { type: 'string', pattern: '^to-be-', maxLength: 10 } + }); + + object = { + foo: 'foo', bar: 'bar', baz: 'to-be-kept', quux: 'to-be-removed', fizz: 1000 + }; + + ajv.validate('//test/fooBar2', object).should.equal(true); + object.should.have.property('foo'); + object.should.have.property('bar'); + object.should.have.property('baz'); + object.should.not.have.property('fizz'); + }); +}); diff --git a/spec/options/schemaId.spec.js b/spec/options/schemaId.spec.js new file mode 100644 index 0000000..f887103 --- /dev/null +++ b/spec/options/schemaId.spec.js @@ -0,0 +1,72 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('schemaId option', function() { + describe('= "$id" (default)', function() { + it('should use $id and ignore id', function() { + test(new Ajv); + test(new Ajv({schemaId: '$id'})); + + function test(ajv) { + ajv.addSchema({ $id: 'mySchema1', type: 'string' }); + var validate = ajv.getSchema('mySchema1'); + validate('foo') .should.equal(true); + validate(1) .should.equal(false); + + validate = ajv.compile({ id: 'mySchema2', type: 'string' }); + should.not.exist(ajv.getSchema('mySchema2')); + } + }); + }); + + describe('= "id"', function() { + it('should use id and ignore $id', function() { + var ajv = new Ajv({schemaId: 'id', meta: false}); + ajv.addMetaSchema(require('../../lib/refs/json-schema-draft-04.json')); + ajv._opts.defaultMeta = 'http://json-schema.org/draft-04/schema#'; + + ajv.addSchema({ id: 'mySchema1', type: 'string' }); + var validate = ajv.getSchema('mySchema1'); + validate('foo') .should.equal(true); + validate(1) .should.equal(false); + + validate = ajv.compile({ $id: 'mySchema2', type: 'string' }); + should.not.exist(ajv.getSchema('mySchema2')); + }); + }); + + describe('= "auto"', function() { + it('should use both id and $id', function() { + var ajv = new Ajv({schemaId: 'auto'}); + + ajv.addSchema({ $id: 'mySchema1', type: 'string' }); + var validate = ajv.getSchema('mySchema1'); + validate('foo') .should.equal(true); + validate(1) .should.equal(false); + + ajv.addSchema({ id: 'mySchema2', type: 'string' }); + validate = ajv.getSchema('mySchema2'); + validate('foo') .should.equal(true); + validate(1) .should.equal(false); + }); + + it('should throw if both id and $id are available and different', function() { + var ajv = new Ajv({schemaId: 'auto'}); + + ajv.compile({ + id: 'mySchema', + $id: 'mySchema' + }); + + should.throw(function() { + ajv.compile({ + id: 'mySchema1', + $id: 'mySchema2' + }); + }); + }); + }); +}); diff --git a/spec/options/unknownFormats.spec.js b/spec/options/unknownFormats.spec.js new file mode 100644 index 0000000..81ea326 --- /dev/null +++ b/spec/options/unknownFormats.spec.js @@ -0,0 +1,108 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('unknownFormats option', function() { + describe('= true (default)', function() { + it('should fail schema compilation if unknown format is used', function() { + test(new Ajv); + test(new Ajv({unknownFormats: true})); + + function test(ajv) { + should.throw(function() { + ajv.compile({ format: 'unknown' }); + }); + } + }); + + it('should fail validation if unknown format is used via $data', function() { + test(new Ajv({$data: true})); + test(new Ajv({$data: true, unknownFormats: true})); + + function test(ajv) { + var validate = ajv.compile({ + properties: { + foo: { format: { $data: '1/bar' } }, + bar: { type: 'string' } + } + }); + + validate({foo: 1, bar: 'unknown'}) .should.equal(false); + validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true); + validate({foo: '20161016', bar: 'date'}) .should.equal(false); + validate({foo: '20161016'}) .should.equal(true); + + validate({foo: '2016-10-16', bar: 'unknown'}) .should.equal(false); + } + }); + }); + + describe('= "ignore (default before 5.0.0)"', function() { + it('should pass schema compilation and be valid if unknown format is used', function() { + test(new Ajv({unknownFormats: 'ignore'})); + + function test(ajv) { + var validate = ajv.compile({ format: 'unknown' }); + validate('anything') .should.equal(true); + } + }); + + it('should be valid if unknown format is used via $data', function() { + test(new Ajv({$data: true, unknownFormats: 'ignore'})); + + function test(ajv) { + var validate = ajv.compile({ + properties: { + foo: { format: { $data: '1/bar' } }, + bar: { type: 'string' } + } + }); + + validate({foo: 1, bar: 'unknown'}) .should.equal(true); + validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true); + validate({foo: '20161016', bar: 'date'}) .should.equal(false); + validate({foo: '20161016'}) .should.equal(true); + validate({foo: '2016-10-16', bar: 'unknown'}) .should.equal(true); + } + }); + }); + + describe('= [String]', function() { + it('should pass schema compilation and be valid if whitelisted unknown format is used', function() { + test(new Ajv({unknownFormats: ['allowed']})); + + function test(ajv) { + var validate = ajv.compile({ format: 'allowed' }); + validate('anything') .should.equal(true); + + should.throw(function() { + ajv.compile({ format: 'unknown' }); + }); + } + }); + + it('should be valid if whitelisted unknown format is used via $data', function() { + test(new Ajv({$data: true, unknownFormats: ['allowed']})); + + function test(ajv) { + var validate = ajv.compile({ + properties: { + foo: { format: { $data: '1/bar' } }, + bar: { type: 'string' } + } + }); + + validate({foo: 1, bar: 'allowed'}) .should.equal(true); + validate({foo: 1, bar: 'unknown'}) .should.equal(false); + validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true); + validate({foo: '20161016', bar: 'date'}) .should.equal(false); + validate({foo: '20161016'}) .should.equal(true); + + validate({foo: '2016-10-16', bar: 'allowed'}) .should.equal(true); + validate({foo: '2016-10-16', bar: 'unknown'}) .should.equal(false); + } + }); + }); +}); diff --git a/spec/options/useDefaults.spec.js b/spec/options/useDefaults.spec.js new file mode 100644 index 0000000..7a12e84 --- /dev/null +++ b/spec/options/useDefaults.spec.js @@ -0,0 +1,223 @@ +'use strict'; + +var Ajv = require('../ajv'); +var getAjvInstances = require('../ajv_instances'); +require('../chai').should(); + + +describe('useDefaults options', function() { + it('should replace undefined property with default value', function() { + var instances = getAjvInstances({ + allErrors: true, + loopRequired: 3 + }, { useDefaults: true }); + + instances.forEach(test); + + + function test(ajv) { + var schema = { + properties: { + foo: { type: 'string', default: 'abc' }, + bar: { type: 'number', default: 1 }, + baz: { type: 'boolean', default: false }, + nil: { type: 'null', default: null }, + obj: { type: 'object', default: {} }, + arr: { type: 'array', default: [] } + }, + required: ['foo', 'bar', 'baz', 'nil', 'obj', 'arr'], + minProperties: 6 + }; + + var validate = ajv.compile(schema); + + var data = {}; + validate(data) .should.equal(true); + data .should.eql({ foo: 'abc', bar: 1, baz: false, nil: null, obj: {}, arr:[] }); + + data = { foo: 'foo', bar: 2, obj: { test: true } }; + validate(data) .should.equal(true); + data .should.eql({ foo: 'foo', bar: 2, baz: false, nil: null, obj: { test: true }, arr:[] }); + } + }); + + it('should replace undefined item with default value', function() { + test(new Ajv({ useDefaults: true })); + test(new Ajv({ useDefaults: true, allErrors: true })); + + function test(ajv) { + var schema = { + items: [ + { type: 'string', default: 'abc' }, + { type: 'number', default: 1 }, + { type: 'boolean', default: false } + ], + minItems: 3 + }; + + var validate = ajv.compile(schema); + + var data = []; + validate(data) .should.equal(true); + data .should.eql([ 'abc', 1, false ]); + + data = [ 'foo' ]; + validate(data) .should.equal(true); + data .should.eql([ 'foo', 1, false ]); + + data = ['foo', 2,'false']; + validate(data) .should.equal(false); + validate.errors .should.have.length(1); + data .should.eql([ 'foo', 2, 'false' ]); + } + }); + + it('should apply default in "then" subschema (issue #635)', function() { + test(new Ajv({ useDefaults: true })); + test(new Ajv({ useDefaults: true, allErrors: true })); + + function test(ajv) { + var schema = { + if: { required: ['foo'] }, + then: { + properties: { + bar: { default: 2 } + } + }, + else: { + properties: { + foo: { default: 1 } + } + } + }; + + var validate = ajv.compile(schema); + + var data = {}; + validate(data) .should.equal(true); + data .should.eql({foo: 1}); + + data = {foo: 1}; + validate(data) .should.equal(true); + data .should.eql({foo: 1, bar: 2}); + } + }); + + + describe('useDefaults: by value / by reference', function() { + describe('using by value', function() { + it('should NOT modify underlying defaults when modifying validated data', function() { + test('value', new Ajv({ useDefaults: true })); + test('value', new Ajv({ useDefaults: true, allErrors: true })); + }); + }); + + describe('using by reference', function() { + it('should modify underlying defaults when modifying validated data', function() { + test('reference', new Ajv({ useDefaults: 'shared' })); + test('reference', new Ajv({ useDefaults: 'shared', allErrors: true })); + }); + }); + + function test(useDefaultsMode, ajv) { + var schema = { + properties: { + items: { + type: 'array', + default: ['a-default'] + } + } + }; + + var validate = ajv.compile(schema); + + var data = {}; + validate(data) .should.equal(true); + data.items .should.eql([ 'a-default' ]); + + data.items.push('another-value'); + data.items .should.eql([ 'a-default', 'another-value' ]); + + var data2 = {}; + validate(data2) .should.equal(true); + + if (useDefaultsMode == 'reference') + data2.items .should.eql([ 'a-default', 'another-value' ]); + else if (useDefaultsMode == 'value') + data2.items .should.eql([ 'a-default' ]); + else + throw new Error('unknown useDefaults mode'); + } + }); + + + describe('defaults with "empty" values', function() { + var schema, data; + + beforeEach(function() { + schema = { + properties: { + obj: { + properties: { + str: {default: 'foo'}, + n1: {default: 1}, + n2: {default: 2}, + n3: {default: 3} + } + }, + arr: { + items: [ + {default: 'foo'}, + {default: 1}, + {default: 2}, + {default: 3} + ] + } + } + }; + + data = { + obj: { + str: '', + n1: null, + n2: undefined + }, + arr: ['', null, undefined] + }; + }); + + it('should NOT assign defaults when useDefaults is true/"shared"', function() { + test(new Ajv({useDefaults: true})); + test(new Ajv({useDefaults: 'shared'})); + + function test(ajv) { + var validate = ajv.compile(schema); + validate(data) .should.equal(true); + data .should.eql({ + obj: { + str: '', + n1: null, + n2: 2, + n3: 3 + }, + arr: ['', null, 2, 3] + }); + } + }); + + it('should assign defaults when useDefaults = "empty"', function() { + var ajv = new Ajv({useDefaults: 'empty'}); + var validate = ajv.compile(schema); + validate(data) .should.equal(true); + data .should.eql({ + obj: { + str: 'foo', + n1: 1, + n2: 2, + n3: 3 + }, + arr: ['foo', 1, 2, 3] + }); + }); + }); +});