不要怂,就是干,撸起袖子干!

Commit 341b0172 by Jan Aagaard Meier

Merge pull request #3757 from BridgeAR/fix-isip

Fix isIP: closes #2972, move integration tests to unit tests and…
2 parents a5275271 2a0f8e37
# next
- [BUG] Fix an issue with the build in isIP validator returning false negatives [#3756](https://github.com/sequelize/sequelize/pull/3756)
# 3.0.1 # 3.0.1
- [FIXED] `include.attributes = []` will no longer force the inclusion of the primary key, making it possible to write aggregates with includes. - [FIXED] `include.attributes = []` will no longer force the inclusion of the primary key, making it possible to write aggregates with includes.
......
...@@ -89,7 +89,7 @@ var Hooks = { ...@@ -89,7 +89,7 @@ var Hooks = {
runHooks: function(hooks) { runHooks: function(hooks) {
var self = this var self = this
, fn , fn
, fnArgs = Array.prototype.slice.call(arguments, 1) , fnArgs = Utils.sliceArgs(arguments, 1)
, hookType; , hookType;
if (typeof fnArgs[fnArgs.length - 1] === 'function') { if (typeof fnArgs[fnArgs.length - 1] === 'function') {
......
...@@ -343,11 +343,16 @@ InstanceValidator.prototype._invokeBuiltinValidator = Promise.method(function(va ...@@ -343,11 +343,16 @@ InstanceValidator.prototype._invokeBuiltinValidator = Promise.method(function(va
'Validation ' + validatorType + ' failed'; 'Validation ' + validatorType + ' failed';
if (!Array.isArray(validatorArgs)) { if (!Array.isArray(validatorArgs)) {
validatorArgs = [validatorArgs]; if (validatorType === 'isImmutable') {
validatorArgs = [validatorArgs, field];
} else if (validatorType === 'isIP') {
validatorArgs = [];
} else {
validatorArgs = [validatorArgs];
}
} else { } else {
validatorArgs = validatorArgs.slice(0); validatorArgs = validatorArgs.slice(0);
} }
validatorArgs.push(field);
if (!Validator[validatorType].apply(Validator, [value].concat(validatorArgs))) { if (!Validator[validatorType].apply(Validator, [value].concat(validatorArgs))) {
throw errorMessage; throw errorMessage;
......
...@@ -936,7 +936,7 @@ module.exports = (function() { ...@@ -936,7 +936,7 @@ module.exports = (function() {
* @return {Sequelize.fn} * @return {Sequelize.fn}
*/ */
Sequelize.fn = Sequelize.prototype.fn = function(fn) { Sequelize.fn = Sequelize.prototype.fn = function(fn) {
return new Utils.fn(fn, Array.prototype.slice.call(arguments, 1)); return new Utils.fn(fn, Utils.sliceArgs(arguments, 1));
}; };
/** /**
...@@ -989,7 +989,7 @@ module.exports = (function() { ...@@ -989,7 +989,7 @@ module.exports = (function() {
* @return {Sequelize.and} * @return {Sequelize.and}
*/ */
Sequelize.and = Sequelize.prototype.and = function() { Sequelize.and = Sequelize.prototype.and = function() {
return new Utils.and(Array.prototype.slice.call(arguments)); return new Utils.and(Utils.sliceArgs(arguments));
}; };
/** /**
...@@ -1002,7 +1002,7 @@ module.exports = (function() { ...@@ -1002,7 +1002,7 @@ module.exports = (function() {
* @return {Sequelize.or} * @return {Sequelize.or}
*/ */
Sequelize.or = Sequelize.prototype.or = function() { Sequelize.or = Sequelize.prototype.or = function() {
return new Utils.or(Array.prototype.slice.call(arguments)); return new Utils.or(Utils.sliceArgs(arguments));
}; };
/** /**
...@@ -1136,7 +1136,7 @@ module.exports = (function() { ...@@ -1136,7 +1136,7 @@ module.exports = (function() {
}; };
Sequelize.prototype.log = function() { Sequelize.prototype.log = function() {
var args = [].slice.call(arguments) var args = Utils.sliceArgs(arguments)
, last = Utils._.last(args) , last = Utils._.last(args)
, options; , options;
......
...@@ -304,6 +304,15 @@ var Utils = module.exports = { ...@@ -304,6 +304,15 @@ var Utils = module.exports = {
return errStack; return errStack;
}, },
sliceArgs: function (args, begin) {
begin = begin || 0;
var tmp = new Array(args.length - begin);
for (var i = begin; i < args.length; ++i) {
tmp[i - begin] = args[i];
}
return tmp;
},
now: function(dialect) { now: function(dialect) {
var now = new Date(); var now = new Date();
if (dialect !== 'postgres') now.setMilliseconds(0); if (dialect !== 'postgres') now.setMilliseconds(0);
...@@ -339,7 +348,7 @@ var Utils = module.exports = { ...@@ -339,7 +348,7 @@ var Utils = module.exports = {
col: function(col) { col: function(col) {
if (arguments.length > 1) { if (arguments.length > 1) {
col = Array.prototype.slice.call(arguments); col = this.sliceArgs(arguments);
} }
this.col = col; this.col = col;
}, },
......
...@@ -9,924 +9,685 @@ var chai = require('chai') ...@@ -9,924 +9,685 @@ var chai = require('chai')
, config = require(__dirname + '/../config/config'); , config = require(__dirname + '/../config/config');
describe(Support.getTestDialectTeaser('InstanceValidator'), function() { describe(Support.getTestDialectTeaser('InstanceValidator'), function() {
describe('validations', function() { describe('#update', function() {
var checks = { it('should allow us to update specific columns without tripping the validations', function() {
is: { var User = this.sequelize.define('model', {
spec: { args: ['[a-z]', 'i'] }, username: Sequelize.STRING,
fail: '0', email: {
pass: 'a' type: Sequelize.STRING,
}, allowNull: false,
not: { validate: {
spec: { args: ['[a-z]', 'i'] }, isEmail: {
fail: 'a', msg: 'You must enter a valid email address'
pass: '0' }
},
isEmail: {
fail: 'a',
pass: 'abc@abc.com'
}
, isUrl: {
fail: 'abc',
pass: 'http://abc.com'
}
, isIP: {
fail: 'abc',
pass: '129.89.23.1'
}
, isIPv6: {
fail: '1111:2222:3333::5555:',
pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156'
}
, isAlpha: {
fail: '012',
pass: 'abc'
}
, isAlphanumeric: {
fail: '_abc019',
pass: 'abc019'
}
, isNumeric: {
fail: 'abc',
pass: '019'
}
, isInt: {
fail: '9.2',
pass: '-9'
}
, isLowercase: {
fail: 'AB',
pass: 'ab'
}
, isUppercase: {
fail: 'ab',
pass: 'AB'
}
, isDecimal: {
fail: 'a',
pass: '0.2'
}
, isFloat: {
fail: 'a',
pass: '9.2'
}
, isNull: {
fail: 0,
pass: null
}
, notEmpty: {
fail: ' ',
pass: 'a'
}
, equals: {
spec: { args: 'bla bla bla' },
fail: 'bla',
pass: 'bla bla bla'
}
, contains: {
spec: { args: 'bla' },
fail: 'la',
pass: '0bla23'
}
, notContains: {
spec: { args: 'bla' },
fail: '0bla23',
pass: 'la'
}
, regex: {
spec: { args: ['[a-z]', 'i'] },
fail: '0',
pass: 'a'
}
, notRegex: {
spec: { args: ['[a-z]', 'i'] },
fail: 'a',
pass: '0'
}
, len: {
spec: { args: [2, 4] },
fail: ['1', '12345'],
pass: ['12', '123', '1234'],
raw: true
}
, len$: {
spec: [2, 4],
fail: ['1', '12345'],
pass: ['12', '123', '1234'],
raw: true
}
, isUUID: {
spec: { args: 4 },
fail: 'f47ac10b-58cc-3372-a567-0e02b2c3d479',
pass: 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
}
, isDate: {
fail: 'not a date',
pass: '2011-02-04'
}
, isAfter: {
spec: { args: '2011-11-05' },
fail: '2011-11-04',
pass: '2011-11-06'
}
, isBefore: {
spec: { args: '2011-11-05' },
fail: '2011-11-06',
pass: '2011-11-04'
}
, isIn: {
spec: { args: 'abcdefghijk' },
fail: 'ghik',
pass: 'ghij'
}
, notIn: {
spec: { args: 'abcdefghijk' },
fail: 'ghij',
pass: 'ghik'
}
, max: {
spec: { args: 23 },
fail: '24',
pass: '23'
}
, max$: {
spec: 23,
fail: '24',
pass: '23'
}
, min: {
spec: { args: 23 },
fail: '22',
pass: '23'
}
, min$: {
spec: 23,
fail: '22',
pass: '23'
}
, isCreditCard: {
fail: '401288888888188f',
pass: '4012888888881881'
}
};
var applyFailTest = function applyFailTest(validatorDetails, i, validator) {
var failingValue = validatorDetails.fail[i];
it('correctly specifies an instance as invalid using a value of "' + failingValue + '" for the validation "' + validator + '"', function() {
var validations = {}
, message = validator + '(' + failingValue + ')';
if (validatorDetails.hasOwnProperty('spec')) {
validations[validator] = validatorDetails.spec;
} else {
validations[validator] = {};
} }
}
});
validations[validator].msg = message; return User.sync({ force: true }).then(function() {
return User.create({ username: 'bob', email: 'hello@world.com' }).then(function(user) {
var UserFail = this.sequelize.define('User' + config.rand(), { return User
name: { .update({ username: 'toni' }, { where: {id: user.id }})
type: Sequelize.STRING, .then(function() {
validate: validations return User.findById(1).then(function(user) {
} expect(user.username).to.equal('toni');
}); });
});
});
});
});
var failingUser = UserFail.build({ name: failingValue }); it('should be able to emit an error upon updating when a validation has failed from an instance', function() {
var Model = this.sequelize.define('model', {
name: {
type: Sequelize.STRING,
allowNull: false,
validate: {
notEmpty: true // don't allow empty strings
}
}
});
return failingUser.validate().then(function(_errors) { return Model.sync({ force: true }).then(function() {
expect(_errors).not.to.be.null; return Model.create({name: 'World'}).then(function(model) {
expect(_errors).to.be.an.instanceOf(Error); return model.updateAttributes({name: ''}).catch(function(err) {
expect(_errors.get('name')[0].message).to.equal(message); expect(err).to.be.an.instanceOf(Error);
expect(err.get('name')[0].message).to.equal('Validation notEmpty failed');
}); });
}); });
} });
, applyPassTest = function applyPassTest(validatorDetails, j, validator) { });
var succeedingValue = validatorDetails.pass[j];
it('correctly specifies an instance as valid using a value of "' + succeedingValue + '" for the validation "' + validator + '"', function() {
var validations = {};
if (validatorDetails.hasOwnProperty('spec')) {
validations[validator] = validatorDetails.spec;
} else {
validations[validator] = {};
}
validations[validator].msg = validator + '(' + succeedingValue + ')'; it('should be able to emit an error upon updating when a validation has failed from the factory', function() {
var Model = this.sequelize.define('model', {
name: {
type: Sequelize.STRING,
allowNull: false,
validate: {
notEmpty: true // don't allow empty strings
}
}
});
var UserSuccess = this.sequelize.define('User' + config.rand(), { return Model.sync({ force: true }).then(function() {
name: { return Model.create({name: 'World'}).then(function() {
type: Sequelize.STRING, return Model.update({name: ''}, {where: {id: 1}}).catch(function(err) {
validate: validations expect(err).to.be.an.instanceOf(Error);
} expect(err.get('name')[0].message).to.equal('Validation notEmpty failed');
});
var successfulUser = UserSuccess.build({ name: succeedingValue });
return successfulUser.validate().then(function(errors) {
expect(errors).to.be.undefined;
}).catch(function(err) {
expect(err).to.deep.equal({});
});
}); });
}; });
});
for (var validator in checks) { });
if (checks.hasOwnProperty(validator)) {
validator = validator.replace(/\$$/, '');
var validatorDetails = checks[validator];
if (!validatorDetails.hasOwnProperty('raw')) {
validatorDetails.fail = Array.isArray(validatorDetails.fail) ? validatorDetails.fail : [validatorDetails.fail];
validatorDetails.pass = Array.isArray(validatorDetails.pass) ? validatorDetails.pass : [validatorDetails.pass];
}
for (var i = 0; i < validatorDetails.fail.length; i++) { it('should enforce a unque constraint', function() {
applyFailTest(validatorDetails, i, validator); var Model = this.sequelize.define('model', {
} uniqueName: { type: Sequelize.STRING, unique: true }
});
var records = [
{ uniqueName: 'unique name one' },
{ uniqueName: 'unique name two' }
];
return Model.sync({ force: true })
.then(function() {
return Model.create(records[0]);
}).then(function(instance) {
expect(instance).to.be.ok;
return Model.create(records[1]);
}).then(function(instance) {
expect(instance).to.be.ok;
return expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected;
}).then(function(err) {
expect(err).to.be.an.instanceOf(Error);
expect(err.errors).to.have.length(1);
expect(err.errors[0].path).to.include('uniqueName');
expect(err.errors[0].message).to.include('must be unique');
});
});
});
for (var j = 0; j < validatorDetails.pass.length; j++) { describe('#create', function() {
applyPassTest(validatorDetails, j, validator); describe('generic', function() {
} beforeEach(function() {
} var self = this;
}
describe('#update', function() { var Project = this.sequelize.define('Project', {
it('should allow us to update specific columns without tripping the validations', function() { name: {
var User = this.sequelize.define('model', {
username: Sequelize.STRING,
email: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false, allowNull: false,
defaultValue: 'unknown',
validate: { validate: {
isEmail: { isIn: [['unknown', 'hello', 'test']]
msg: 'You must enter a valid email address'
}
} }
} }
}); });
return User.sync({ force: true }).then(function() { var Task = this.sequelize.define('Task', {
return User.create({ username: 'bob', email: 'hello@world.com' }).then(function(user) { something: Sequelize.INTEGER
return User });
.update({ username: 'toni' }, { where: {id: user.id }})
.then(function() { Project.hasOne(Task);
return User.findById(1).then(function(user) { Task.belongsTo(Project);
expect(user.username).to.equal('toni');
}); return this.sequelize.sync({ force: true }).then(function() {
}); self.Project = Project;
}); self.Task = Task;
}); });
}); });
it('should be able to emit an error upon updating when a validation has failed from an instance', function() { it('correctly throws an error using create method ', function() {
var Model = this.sequelize.define('model', { return this.Project.create({name: 'nope'}).catch(function(err) {
name: { expect(err).to.have.ownProperty('name');
type: Sequelize.STRING,
allowNull: false,
validate: {
notEmpty: true // don't allow empty strings
}
}
}); });
});
return Model.sync({ force: true }).then(function() { it('correctly validates using create method ', function() {
return Model.create({name: 'World'}).then(function(model) { var self = this;
return model.updateAttributes({name: ''}).catch(function(err) { return this.Project.create({}).then(function(project) {
expect(err).to.be.an.instanceOf(Error); return self.Task.create({something: 1}).then(function(task) {
expect(err.get('name')[0].message).to.equal('Validation notEmpty failed'); return project.setTask(task).then(function(task) {
expect(task.ProjectId).to.not.be.null;
return task.setProject(project).then(function(project) {
expect(project.ProjectId).to.not.be.null;
});
}); });
}); });
}); });
}); });
});
it('should be able to emit an error upon updating when a validation has failed from the factory', function() { describe('explicitly validating primary/auto incremented columns', function() {
var Model = this.sequelize.define('model', { it('should emit an error when we try to enter in a string for the id key without validation arguments', function() {
name: { var User = this.sequelize.define('UserId', {
type: Sequelize.STRING, id: {
allowNull: false, type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
validate: { validate: {
notEmpty: true // don't allow empty strings isInt: true
} }
} }
}); });
return Model.sync({ force: true }).then(function() { return User.sync({ force: true }).then(function() {
return Model.create({name: 'World'}).then(function() { return User.create({id: 'helloworld'}).catch(function(err) {
return Model.update({name: ''}, {where: {id: 1}}).catch(function(err) {
expect(err).to.be.an.instanceOf(Error);
expect(err.get('name')[0].message).to.equal('Validation notEmpty failed');
});
});
});
});
it('should enforce a unque constraint', function() {
var Model = this.sequelize.define('model', {
uniqueName: { type: Sequelize.STRING, unique: true }
});
var records = [
{ uniqueName: 'unique name one' },
{ uniqueName: 'unique name two' }
];
return Model.sync({ force: true })
.then(function() {
return Model.create(records[0]);
}).then(function(instance) {
expect(instance).to.be.ok;
return Model.create(records[1]);
}).then(function(instance) {
expect(instance).to.be.ok;
return expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected;
}).then(function(err) {
expect(err).to.be.an.instanceOf(Error); expect(err).to.be.an.instanceOf(Error);
expect(err.errors).to.have.length(1); expect(err.get('id')[0].message).to.equal('Validation isInt failed');
expect(err.errors[0].path).to.include('uniqueName');
expect(err.errors[0].message).to.include('must be unique');
}); });
});
}); });
});
describe('#create', function() { it('should emit an error when we try to enter in a string for an auto increment key (not named id)', function() {
describe('generic', function() { var User = this.sequelize.define('UserId', {
beforeEach(function() { username: {
var self = this; type: Sequelize.INTEGER,
autoIncrement: true,
var Project = this.sequelize.define('Project', { primaryKey: true,
name: { validate: {
type: Sequelize.STRING, isInt: { args: true, msg: 'Username must be an integer!' }
allowNull: false,
defaultValue: 'unknown',
validate: {
isIn: [['unknown', 'hello', 'test']]
}
} }
}); }
var Task = this.sequelize.define('Task', {
something: Sequelize.INTEGER
});
Project.hasOne(Task);
Task.belongsTo(Project);
return this.sequelize.sync({ force: true }).then(function() {
self.Project = Project;
self.Task = Task;
});
});
it('correctly throws an error using create method ', function() {
return this.Project.create({name: 'nope'}).catch(function(err) {
expect(err).to.have.ownProperty('name');
});
}); });
it('correctly validates using create method ', function() { return User.sync({ force: true }).then(function() {
var self = this; return User.create({username: 'helloworldhelloworld'}).catch(function(err) {
return this.Project.create({}).then(function(project) { expect(err).to.be.an.instanceOf(Error);
return self.Task.create({something: 1}).then(function(task) { expect(err.get('username')[0].message).to.equal('Username must be an integer!');
return project.setTask(task).then(function(task) {
expect(task.ProjectId).to.not.be.null;
return task.setProject(project).then(function(project) {
expect(project.ProjectId).to.not.be.null;
});
});
});
}); });
}); });
}); });
describe('explicitly validating primary/auto incremented columns', function() { describe('primaryKey with the name as id with arguments for it\'s validatio', function() {
it('should emit an error when we try to enter in a string for the id key without validation arguments', function() { beforeEach(function() {
var User = this.sequelize.define('UserId', { this.User = this.sequelize.define('UserId', {
id: { id: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
autoIncrement: true, autoIncrement: true,
primaryKey: true, primaryKey: true,
validate: { validate: {
isInt: true isInt: { args: true, msg: 'ID must be an integer!' }
} }
} }
}); });
return User.sync({ force: true }).then(function() { return this.User.sync({ force: true });
return User.create({id: 'helloworld'}).catch(function(err) {
expect(err).to.be.an.instanceOf(Error);
expect(err.get('id')[0].message).to.equal('Validation isInt failed');
});
});
}); });
it('should emit an error when we try to enter in a string for an auto increment key (not named id)', function() { it('should emit an error when we try to enter in a string for the id key with validation arguments', function() {
var User = this.sequelize.define('UserId', { return this.User.create({id: 'helloworld'}).catch(function(err) {
username: { expect(err).to.be.an.instanceOf(Error);
type: Sequelize.INTEGER, expect(err.get('id')[0].message).to.equal('ID must be an integer!');
autoIncrement: true,
primaryKey: true,
validate: {
isInt: { args: true, msg: 'Username must be an integer!' }
}
}
});
return User.sync({ force: true }).then(function() {
return User.create({username: 'helloworldhelloworld'}).catch(function(err) {
expect(err).to.be.an.instanceOf(Error);
expect(err.get('username')[0].message).to.equal('Username must be an integer!');
});
}); });
}); });
describe('primaryKey with the name as id with arguments for it\'s validatio', function() { it('should emit an error when we try to enter in a string for an auto increment key through .build().validate()', function() {
beforeEach(function() { var user = this.User.build({id: 'helloworld'});
this.User = this.sequelize.define('UserId', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
validate: {
isInt: { args: true, msg: 'ID must be an integer!' }
}
}
});
return this.User.sync({ force: true });
});
it('should emit an error when we try to enter in a string for the id key with validation arguments', function() {
return this.User.create({id: 'helloworld'}).catch(function(err) {
expect(err).to.be.an.instanceOf(Error);
expect(err.get('id')[0].message).to.equal('ID must be an integer!');
});
});
it('should emit an error when we try to enter in a string for an auto increment key through .build().validate()', function() {
var user = this.User.build({id: 'helloworld'});
return user.validate().then(function(err) { return user.validate().then(function(err) {
expect(err).to.be.an.instanceOf(Error); expect(err).to.be.an.instanceOf(Error);
expect(err.get('id')[0].message).to.equal('ID must be an integer!'); expect(err.get('id')[0].message).to.equal('ID must be an integer!');
});
}); });
});
it('should emit an error when we try to .save()', function() { it('should emit an error when we try to .save()', function() {
var user = this.User.build({id: 'helloworld'}); var user = this.User.build({id: 'helloworld'});
return user.save().catch(function(err) { return user.save().catch(function(err) {
expect(err).to.be.an.instanceOf(Error); expect(err).to.be.an.instanceOf(Error);
expect(err.get('id')[0].message).to.equal('ID must be an integer!'); expect(err.get('id')[0].message).to.equal('ID must be an integer!');
});
}); });
}); });
}); });
describe('Pass all paths when validating', function() { });
beforeEach(function() { describe('Pass all paths when validating', function() {
var self = this; beforeEach(function() {
var Project = this.sequelize.define('Project', { var self = this;
name: { var Project = this.sequelize.define('Project', {
type: Sequelize.STRING, name: {
allowNull: false, type: Sequelize.STRING,
validate: { allowNull: false,
isIn: [['unknown', 'hello', 'test']] validate: {
} isIn: [['unknown', 'hello', 'test']]
},
creatorName: {
type: Sequelize.STRING,
allowNull: false
},
cost: {
type: Sequelize.INTEGER,
allowNull: false
} }
},
creatorName: {
type: Sequelize.STRING,
allowNull: false
},
cost: {
type: Sequelize.INTEGER,
allowNull: false
}
}); });
var Task = this.sequelize.define('Task', { var Task = this.sequelize.define('Task', {
something: Sequelize.INTEGER something: Sequelize.INTEGER
}); });
Project.hasOne(Task); Project.hasOne(Task);
Task.belongsTo(Project); Task.belongsTo(Project);
return Project.sync({ force: true }).then(function() { return Project.sync({ force: true }).then(function() {
return Task.sync({ force: true }).then(function() { return Task.sync({ force: true }).then(function() {
self.Project = Project; self.Project = Project;
self.Task = Task; self.Task = Task;
});
}); });
}); });
});
it('produce 3 errors', function() { it('produce 3 errors', function() {
return this.Project.create({}).catch(function(err) { return this.Project.create({}).catch(function(err) {
expect(err).to.be.an.instanceOf(Error); expect(err).to.be.an.instanceOf(Error);
delete err.stack; // longStackTraces delete err.stack; // longStackTraces
expect(Object.keys(err)).to.have.length(3); expect(Object.keys(err)).to.have.length(3);
});
}); });
}); });
}); });
});
it('correctly validates using custom validation methods', function() { it('correctly validates using custom validation methods', function() {
var User = this.sequelize.define('User' + config.rand(), { var User = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: { validate: {
customFn: function(val, next) { customFn: function(val, next) {
if (val !== '2') { if (val !== '2') {
next("name should equal '2'"); next("name should equal '2'");
} else { } else {
next(); next();
}
} }
} }
} }
}); }
});
var failingUser = User.build({ name: '3' }); var failingUser = User.build({ name: '3' });
return failingUser.validate().then(function(error) { return failingUser.validate().then(function(error) {
expect(error).to.be.an.instanceOf(Error); expect(error).to.be.an.instanceOf(Error);
expect(error.get('name')[0].message).to.equal("name should equal '2'"); expect(error.get('name')[0].message).to.equal("name should equal '2'");
var successfulUser = User.build({ name: '2' }); var successfulUser = User.build({ name: '2' });
return successfulUser.validate().then(function(err) { return successfulUser.validate().then(function(err) {
expect(err).to.be.undefined; expect(err).to.be.undefined;
});
}); });
}); });
});
it('supports promises with custom validation methods', function() { it('supports promises with custom validation methods', function() {
var self = this var self = this
, User = this.sequelize.define('User' + config.rand(), { , User = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: { validate: {
customFn: function(val) { customFn: function(val) {
return User.findAll() return User.findAll()
.then(function() { .then(function() {
if (val === 'error') { if (val === 'error') {
throw new Error('Invalid username'); throw new Error('Invalid username');
} }
}); });
}
} }
} }
}); }
});
return User.sync().then(function() { return User.sync().then(function() {
return User.build({ name: 'error' }).validate().then(function(error) { return User.build({ name: 'error' }).validate().then(function(error) {
expect(error).to.be.an.instanceOf(self.sequelize.ValidationError); expect(error).to.be.an.instanceOf(self.sequelize.ValidationError);
expect(error.get('name')[0].message).to.equal('Invalid username'); expect(error.get('name')[0].message).to.equal('Invalid username');
return User.build({ name: 'no error' }).validate().then(function(errors) { return User.build({ name: 'no error' }).validate().then(function(errors) {
expect(errors).to.be.undefined; expect(errors).to.be.undefined;
});
}); });
}); });
}); });
});
it('skips other validations if allowNull is true and the value is null', function() { it('skips other validations if allowNull is true and the value is null', function() {
var User = this.sequelize.define('User' + config.rand(), { var User = this.sequelize.define('User' + config.rand(), {
age: { age: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: true, allowNull: true,
validate: { validate: {
min: { args: 0, msg: 'must be positive' } min: { args: 0, msg: 'must be positive' }
}
} }
}); }
});
return User return User
.build({ age: -1 }) .build({ age: -1 })
.validate() .validate()
.then(function(error) { .then(function(error) {
expect(error).not.to.be.null; expect(error).not.to.be.null;
expect(error).to.be.an.instanceOf(Error); expect(error).to.be.an.instanceOf(Error);
expect(error.get('age')[0].message).to.equal('must be positive'); expect(error.get('age')[0].message).to.equal('must be positive');
// TODO: This does not test anything // TODO: This does not test anything
// Check what the original intention was // Check what the original intention was
return User.build({ age: null }).validate().then(function() { return User.build({ age: null }).validate().then(function() {
return User.build({ age: 1 }).validate(); return User.build({ age: 1 }).validate();
});
}); });
}); });
});
it('validates a model with custom model-wide validation methods', function() { it('validates a model with custom model-wide validation methods', function() {
var Foo = this.sequelize.define('Foo' + config.rand(), { var Foo = this.sequelize.define('Foo' + config.rand(), {
field1: { field1: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: true allowNull: true
}, },
field2: { field2: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: true allowNull: true
} }
}, { }, {
validate: { validate: {
xnor: function() { xnor: function() {
if ((this.field1 === null) === (this.field2 === null)) { if ((this.field1 === null) === (this.field2 === null)) {
throw new Error('xnor failed'); throw new Error('xnor failed');
}
} }
} }
}); }
return Foo
.build({ field1: null, field2: null })
.validate()
.then(function(error) {
expect(error).not.to.be.null;
expect(error).to.be.an.instanceOf(Error);
expect(error.get('xnor')[0].message).to.equal('xnor failed');
return Foo
.build({ field1: 33, field2: null })
.validate()
.then(function(errors) {
expect(errors).not.exist;
});
});
}); });
it('validates model with a validator whose arg is an Array successfully twice in a row', function() { return Foo
var Foo = this.sequelize.define('Foo' + config.rand(), { .build({ field1: null, field2: null })
bar: { .validate()
type: Sequelize.STRING, .then(function(error) {
validate: { expect(error).not.to.be.null;
isIn: [['a', 'b']] expect(error).to.be.an.instanceOf(Error);
} expect(error.get('xnor')[0].message).to.equal('xnor failed');
return Foo
.build({ field1: 33, field2: null })
.validate()
.then(function(errors) {
expect(errors).not.exist;
});
});
});
it('validates model with a validator whose arg is an Array successfully twice in a row', function() {
var Foo = this.sequelize.define('Foo' + config.rand(), {
bar: {
type: Sequelize.STRING,
validate: {
isIn: [['a', 'b']]
} }
}), foo; }
}), foo;
foo = Foo.build({bar: 'a'}); foo = Foo.build({bar: 'a'});
return foo.validate().then(function(errors) {
expect(errors).not.to.exist;
return foo.validate().then(function(errors) { return foo.validate().then(function(errors) {
expect(errors).not.to.exist; expect(errors).not.to.exist;
return foo.validate().then(function(errors) {
expect(errors).not.to.exist;
});
}); });
}); });
});
it('validates enums', function() { it('validates enums', function() {
var values = ['value1', 'value2']; var values = ['value1', 'value2'];
var Bar = this.sequelize.define('Bar' + config.rand(), { var Bar = this.sequelize.define('Bar' + config.rand(), {
field: { field: {
type: Sequelize.ENUM, type: Sequelize.ENUM,
values: values, values: values,
validate: { validate: {
isIn: [values] isIn: [values]
}
} }
}); }
});
var failingBar = Bar.build({ field: 'value3' }); var failingBar = Bar.build({ field: 'value3' });
return failingBar.validate().then(function(errors) { return failingBar.validate().then(function(errors) {
expect(errors).not.to.be.null; expect(errors).not.to.be.null;
expect(errors.get('field')).to.have.length(1); expect(errors.get('field')).to.have.length(1);
expect(errors.get('field')[0].message).to.equal('Validation isIn failed'); expect(errors.get('field')[0].message).to.equal('Validation isIn failed');
});
}); });
});
it('skips validations for the given fields', function() { it('skips validations for the given fields', function() {
var values = ['value1', 'value2']; var values = ['value1', 'value2'];
var Bar = this.sequelize.define('Bar' + config.rand(), { var Bar = this.sequelize.define('Bar' + config.rand(), {
field: { field: {
type: Sequelize.ENUM, type: Sequelize.ENUM,
values: values, values: values,
validate: { validate: {
isIn: [values] isIn: [values]
}
} }
}); }
});
var failingBar = Bar.build({ field: 'value3' }); var failingBar = Bar.build({ field: 'value3' });
return failingBar.validate({ skip: ['field'] }).then(function(errors) { return failingBar.validate({ skip: ['field'] }).then(function(errors) {
expect(errors).not.to.exist; expect(errors).not.to.exist;
});
}); });
});
it('skips validation when asked', function() { it('skips validation when asked', function() {
var values = ['value1', 'value2']; var values = ['value1', 'value2'];
var Bar = this.sequelize.define('Bar' + config.rand(), { var Bar = this.sequelize.define('Bar' + config.rand(), {
field: { field: {
type: Sequelize.ENUM, type: Sequelize.ENUM,
values: values, values: values,
validate: { validate: {
isIn: [values] isIn: [values]
}
} }
}); }
});
return Bar.sync({force: true}).then(function() { return Bar.sync({force: true}).then(function() {
return Bar.create({ field: 'value3' }, {validate: false}) return Bar.create({ field: 'value3' }, {validate: false})
.catch(Sequelize.DatabaseError, function() { .catch(Sequelize.DatabaseError, function() {
}); });
});
}); });
});
it('raises an error if saving a different value into an immutable field', function() { it('raises an error if saving a different value into an immutable field', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: { validate: {
isImmutable: true isImmutable: true
}
} }
}); }
});
return User.sync({force: true}).then(function() { return User.sync({force: true}).then(function() {
return User.create({ name: 'RedCat' }).then(function(user) { return User.create({ name: 'RedCat' }).then(function(user) {
expect(user.getDataValue('name')).to.equal('RedCat'); expect(user.getDataValue('name')).to.equal('RedCat');
user.setDataValue('name', 'YellowCat'); user.setDataValue('name', 'YellowCat');
return user.save() return user.save()
.catch(function(errors) { .catch(function(errors) {
expect(errors).to.not.be.null; expect(errors).to.not.be.null;
expect(errors).to.be.an.instanceOf(Error); expect(errors).to.be.an.instanceOf(Error);
expect(errors.get('name')[0].message).to.eql('Validation isImmutable failed'); expect(errors.get('name')[0].message).to.eql('Validation isImmutable failed');
}); });
});
}); });
}); });
});
it('allows setting an immutable field if the record is unsaved', function() { it('allows setting an immutable field if the record is unsaved', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: { validate: {
isImmutable: true isImmutable: true
}
} }
}); }
});
var user = User.build({ name: 'RedCat' }); var user = User.build({ name: 'RedCat' });
expect(user.getDataValue('name')).to.equal('RedCat'); expect(user.getDataValue('name')).to.equal('RedCat');
user.setDataValue('name', 'YellowCat'); user.setDataValue('name', 'YellowCat');
return user.validate().then(function(errors) { return user.validate().then(function(errors) {
expect(errors).not.to.be.ok; expect(errors).not.to.be.ok;
});
}); });
});
it('raises an error for array on a STRING', function() { it('raises an error for array on a STRING', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
'email': { 'email': {
type: Sequelize.STRING type: Sequelize.STRING
} }
}); });
return User.build({ return User.build({
email: ['iama', 'dummy.com'] email: ['iama', 'dummy.com']
}).validate().then(function(errors) { }).validate().then(function(errors) {
expect(errors).to.be.an.instanceof(Sequelize.ValidationError); expect(errors).to.be.an.instanceof(Sequelize.ValidationError);
});
}); });
});
it('raises an error for array on a STRING(20)', function() { it('raises an error for array on a STRING(20)', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
'email': { 'email': {
type: Sequelize.STRING(20) type: Sequelize.STRING(20)
} }
}); });
return User.build({ return User.build({
email: ['iama', 'dummy.com'] email: ['iama', 'dummy.com']
}).validate().then(function(errors) { }).validate().then(function(errors) {
expect(errors).to.be.an.instanceof(Sequelize.ValidationError); expect(errors).to.be.an.instanceof(Sequelize.ValidationError);
});
}); });
});
it('raises an error for array on a TEXT', function() { it('raises an error for array on a TEXT', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
'email': { 'email': {
type: Sequelize.TEXT type: Sequelize.TEXT
} }
}); });
return User.build({ return User.build({
email: ['iama', 'dummy.com'] email: ['iama', 'dummy.com']
}).validate().then(function(errors) { }).validate().then(function(errors) {
expect(errors).to.be.an.instanceof(Sequelize.ValidationError); expect(errors).to.be.an.instanceof(Sequelize.ValidationError);
});
}); });
});
it('raises an error for {} on a STRING', function() { it('raises an error for {} on a STRING', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
'email': { 'email': {
type: Sequelize.STRING type: Sequelize.STRING
} }
}); });
return User.build({ return User.build({
email: {lol: true} email: {lol: true}
}).validate().then(function(errors) { }).validate().then(function(errors) {
expect(errors).to.be.an.instanceof(Sequelize.ValidationError); expect(errors).to.be.an.instanceof(Sequelize.ValidationError);
});
}); });
});
it('raises an error for {} on a STRING(20)', function() { it('raises an error for {} on a STRING(20)', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
'email': { 'email': {
type: Sequelize.STRING(20) type: Sequelize.STRING(20)
} }
}); });
return User.build({ return User.build({
email: {lol: true} email: {lol: true}
}).validate().then(function(errors) { }).validate().then(function(errors) {
expect(errors).to.be.an.instanceof(Sequelize.ValidationError); expect(errors).to.be.an.instanceof(Sequelize.ValidationError);
});
}); });
});
it('raises an error for {} on a TEXT', function() { it('raises an error for {} on a TEXT', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
'email': { 'email': {
type: Sequelize.TEXT type: Sequelize.TEXT
} }
}); });
return User.build({ return User.build({
email: {lol: true} email: {lol: true}
}).validate().then(function(errors) { }).validate().then(function(errors) {
expect(errors).to.be.an.instanceof(Sequelize.ValidationError); expect(errors).to.be.an.instanceof(Sequelize.ValidationError);
});
}); });
});
it('does not raise an error for null on a STRING (where null is allowed)', function() { it('does not raise an error for null on a STRING (where null is allowed)', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
'email': { 'email': {
type: Sequelize.STRING type: Sequelize.STRING
} }
}); });
return User.build({ return User.build({
email: null email: null
}).validate().then(function(errors) { }).validate().then(function(errors) {
expect(errors).not.to.be.ok; expect(errors).not.to.be.ok;
});
}); });
});
it('validates VIRTUAL fields', function() { it('validates VIRTUAL fields', function() {
var User = this.sequelize.define('user', { var User = this.sequelize.define('user', {
password_hash: Sequelize.STRING, password_hash: Sequelize.STRING,
salt: Sequelize.STRING, salt: Sequelize.STRING,
password: { password: {
type: Sequelize.VIRTUAL, type: Sequelize.VIRTUAL,
set: function(val) { set: function(val) {
this.setDataValue('password', val); this.setDataValue('password', val);
this.setDataValue('password_hash', this.salt + val); this.setDataValue('password_hash', this.salt + val);
}, },
validate: { validate: {
isLongEnough: function(val) { isLongEnough: function(val) {
if (val.length < 7) { if (val.length < 7) {
throw new Error('Please choose a longer password'); throw new Error('Please choose a longer password');
}
} }
} }
} }
}); }
return Sequelize.Promise.all([
User.build({
password: 'short',
salt: '42'
}).validate().then(function(errors) {
expect(errors).not.to.be.undefined;
expect(errors.get('password')[0].message).to.equal('Please choose a longer password');
}),
User.build({
password: 'loooooooong',
salt: '42'
}).validate().then(function(errors) {
expect(errors).to.be.undefined;
})
]);
}); });
it('allows me to add custom validation functions to validator.js', function() { return Sequelize.Promise.all([
this.sequelize.Validator.extend('isExactly7Characters', function(val) { User.build({
return val.length === 7; password: 'short',
}); salt: '42'
}).validate().then(function(errors) {
expect(errors).not.to.be.undefined;
expect(errors.get('password')[0].message).to.equal('Please choose a longer password');
}),
User.build({
password: 'loooooooong',
salt: '42'
}).validate().then(function(errors) {
expect(errors).to.be.undefined;
})
]);
});
var User = this.sequelize.define('User', { it('allows me to add custom validation functions to validator.js', function() {
name: { this.sequelize.Validator.extend('isExactly7Characters', function(val) {
type: Sequelize.STRING, return val.length === 7;
validate: { });
isExactly7Characters: true
} var User = this.sequelize.define('User', {
name: {
type: Sequelize.STRING,
validate: {
isExactly7Characters: true
} }
}); }
});
return User.build({ return User.build({
name: 'abcdefg' name: 'abcdefg'
}).validate().then(function(errors) { }).validate().then(function(errors) {
expect(errors === undefined).to.be.ok; expect(errors === undefined).to.be.ok;
return User.build({ return User.build({
name: 'a' name: 'a'
}).validate(); }).validate();
}).then(function(errors) { }).then(function(errors) {
expect(errors.get('name')[0].message).to.equal('Validation isExactly7Characters failed'); expect(errors.get('name')[0].message).to.equal('Validation isExactly7Characters failed');
});
}); });
}); });
}); });
\ No newline at end of file
...@@ -20,17 +20,37 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -20,17 +20,37 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: true, allowNull: true,
defaultValue: DataTypes.NOW defaultValue: DataTypes.NOW
},
ip: {
type: DataTypes.STRING,
validate: {
isIP: true
}
},
ip2: {
type: DataTypes.STRING,
validate: {
isIP: {
msg: 'test'
}
}
} }
}, {
timestamp: false
}) })
, instance; , instance;
instance = Model.build({}); instance = Model.build({ip: '127.0.0.1', ip2: '0.0.0.0'});
expect(instance.get('created_time')).to.be.ok; expect(instance.get('created_time')).to.be.ok;
expect(instance.get('created_time')).to.be.an.instanceof(Date); expect(instance.get('created_time')).to.be.an.instanceof(Date);
expect(instance.get('updated_time')).to.be.ok; expect(instance.get('updated_time')).to.be.ok;
expect(instance.get('updated_time')).to.be.an.instanceof(Date); expect(instance.get('updated_time')).to.be.an.instanceof(Date);
return instance.validate().then(function(err) {
expect(err).to.be.equal(undefined);
});
}); });
it('should popuplate explicitely undefined UUID primary keys', function () { it('should popuplate explicitely undefined UUID primary keys', function () {
......
'use strict';
/* jshint -W030 */
/* jshint -W110 */
var chai = require('chai')
, expect = chai.expect
, Sequelize = require(__dirname + '/../../../index')
, Support = require(__dirname + '/../support')
, config = require(__dirname + '/../../config/config');
describe(Support.getTestDialectTeaser('InstanceValidator'), function() {
describe('validations', function() {
var checks = {
is: {
spec: { args: ['[a-z]', 'i'] },
fail: '0',
pass: 'a'
}
, not: {
spec: { args: ['[a-z]', 'i'] },
fail: 'a',
pass: '0'
}
, isEmail: {
fail: 'a',
pass: 'abc@abc.com'
}
, isUrl: {
fail: 'abc',
pass: 'http://abc.com'
}
, isIP: {
fail: 'abc',
pass: '129.89.23.1'
}
, isIPv4: {
fail: 'abc',
pass: '129.89.23.1'
}
, isIPv6: {
fail: '1111:2222:3333::5555:',
pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156'
}
, isAlpha: {
fail: '012',
pass: 'abc'
}
, isAlphanumeric: {
fail: '_abc019',
pass: 'abc019'
}
, isNumeric: {
fail: 'abc',
pass: '019'
}
, isInt: {
fail: '9.2',
pass: '-9'
}
, isLowercase: {
fail: 'AB',
pass: 'ab'
}
, isUppercase: {
fail: 'ab',
pass: 'AB'
}
, isDecimal: {
fail: 'a',
pass: '0.2'
}
, isFloat: {
fail: 'a',
pass: '9.2'
}
, isNull: {
fail: 0,
pass: null
}
, notEmpty: {
fail: ' ',
pass: 'a'
}
, equals: {
spec: { args: 'bla bla bla' },
fail: 'bla',
pass: 'bla bla bla'
}
, contains: {
spec: { args: 'bla' },
fail: 'la',
pass: '0bla23'
}
, notContains: {
spec: { args: 'bla' },
fail: '0bla23',
pass: 'la'
}
, regex: {
spec: { args: ['[a-z]', 'i'] },
fail: '0',
pass: 'a'
}
, notRegex: {
spec: { args: ['[a-z]', 'i'] },
fail: 'a',
pass: '0'
}
, len: {
spec: { args: [2, 4] },
fail: ['1', '12345'],
pass: ['12', '123', '1234'],
raw: true
}
, len$: {
spec: [2, 4],
fail: ['1', '12345'],
pass: ['12', '123', '1234'],
raw: true
}
, isUUID: {
spec: { args: 4 },
fail: 'f47ac10b-58cc-3372-a567-0e02b2c3d479',
pass: 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
}
, isDate: {
fail: 'not a date',
pass: '2011-02-04'
}
, isAfter: {
spec: { args: '2011-11-05' },
fail: '2011-11-04',
pass: '2011-11-06'
}
, isBefore: {
spec: { args: '2011-11-05' },
fail: '2011-11-06',
pass: '2011-11-04'
}
, isIn: {
spec: { args: 'abcdefghijk' },
fail: 'ghik',
pass: 'ghij'
}
, notIn: {
spec: { args: 'abcdefghijk' },
fail: 'ghij',
pass: 'ghik'
}
, max: {
spec: { args: 23 },
fail: '24',
pass: '23'
}
, max$: {
spec: 23,
fail: '24',
pass: '23'
}
, min: {
spec: { args: 23 },
fail: '22',
pass: '23'
}
, min$: {
spec: 23,
fail: '22',
pass: '23'
}
, isCreditCard: {
fail: '401288888888188f',
pass: '4012888888881881'
}
};
var applyFailTest = function applyFailTest(validatorDetails, i, validator) {
var failingValue = validatorDetails.fail[i];
it('correctly specifies an instance as invalid using a value of "' + failingValue + '" for the validation "' + validator + '"', function() {
var validations = {}
, message = validator + '(' + failingValue + ')';
if (validatorDetails.hasOwnProperty('spec')) {
validations[validator] = validatorDetails.spec;
} else {
validations[validator] = {};
}
validations[validator].msg = message;
var UserFail = this.sequelize.define('User' + config.rand(), {
name: {
type: Sequelize.STRING,
validate: validations
}
});
var failingUser = UserFail.build({ name: failingValue });
return failingUser.validate().then(function(_errors) {
expect(_errors).not.to.be.null;
expect(_errors).to.be.an.instanceOf(Error);
expect(_errors.get('name')[0].message).to.equal(message);
});
});
}
, applyPassTest = function applyPassTest(validatorDetails, j, validator, msg) {
var succeedingValue = validatorDetails.pass[j];
it('correctly specifies an instance as valid using a value of "' + succeedingValue + '" for the validation "' + validator + '"', function() {
var validations = {};
if (validatorDetails.hasOwnProperty('spec')) {
validations[validator] = validatorDetails.spec;
} else {
validations[validator] = {};
}
if (msg) {
validations[validator].msg = validator + '(' + succeedingValue + ')';
}
var UserSuccess = this.sequelize.define('User' + config.rand(), {
name: {
type: Sequelize.STRING,
validate: validations
}
});
var successfulUser = UserSuccess.build({ name: succeedingValue });
return successfulUser.validate().then(function(errors) {
expect(errors).to.be.undefined;
}).catch(function(err) {
expect(err).to.deep.equal({});
});
});
};
for (var validator in checks) {
if (checks.hasOwnProperty(validator)) {
validator = validator.replace(/\$$/, '');
var validatorDetails = checks[validator];
if (!validatorDetails.hasOwnProperty('raw')) {
validatorDetails.fail = Array.isArray(validatorDetails.fail) ? validatorDetails.fail : [validatorDetails.fail];
validatorDetails.pass = Array.isArray(validatorDetails.pass) ? validatorDetails.pass : [validatorDetails.pass];
}
for (var i = 0; i < validatorDetails.fail.length; i++) {
applyFailTest(validatorDetails, i, validator);
}
for (i = 0; i < validatorDetails.pass.length; i++) {
applyPassTest(validatorDetails, i, validator, false);
}
for (i = 0; i < validatorDetails.pass.length; i++) {
applyPassTest(validatorDetails, i, validator, true);
}
}
}
});
});
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!