ajv/spec/async.spec.js

460 lines
13 KiB
JavaScript

'use strict';
/* global Promise */
var Ajv = require('./ajv')
, should = require('./chai').should();
describe('compileAsync method', function() {
var ajv, loadCallCount;
var SCHEMAS = {
"http://example.com/object.json": {
"$id": "http://example.com/object.json",
"properties": {
"a": { "type": "string" },
"b": { "$ref": "int2plus.json" }
}
},
"http://example.com/int2plus.json": {
"$id": "http://example.com/int2plus.json",
"type": "integer",
"minimum": 2
},
"http://example.com/tree.json": {
"$id": "http://example.com/tree.json",
"type": "array",
"items": { "$ref": "leaf.json" }
},
"http://example.com/leaf.json": {
"$id": "http://example.com/leaf.json",
"properties": {
"name": { "type": "string" },
"subtree": { "$ref": "tree.json" }
}
},
"http://example.com/recursive.json": {
"$id": "http://example.com/recursive.json",
"properties": {
"b": { "$ref": "parent.json" }
},
"required": ["b"]
},
"http://example.com/invalid.json": {
"$id": "http://example.com/recursive.json",
"properties": {
"invalid": { "type": "number" }
},
"required": "invalid"
},
"http://example.com/foobar.json": {
"$id": "http://example.com/foobar.json",
"$schema": "http://example.com/foobar_meta.json",
"myFooBar": "foo"
},
"http://example.com/foobar_meta.json": {
"$id": "http://example.com/foobar_meta.json",
"type": "object",
"properties": {
"myFooBar": {
"enum": ["foo", "bar"]
}
}
},
"http://example.com/foo.json": {
"$id": "http://example.com/foo.json",
"type": "object",
"properties": {
"bar": {"$ref": "bar.json"},
"other": {"$ref": "other.json"}
}
},
"http://example.com/bar.json": {
"$id": "http://example.com/bar.json",
"type": "object",
"properties": {
"foo": {"$ref": "foo.json"}
}
},
"http://example.com/other.json": {
"$id": "http://example.com/other.json"
}
};
beforeEach(function() {
loadCallCount = 0;
ajv = new Ajv({ loadSchema: loadSchema });
});
it('should compile schemas loading missing schemas with options.loadSchema function', function() {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "object.json" }
}
};
return ajv.compileAsync(schema).then(function (validate) {
should.equal(loadCallCount, 2);
validate .should.be.a('function');
validate({ a: { b: 2 } }) .should.equal(true);
validate({ a: { b: 1 } }) .should.equal(false);
});
});
it('should compile schemas loading missing schemas and return function via callback', function (done) {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "object.json" }
}
};
ajv.compileAsync(schema, function (err, validate) {
should.equal(loadCallCount, 2);
should.not.exist(err);
validate .should.be.a('function');
validate({ a: { b: 2 } }) .should.equal(true);
validate({ a: { b: 1 } }) .should.equal(false);
done();
});
});
it('should correctly load schemas when missing reference has JSON path', function() {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "object.json#/properties/b" }
}
};
return ajv.compileAsync(schema).then(function (validate) {
should.equal(loadCallCount, 2);
validate .should.be.a('function');
validate({ a: 2 }) .should.equal(true);
validate({ a: 1 }) .should.equal(false);
});
});
it('should correctly compile with remote schemas that have mutual references', function() {
var schema = {
"$id": "http://example.com/root.json",
"properties": {
"tree": { "$ref": "tree.json" }
}
};
return ajv.compileAsync(schema).then(function (validate) {
validate .should.be.a('function');
var validData = { tree: [
{ name: 'a', subtree: [ { name: 'a.a' } ] },
{ name: 'b' }
] };
var invalidData = { tree: [
{ name: 'a', subtree: [ { name: 1 } ] }
] };
validate(validData) .should.equal(true);
validate(invalidData) .should.equal(false);
});
});
it('should correctly compile with remote schemas that reference the compiled schema', function() {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "recursive.json" }
}
};
return ajv.compileAsync(schema).then(function (validate) {
should.equal(loadCallCount, 1);
validate .should.be.a('function');
var validData = { a: { b: { a: { b: {} } } } };
var invalidData = { a: { b: { a: {} } } };
validate(validData) .should.equal(true);
validate(invalidData) .should.equal(false);
});
});
it('should resolve reference containing "properties" segment with the same property (issue #220)', function() {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": {
"$ref": "object.json#/properties/a"
}
}
};
return ajv.compileAsync(schema).then(function (validate) {
should.equal(loadCallCount, 2);
validate .should.be.a('function');
validate({ a: 'foo' }) .should.equal(true);
validate({ a: 42 }) .should.equal(false);
});
});
describe('loading metaschemas (#334)', function() {
it('should load metaschema if not available', function() {
return test(SCHEMAS['http://example.com/foobar.json'], 1);
});
it('should load metaschema of referenced schema if not available', function() {
return test({ "$ref": "http://example.com/foobar.json" }, 2);
});
function test(schema, expectedLoadCallCount) {
ajv.addKeyword('myFooBar', {
type: 'string',
validate: function (sch, data) {
return sch == data;
}
});
return ajv.compileAsync(schema).then(function (validate) {
should.equal(loadCallCount, expectedLoadCallCount);
validate .should.be.a('function');
validate('foo') .should.equal(true);
validate('bar') .should.equal(false);
});
}
});
it('should return compiled schema on the next tick if there are no references (#51)', function() {
var schema = {
"$id": "http://example.com/int2plus.json",
"type": "integer",
"minimum": 2
};
var beforeCallback1;
var p1 = ajv.compileAsync(schema).then(function (validate) {
beforeCallback1 .should.equal(true);
spec(validate);
var beforeCallback2;
var p2 = ajv.compileAsync(schema).then(function (_validate) {
beforeCallback2 .should.equal(true);
spec(_validate);
});
beforeCallback2 = true;
return p2;
});
beforeCallback1 = true;
return p1;
function spec(validate) {
should.equal(loadCallCount, 0);
validate .should.be.a('function');
var validData = 2;
var invalidData = 1;
validate(validData) .should.equal(true);
validate(invalidData) .should.equal(false);
}
});
it('should queue calls so only one compileAsync executes at a time (#52)', function() {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "object.json" }
}
};
return Promise.all([
ajv.compileAsync(schema).then(spec),
ajv.compileAsync(schema).then(spec),
ajv.compileAsync(schema).then(spec)
]);
function spec(validate) {
should.equal(loadCallCount, 2);
validate .should.be.a('function');
validate({ a: { b: 2 } }) .should.equal(true);
validate({ a: { b: 1 } }) .should.equal(false);
}
});
it('should throw exception if loadSchema is not passed', function (done) {
var schema = {
"$id": "http://example.com/int2plus.json",
"type": "integer",
"minimum": 2
};
ajv = new Ajv;
should.throw(function() {
ajv.compileAsync(schema, function() {
done(new Error('it should have thrown exception'));
});
});
setTimeout(function() {
// function is needed for the test to pass in Firefox 4
done();
});
});
describe('should return error via callback', function() {
it('if passed schema is invalid', function (done) {
var invalidSchema = {
"$id": "http://example.com/int2plus.json",
"type": "integer",
"minimum": "invalid"
};
ajv.compileAsync(invalidSchema, shouldFail(done));
});
it('if loaded schema is invalid', function (done) {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "invalid.json" }
}
};
ajv.compileAsync(schema, shouldFail(done));
});
it('if required schema is loaded but the reference cannot be resolved', function (done) {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "object.json#/definitions/not_found" }
}
};
ajv.compileAsync(schema, shouldFail(done));
});
it('if loadSchema returned error', function (done) {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "object.json" }
}
};
ajv = new Ajv({ loadSchema: badLoadSchema });
ajv.compileAsync(schema, shouldFail(done));
function badLoadSchema() {
return Promise.reject(new Error('cant load'));
}
});
it('if schema compilation throws some other exception', function (done) {
ajv.addKeyword('badkeyword', { compile: badCompile });
var schema = { badkeyword: true };
ajv.compileAsync(schema, shouldFail(done));
function badCompile(/* schema */) {
throw new Error('cant compile keyword schema');
}
});
function shouldFail(done) {
return function (err, validate) {
should.exist(err);
should.not.exist(validate);
done();
};
}
});
describe('should return error via promise', function() {
it('if passed schema is invalid', function() {
var invalidSchema = {
"$id": "http://example.com/int2plus.json",
"type": "integer",
"minimum": "invalid"
};
return shouldReject(ajv.compileAsync(invalidSchema));
});
it('if loaded schema is invalid', function() {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "invalid.json" }
}
};
return shouldReject(ajv.compileAsync(schema));
});
it('if required schema is loaded but the reference cannot be resolved', function() {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "object.json#/definitions/not_found" }
}
};
return shouldReject(ajv.compileAsync(schema));
});
it('if loadSchema returned error', function() {
var schema = {
"$id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "object.json" }
}
};
ajv = new Ajv({ loadSchema: badLoadSchema });
return shouldReject(ajv.compileAsync(schema));
function badLoadSchema() {
return Promise.reject(new Error('cant load'));
}
});
it('if schema compilation throws some other exception', function() {
ajv.addKeyword('badkeyword', { compile: badCompile });
var schema = { badkeyword: true };
return shouldReject(ajv.compileAsync(schema));
function badCompile(/* schema */) {
throw new Error('cant compile keyword schema');
}
});
function shouldReject(p) {
return p.then(
function(validate) {
should.not.exist(validate);
throw new Error('Promise has resolved; it should have rejected');
},
function(err) {
should.exist(err);
}
);
}
});
describe('schema with multiple remote properties, the first is recursive schema (#801)', function() {
it('should validate data', function() {
var schema = {
"$id": "http://example.com/list.json",
"type": "object",
"properties": {
"foo": {"$ref": "foo.json"}
}
};
return ajv.compileAsync(schema).then(function (validate) {
validate({foo: {}}) .should.equal(true);
});
});
});
function loadSchema(uri) {
loadCallCount++;
return new Promise(function (resolve, reject) {
setTimeout(function() {
if (SCHEMAS[uri]) resolve(SCHEMAS[uri]);
else reject(new Error('404'));
}, 10);
});
}
});