ajv/spec/options.spec.js

727 lines
23 KiB
JavaScript

'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` 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() {
it('should only validate against own properties of data if specified', function() {
var ajv = new Ajv({ ownProperties: true });
var validate = ajv.compile({
properties: { c: { type: 'number' } },
additionalProperties: false
});
var triangle = { a: 1, b: 2 };
function ColoredTriangle() { this.c = 3; }
ColoredTriangle.prototype = triangle;
var object = new ColoredTriangle();
validate(object).should.equal(true);
should.equal(validate.errors, null);
});
it('should only validate against own properties when using patternProperties', function() {
var ajv = new Ajv({ allErrors: true, ownProperties: true });
var validate = ajv.compile({
patternProperties: { 'f.*o': { type: 'integer' } },
});
var baz = { foooo: false, fooooooo: 42.31 };
function FooThing() { this.foo = 'not a number'; }
FooThing.prototype = baz;
var object = new FooThing();
validate(object).should.equal(false);
validate.errors.should.have.length(1);
});
it('should only validate against own properties when using patternGroups', function() {
var ajv = new Ajv({ v5: true, allErrors: true, ownProperties: true });
var validate = ajv.compile({
patternGroups: {
'f.*o': { schema: { type: 'integer' } }
}
});
var baz = { foooo: false, fooooooo: 42.31 };
function FooThing() { this.foo = 'not a number'; }
FooThing.prototype = baz;
var object = new FooThing();
validate(object).should.equal(false);
validate.errors.should.have.length(1);
});
it('should only validate against own properties when using patternRequired', function() {
var ajv = new Ajv({ v5: true, allErrors: true, ownProperties: true });
var validate = ajv.compile({
patternRequired: [ 'f.*o' ]
});
var baz = { foooo: false, fooooooo: 42.31 };
function FooThing() { this.bar = 123; }
FooThing.prototype = baz;
var object = new FooThing();
validate(object).should.equal(false);
validate.errors.should.have.length(1);
});
});
describe('meta and validateSchema', function() {
it('should add draft-4 meta schema by default', function() {
testOptionMeta(new Ajv());
testOptionMeta(new Ajv({ meta: true }));
function testOptionMeta(ajv) {
ajv.getSchema('http://json-schema.org/draft-04/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-04/schema'));
should.throw(function() { ajv.addSchema({ type: 'integer' }, '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);
console.error = logError;
ajv = new Ajv({ validateSchema: 'log', meta: false });
should.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); });
});
it('should validate v5 schema', function() {
var ajv = new Ajv({ v5: true });
ajv.validateSchema({ contains: { minimum: 2 } }) .should.equal(true);
ajv.validateSchema({ contains: 2 }). should.equal(false);
ajv = new Ajv();
ajv.validateSchema({ contains: 2 }). should.equal(true);
});
it('should use option meta as default meta schema', function() {
var meta = {
$schema: 'http://json-schema.org/draft-04/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-04/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);
});
it('should not validate formatMaximum/Minimum if option format == false', function() {
var ajv = new Ajv({ v5: true, allErrors: true })
, ajvFF = new Ajv({ v5: true, allErrors: true, format: false });
var schema = {
format: 'date',
formatMaximum: '2015-08-01'
};
var date = '2015-09-01';
ajv.validate(schema, date) .should.equal(false);
ajvFF.validate(schema, date) .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' ]);
}
});
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('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);
}
});
});
});