2016-01-25 01:55:19 +03:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
try { eval("(function*(){})()"); var hasGenerators = true; } catch(e){}
|
|
|
|
|
|
|
|
var Ajv = require('./ajv')
|
2016-01-26 01:05:15 +03:00
|
|
|
, should = require('./chai').should()
|
|
|
|
, co = require('co');
|
2016-01-25 01:55:19 +03:00
|
|
|
|
|
|
|
|
2016-01-26 01:05:15 +03:00
|
|
|
(hasGenerators ? describe : describe.skip)
|
2016-01-25 01:55:19 +03:00
|
|
|
('async schemas, formats and keywords', function() {
|
|
|
|
var ajv, fullAjv;
|
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
ajv = Ajv();
|
|
|
|
fullAjv = Ajv({ allErrors: true });
|
|
|
|
});
|
|
|
|
|
|
|
|
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.all([
|
|
|
|
test(ajv),
|
|
|
|
test(fullAjv)
|
|
|
|
]);
|
|
|
|
|
|
|
|
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() {
|
|
|
|
[ajv, fullAjv].forEach(function (ajv) {
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
return Promise.all([
|
|
|
|
test(ajv),
|
|
|
|
test(fullAjv)
|
|
|
|
]);
|
|
|
|
|
|
|
|
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() {
|
|
|
|
test(ajv);
|
|
|
|
test(fullAjv);
|
|
|
|
|
|
|
|
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-26 02:57:16 +03:00
|
|
|
ajv = Ajv({ v5: true });
|
|
|
|
fullAjv = Ajv({ v5: true, allErrors: true });
|
2016-01-26 01:05:15 +03:00
|
|
|
addFormatEnglishWord();
|
|
|
|
|
|
|
|
var schema = {
|
|
|
|
$async: true,
|
|
|
|
additionalProperties: {
|
|
|
|
type: 'string',
|
|
|
|
format: { $data: '0#' }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return Promise.all([
|
|
|
|
test(ajv),
|
|
|
|
test(fullAjv)
|
|
|
|
]);
|
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() {
|
|
|
|
[ajv, fullAjv].forEach(function (ajv) {
|
|
|
|
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(ajv, schema1, true),
|
|
|
|
test(ajv, schema2),
|
|
|
|
test(fullAjv, schema1, true),
|
|
|
|
test(fullAjv, schema2)
|
|
|
|
]);
|
|
|
|
|
|
|
|
function test(ajv, schema, checkThrow) {
|
|
|
|
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
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|