2016-01-25 01:55:19 +03:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var Ajv = require('./ajv')
|
2016-01-26 01:05:15 +03:00
|
|
|
, should = require('./chai').should()
|
2016-01-27 02:56:38 +03:00
|
|
|
, co = require('co')
|
|
|
|
, Promise = require('bluebird')
|
|
|
|
, util = require('../lib/compile/util');
|
|
|
|
|
|
|
|
Promise.config({ warnings: false });
|
|
|
|
|
|
|
|
var g = typeof global == 'object' ? global :
|
|
|
|
typeof window == 'object' ? window : this;
|
2016-01-25 01:55:19 +03:00
|
|
|
|
2016-01-27 02:56:38 +03:00
|
|
|
g.Promise = g.Promise || Promise;
|
2016-01-25 01:55:19 +03:00
|
|
|
|
2016-01-27 02:56:38 +03:00
|
|
|
|
|
|
|
describe('async schemas, formats and keywords', function() {
|
|
|
|
var ajv, instances;
|
2016-01-25 01:55:19 +03:00
|
|
|
|
|
|
|
beforeEach(function () {
|
2016-01-27 02:56:38 +03:00
|
|
|
getInstances();
|
|
|
|
ajv = instances[0];
|
2016-01-25 01:55:19 +03:00
|
|
|
});
|
|
|
|
|
2016-01-27 02:56:38 +03:00
|
|
|
function getInstances(opts) {
|
|
|
|
opts = opts || {};
|
|
|
|
var firstTime = instances === undefined;
|
|
|
|
instances = [];
|
|
|
|
[
|
|
|
|
{},
|
|
|
|
{ allErrors: true },
|
|
|
|
{ async: 'generators' },
|
|
|
|
{ async: 'generators', allErrors: true },
|
|
|
|
{ async: 'regenerator' },
|
|
|
|
{ async: 'regenerator', allErrors: true }
|
|
|
|
].forEach(function (_opts) {
|
|
|
|
util.copy(opts, _opts);
|
|
|
|
var ajv = getAjv(_opts);
|
|
|
|
if (ajv) instances.push(ajv);
|
|
|
|
});
|
|
|
|
if (firstTime) console.log('Testing', instances.length, 'ajv instances');
|
|
|
|
}
|
|
|
|
|
|
|
|
function getAjv(opts){
|
|
|
|
try { return Ajv(opts); } catch(e) {}
|
|
|
|
}
|
|
|
|
|
2016-01-25 01:55:19 +03:00
|
|
|
describe('async schemas without async elements', function() {
|
|
|
|
it('should pass result via callback in setTimeout', function() {
|
|
|
|
var schema = {
|
|
|
|
$async: true,
|
|
|
|
type: 'string',
|
|
|
|
maxLength: 3
|
|
|
|
};
|
|
|
|
|
2016-01-27 02:56:38 +03:00
|
|
|
return Promise.map(instances, test);
|
2016-01-25 01:55:19 +03:00
|
|
|
|
|
|
|
function test(ajv) {
|
|
|
|
var validate = ajv.compile(schema);
|
|
|
|
|
|
|
|
return Promise.all([
|
|
|
|
shouldBeValid( co(validate('abc')) ),
|
|
|
|
shouldBeInvalid( co(validate('abcd')) ),
|
|
|
|
shouldBeInvalid( co(validate(1)) )
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should fail compilation if async schema is inside sync schema', function() {
|
|
|
|
var schema = {
|
|
|
|
properties: {
|
|
|
|
foo: {
|
|
|
|
$async: true,
|
|
|
|
type: 'string',
|
|
|
|
maxLength: 3
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-01-26 01:05:15 +03:00
|
|
|
shouldThrowFunc('async schema in sync schema', function() {
|
2016-01-25 01:55:19 +03:00
|
|
|
ajv.compile(schema);
|
|
|
|
});
|
|
|
|
|
|
|
|
schema.$async = true;
|
|
|
|
|
|
|
|
ajv.compile(schema);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('async formats', function() {
|
2016-01-26 01:05:15 +03:00
|
|
|
beforeEach(addFormatEnglishWord);
|
|
|
|
|
|
|
|
function addFormatEnglishWord() {
|
2016-01-27 02:56:38 +03:00
|
|
|
instances.forEach(function (ajv) {
|
2016-01-26 01:05:15 +03:00
|
|
|
ajv.addFormat('english_word', {
|
|
|
|
async: true,
|
|
|
|
validate: checkWordOnServer
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-01-25 01:55:19 +03:00
|
|
|
it('should return promise that resolves as true or rejects with array of errors', function() {
|
|
|
|
var schema = {
|
|
|
|
$async: true,
|
|
|
|
type: 'string',
|
|
|
|
format: 'english_word',
|
|
|
|
minimum: 5
|
|
|
|
};
|
|
|
|
|
2016-01-27 02:56:38 +03:00
|
|
|
return Promise.map(instances, test);
|
2016-01-25 01:55:19 +03:00
|
|
|
|
|
|
|
function test(ajv) {
|
|
|
|
var validate = ajv.compile(schema);
|
|
|
|
|
|
|
|
return Promise.all([
|
|
|
|
shouldBeValid( co(validate('tomorrow')) ),
|
|
|
|
shouldBeInvalid( co(validate('manana')) ),
|
2016-01-26 01:05:15 +03:00
|
|
|
shouldBeInvalid( co(validate(1)) ),
|
|
|
|
shouldThrow( co(validate('today')), 'unknown word' )
|
2016-01-25 01:55:19 +03:00
|
|
|
]);
|
|
|
|
}
|
2016-01-26 01:05:15 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should fail compilation if async format is inside sync schema or subschema', function() {
|
2016-01-27 02:56:38 +03:00
|
|
|
instances.forEach(test);
|
2016-01-26 01:05:15 +03:00
|
|
|
|
|
|
|
function test(ajv) {
|
|
|
|
var schema1 = {
|
|
|
|
type: 'string',
|
|
|
|
format: 'english_word',
|
|
|
|
minimum: 5
|
|
|
|
};
|
|
|
|
|
|
|
|
shouldThrowFunc('async format in sync schema', function() {
|
|
|
|
ajv.compile(schema1);
|
|
|
|
})
|
|
|
|
schema1.$async = true;
|
|
|
|
ajv.compile(schema1);
|
|
|
|
|
|
|
|
|
|
|
|
var schema2 = {
|
|
|
|
$async: true,
|
|
|
|
properties: {
|
|
|
|
foo: {
|
|
|
|
type: 'string',
|
|
|
|
format: 'english_word',
|
|
|
|
minimum: 5
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
shouldThrowFunc('async format in sync schema', function() {
|
|
|
|
ajv.compile(schema2);
|
|
|
|
})
|
|
|
|
schema2.properties.foo.$async = true;
|
|
|
|
ajv.compile(schema2);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should support async formats when $data ref resolves to async format name', function() {
|
2016-01-27 02:56:38 +03:00
|
|
|
getInstances({ v5: true });
|
2016-01-26 01:05:15 +03:00
|
|
|
addFormatEnglishWord();
|
|
|
|
|
|
|
|
var schema = {
|
|
|
|
$async: true,
|
|
|
|
additionalProperties: {
|
|
|
|
type: 'string',
|
|
|
|
format: { $data: '0#' }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-01-27 02:56:38 +03:00
|
|
|
return Promise.map(instances, test);
|
2016-01-25 01:55:19 +03:00
|
|
|
|
2016-01-26 01:05:15 +03:00
|
|
|
function test(ajv) {
|
|
|
|
var validate = ajv.compile(schema);
|
|
|
|
|
|
|
|
return Promise.all([
|
|
|
|
shouldBeValid( co(validate({ english_word: 'tomorrow' })) ),
|
|
|
|
shouldBeInvalid( co(validate({ english_word: 'manana' })) ),
|
|
|
|
shouldBeInvalid( co(validate({ english_word: 1 })) ),
|
|
|
|
shouldThrow( co(validate({ english_word: 'today' })), 'unknown word' ),
|
|
|
|
|
|
|
|
shouldBeValid( co(validate({ date: '2016-01-25' })) ),
|
|
|
|
shouldBeInvalid( co(validate({ date: '01/25/2016' })) ),
|
|
|
|
shouldBeInvalid( co(validate({ date: 1 })) ),
|
|
|
|
]);
|
2016-01-25 01:55:19 +03:00
|
|
|
}
|
|
|
|
});
|
2016-01-26 01:05:15 +03:00
|
|
|
|
|
|
|
|
|
|
|
function checkWordOnServer(str) {
|
|
|
|
return str == 'tomorrow' ? Promise.resolve(true)
|
|
|
|
: str == 'manana' ? Promise.resolve(false)
|
|
|
|
: Promise.reject(new Error('unknown word'));
|
|
|
|
}
|
2016-01-25 01:55:19 +03:00
|
|
|
});
|
2016-01-26 02:57:16 +03:00
|
|
|
|
|
|
|
|
|
|
|
describe('async custom keywords', function() {
|
|
|
|
beforeEach(function() {
|
2016-01-27 02:56:38 +03:00
|
|
|
instances.forEach(function (ajv) {
|
2016-01-26 02:57:16 +03:00
|
|
|
ajv.addKeyword('idExists', {
|
|
|
|
async: true,
|
|
|
|
type: 'number',
|
|
|
|
validate: checkIdExists
|
|
|
|
});
|
|
|
|
|
|
|
|
ajv.addKeyword('idExistsCompiled', {
|
|
|
|
async: true,
|
|
|
|
type: 'number',
|
|
|
|
compile: compileCheckIdExists
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should validate custom keyword that returns promise', function() {
|
|
|
|
var schema1 = {
|
|
|
|
$async: true,
|
|
|
|
properties: {
|
|
|
|
userId: {
|
|
|
|
$async: true,
|
|
|
|
type: 'integer',
|
|
|
|
idExists: { table: 'users' }
|
|
|
|
},
|
|
|
|
postId: {
|
|
|
|
$async: true,
|
|
|
|
type: 'integer',
|
|
|
|
idExists: { table: 'posts' }
|
|
|
|
},
|
|
|
|
categoryId: {
|
|
|
|
$async: true,
|
|
|
|
description: 'will throw if present, no such table',
|
|
|
|
type: 'integer',
|
|
|
|
idExists: { table: 'categories' }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var schema2 = {
|
|
|
|
$async: true,
|
|
|
|
properties: {
|
|
|
|
userId: {
|
|
|
|
$async: true,
|
|
|
|
type: 'integer',
|
|
|
|
idExistsCompiled: { table: 'users' }
|
|
|
|
},
|
|
|
|
postId: {
|
|
|
|
$async: true,
|
|
|
|
type: 'integer',
|
|
|
|
idExistsCompiled: { table: 'posts' }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return Promise.all([
|
2016-01-27 02:56:38 +03:00
|
|
|
test(instances, schema1, true),
|
|
|
|
test(instances, schema2)
|
2016-01-26 02:57:16 +03:00
|
|
|
]);
|
|
|
|
|
2016-01-27 02:56:38 +03:00
|
|
|
function test(instances, schema, checkThrow) {
|
|
|
|
return Promise.map(instances, function (ajv) {
|
|
|
|
var validate = ajv.compile(schema);
|
|
|
|
|
|
|
|
return Promise.all([
|
|
|
|
shouldBeValid( co(validate({ userId: 1, postId: 21 })) ),
|
|
|
|
shouldBeValid( co(validate({ userId: 5, postId: 25 })) ),
|
|
|
|
shouldBeInvalid( co(validate({ userId: 5, postId: 10 })) ), // no post
|
|
|
|
shouldBeInvalid( co(validate({ userId: 9, postId: 25 })) ), // no user
|
|
|
|
checkThrow
|
|
|
|
? shouldThrow( co(validate({ postId: 25, categoryId: 1 })), 'no such table' )
|
|
|
|
: undefined
|
|
|
|
]);
|
|
|
|
});
|
2016-01-26 02:57:16 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function checkIdExists(schema, data) {
|
|
|
|
switch (schema.table) {
|
|
|
|
case 'users': return check([1, 5, 8]);
|
|
|
|
case 'posts': return check([21, 25, 28]);
|
|
|
|
default: throw new Error('no such table');
|
|
|
|
}
|
|
|
|
|
|
|
|
function check(IDs) {
|
|
|
|
return Promise.resolve(IDs.indexOf(data) >= 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function compileCheckIdExists(schema) {
|
|
|
|
switch (schema.table) {
|
|
|
|
case 'users': return compileCheck([1, 5, 8]);
|
|
|
|
case 'posts': return compileCheck([21, 25, 28]);
|
|
|
|
default: throw new Error('no such table');
|
|
|
|
}
|
|
|
|
|
|
|
|
function compileCheck(IDs) {
|
|
|
|
return function (data) {
|
|
|
|
return Promise.resolve(IDs.indexOf(data) >= 0);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2016-01-25 01:55:19 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2016-01-26 01:05:15 +03:00
|
|
|
function shouldThrowFunc(message, func) {
|
|
|
|
var err;
|
|
|
|
should.throw(function() {
|
|
|
|
try { func(); }
|
|
|
|
catch(e) { err = e; throw e; }
|
|
|
|
});
|
|
|
|
|
|
|
|
err.message .should.equal(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-01-25 01:55:19 +03:00
|
|
|
function shouldBeValid(p) {
|
|
|
|
return p.then(function (valid) {
|
|
|
|
valid .should.equal(true);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var SHOULD_BE_INVALID = 'test: should be invalid';
|
|
|
|
function shouldBeInvalid(p) {
|
2016-01-26 01:05:15 +03:00
|
|
|
return checkNotValid(p)
|
|
|
|
.then(function (err) {
|
|
|
|
err.errors .should.be.an('array');
|
|
|
|
err.validation .should.equal(true);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function shouldThrow(p, exception) {
|
|
|
|
return checkNotValid(p)
|
|
|
|
.then(function (err) {
|
|
|
|
err.message .should.equal(exception);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function checkNotValid(p) {
|
2016-01-25 01:55:19 +03:00
|
|
|
return p.then(function (valid) {
|
|
|
|
throw new Error(SHOULD_BE_INVALID);
|
|
|
|
})
|
|
|
|
.catch(function (err) {
|
|
|
|
err. should.be.instanceof(Error);
|
2016-01-26 01:05:15 +03:00
|
|
|
if (err.message == SHOULD_BE_INVALID) throw err;
|
|
|
|
return err;
|
|
|
|
});
|
2016-01-25 01:55:19 +03:00
|
|
|
}
|