ajv/spec/async_validate.spec.js

358 lines
8.7 KiB
JavaScript
Raw Normal View History

'use strict';
var Ajv = require('./ajv')
2016-01-26 01:05:15 +03:00
, should = require('./chai').should()
, 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;
g.Promise = g.Promise || Promise;
describe('async schemas, formats and keywords', function() {
var ajv, instances;
beforeEach(function () {
getInstances();
ajv = instances[0];
});
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) {}
}
describe('async schemas without async elements', function() {
it('should pass result via callback in setTimeout', function() {
var schema = {
$async: true,
type: 'string',
maxLength: 3
};
return Promise.map(instances, test);
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() {
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() {
instances.forEach(function (ajv) {
2016-01-26 01:05:15 +03:00
ajv.addFormat('english_word', {
async: true,
validate: checkWordOnServer
});
});
}
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
};
return Promise.map(instances, test);
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-26 01:05:15 +03:00
});
it('should fail compilation if async format is inside sync schema or subschema', function() {
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() {
getInstances({ v5: true });
2016-01-26 01:05:15 +03:00
addFormatEnglishWord();
var schema = {
$async: true,
additionalProperties: {
type: 'string',
format: { $data: '0#' }
}
};
return Promise.map(instances, test);
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-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-26 02:57:16 +03:00
describe('async custom keywords', function() {
beforeEach(function() {
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([
test(instances, schema1, true),
test(instances, schema2)
2016-01-26 02:57:16 +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-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);
}
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) {
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;
});
}