'use strict'; 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" } }; beforeEach(function() { loadCallCount = 0; ajv = new Ajv({ loadSchema: loadSchema }); }); it('should compile schemas loading missing schemas with options.loadSchema function', 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 (done) { var schema = { "id": "http://example.com/parent.json", "properties": { "a": { "$ref": "object.json#/properties/b" } } }; ajv.compileAsync(schema, function (err, validate) { should.equal(loadCallCount, 2); should.not.exist(err); validate .should.be.a('function'); validate({ a: 2 }) .should.equal(true); validate({ a: 1 }) .should.equal(false); done(); }); }); it('should correctly compile with remote schemas that have mutual references', function (done) { var schema = { "id": "http://example.com/root.json", "properties": { "tree": { "$ref": "tree.json" } } }; ajv.compileAsync(schema, function (err, validate) { should.not.exist(err); 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); done(); }); }); it('should correctly compile with remote schemas that reference the compiled schema', function (done) { var schema = { "id": "http://example.com/parent.json", "properties": { "a": { "$ref": "recursive.json" } } }; ajv.compileAsync(schema, function (err, validate) { should.equal(loadCallCount, 1); should.not.exist(err); 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); done(); }); }); it('should resolve reference containing "properties" segment with the same property (issue #220)', function (done) { var schema = { "id": "http://example.com/parent.json", "properties": { "a": { "$ref": "object.json#/properties/a" } } }; ajv.compileAsync(schema, function (err, validate) { should.not.exist(err); should.equal(loadCallCount, 2); validate .should.be.a('function'); validate({ a: 'foo' }) .should.equal(true); validate({ a: 42 }) .should.equal(false); done(); }); }); it('should return compiled schema on the next tick if there are no references (#51)', function (done) { var schema = { "id": "http://example.com/int2plus.json", "type": "integer", "minimum": 2 }; var beforeCallback1; ajv.compileAsync(schema, function (err, validate) { beforeCallback1 .should.equal(true); spec(err, validate); var beforeCallback2; ajv.compileAsync(schema, function (_err, _validate) { beforeCallback2 .should.equal(true); spec(_err, _validate); done(); }); beforeCallback2 = true; }); beforeCallback1 = true; function spec(err, validate) { should.not.exist(err); 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 (done) { var schema = { "id": "http://example.com/parent.json", "properties": { "a": { "$ref": "object.json" } } }; var completedCount = 0; ajv.compileAsync(schema, spec); ajv.compileAsync(schema, spec); ajv.compileAsync(schema, spec); function spec(err, validate) { should.not.exist(err); validate .should.be.a('function'); validate({ a: { b: 2 } }) .should.equal(true); validate({ a: { b: 1 } }) .should.equal(false); completed(); } function completed() { completedCount++; if (completedCount == 3) { should.equal(loadCallCount, 2); done(); } } }); 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, function (err, validate) { should.exist(err); should.not.exist(validate); 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, function (err, validate) { should.exist(err); should.not.exist(validate); 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, function (err, validate) { should.exist(err); should.not.exist(validate); 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, function (err, validate) { should.exist(err); should.not.exist(validate); done(); }); function badLoadSchema(ref, callback) { setTimeout(function() { callback(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, function (err, validate) { should.exist(err); should.not.exist(validate); done(); }); function badCompile(/* schema */) { throw new Error('cant compile keyword schema'); } }); }); function loadSchema(uri, callback) { loadCallCount++; setTimeout(function() { if (SCHEMAS[uri]) return callback(null, SCHEMAS[uri]); callback(new Error('404')); }, 10); } });