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

Commit f449abc9 by Mick Hansen

Merge pull request #1476 from thanpolas/refactor-dao-validator

Refactor dao validator
2 parents f435d6b3 0e112da3
......@@ -21,5 +21,6 @@
"laxcomma": true,
"es5": true,
"quotmark": false,
"undef": true,
"strict": false
}
\ No newline at end of file
}
......@@ -7,6 +7,9 @@ Notice: All 1.7.x changes are present in 2.0.x aswell
- [FEATURE] 1:1 and 1:m marks columns as foreign keys, and sets them to cascade on update and set null on delete. If you are working with an existing DB which does not allow null values, be sure to override those options, or disable them completely by passing constraints: false to your assocation call (`M1.belongsTo(M2, { constraints: false})`).
#### Backwards compatability changes
- The `notNull` validator has been removed, use the Schema's `allowNull` property.
- All Validation errors now return a sequelize.ValidationError which inherits from Error.
- selectedValues has been removed for performance reasons, if you depend on this, please open an issue and we will help you work around it.
- foreign keys will now correctly be based on the alias of the model
- if you have any 1:1 relations where both sides use an alias, you'll need to set the foreign key, or they'll each use a different foreign key based on their alias.
......
......@@ -119,7 +119,7 @@ module.exports = (function() {
} else {
this.as = Utils.pluralize(this.target.name, this.target.options.language)
}
this.accessors = {
get: Utils._.camelize('get_' + this.as),
set: Utils._.camelize('set_' + this.as),
......@@ -195,7 +195,8 @@ module.exports = (function() {
if (primaryKeyDeleted) {
targetAttribute.primaryKey = sourceAttribute.primaryKey = true
} else {
} else {
var uniqueKey = [this.through.tableName, this.identifier, this.foreignIdentifier, 'unique'].join('_')
targetAttribute.unique = sourceAttribute.unique = uniqueKey
}
......
......@@ -126,7 +126,7 @@ module.exports = (function() {
this._timestampAttributes.createdAt = Utils._.underscoredIf(this.options.createdAt, this.options.underscored)
}
if (this.options.updatedAt) {
this._timestampAttributes.updatedAt = Utils._.underscoredIf(this.options.updatedAt, this.options.underscored)
this._timestampAttributes.updatedAt = Utils._.underscoredIf(this.options.updatedAt, this.options.underscored)
}
if (this.options.paranoid && this.options.deletedAt) {
this._timestampAttributes.deletedAt = Utils._.underscoredIf(this.options.deletedAt, this.options.underscored)
......@@ -598,7 +598,7 @@ module.exports = (function() {
DAOFactory.prototype.count = function(options) {
options = Utils._.clone(options || {})
return new Utils.CustomEventEmitter(function (emitter) {
var col = this.sequelize.col('*')
if (options.include) {
......@@ -1332,13 +1332,24 @@ module.exports = (function() {
}
if (this._timestampAttributes.createdAt) {
tail[this._timestampAttributes.createdAt] = {type: DataTypes.DATE, allowNull: false}
tail[this._timestampAttributes.createdAt] = {
type: DataTypes.DATE,
allowNull: false,
_autoGenerated: true,
}
}
if (this._timestampAttributes.updatedAt) {
tail[this._timestampAttributes.updatedAt] = {type: DataTypes.DATE, allowNull: false}
tail[this._timestampAttributes.updatedAt] = {
type: DataTypes.DATE,
allowNull: false,
_autoGenerated: true,
}
}
if (this._timestampAttributes.deletedAt) {
tail[this._timestampAttributes.deletedAt] = {type: DataTypes.DATE}
tail[this._timestampAttributes.deletedAt] = {
type: DataTypes.DATE,
_autoGenerated: true,
}
}
var existingAttributes = Utils._.clone(self.rawAttributes)
......
var Validator = require("validator")
, Utils = require("./utils")
, sequelizeError = require("./errors")
, Promise = require("bluebird")
// Backwards compat for people using old validation function
// We cannot use .extend, since it coerces the first arg to string
Validator.notNull = function (val) {
return [null, undefined].indexOf(val) === -1
function noop() {}
// Deprecate this.
Validator.notNull = function () {
throw new Error('Warning "notNull" validation has been deprecated in favor of Schema based "allowNull"');
}
// https://github.com/chriso/validator.js/blob/1.5.0/lib/validators.js
......@@ -76,53 +78,80 @@ Validator.extend('is', function(str, pattern, modifiers) {
return this.regex(str, pattern, modifiers);
})
/**
* The Main DAO Validator.
*
* @param {sequelize.Model} modelInstance The model instance.
* @param {Object=} options A dict with options.
* @constructor
*/
var DaoValidator = module.exports = function(modelInstance, options) {
options = options || {}
options.skip = options.skip || []
// assign defined and default options
this.options = Utils._.defaults(options, {
skip: [],
});
this.modelInstance = modelInstance
this.chainer = new Utils.QueryChainer()
this.options = options
/**
* Expose validator.js to allow users to extend
* @name Validator
*/
this.Validator = Validator
/**
* All errors will be stored here from the validations.
*
* @type {Sequelize.error} Will contain keys that correspond to attributes which will
* be Arrays of Errors.
*/
this.errors = new sequelizeError.ValidationError('Validation error')
/** @type {boolean} Indicates if validations are in progress */
this.inProgress = false;
}
/** @define {string} The error key for arguments as passed by custom validators */
DaoValidator.RAW_KEY_NAME = '__raw'
/**
* The main entry point for the Validation module, invoke to start the dance.
*
* @return {sequelize.Utils.CustomEventEmitter} That thing...
*/
DaoValidator.prototype.validate = function() {
var self = this
if (this.inProgress) {
throw new Error('Validations already in progress.');
}
this.inProgress = true;
this.errors = new sequelizeError.ValidationError('Validation error')
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
validateAttributes.call(self)
validateModel.call(self)
self
.chainer
.run()
.success(function () {
Promise.settle([
self._builtinValidators(),
self._customValidators(),
]).then(function () {
if (Object.keys(self.errors).length) {
emitter.emit('success', self.errors)
} else {
emitter.emit('success')
})
.error(function(err) {
var error = new sequelizeError.ValidationError('Validation error')
error[DaoValidator.RAW_KEY_NAME] = []
Utils._.each(err, function (value) {
error[DaoValidator.RAW_KEY_NAME].push(value[DaoValidator.RAW_KEY_NAME])
delete value[DaoValidator.RAW_KEY_NAME]
Utils._.extend(error, value)
})
emitter.emit('success', error)
})
}
})
}).run()
}
/**
* Invoke the Validation sequence:
* - Before Validation Model Hooks
* - Validation
* - After Validation Model Hooks
*
* @return {sequelize.Utils.CustomEventEmitter} An eventemitter.
*/
DaoValidator.prototype.hookValidate = function() {
var self = this
......@@ -149,143 +178,238 @@ DaoValidator.prototype.hookValidate = function() {
}).run()
}
// private
var validateModel = function() {
Utils._.each(this.modelInstance.__options.validate, function(_validator, validatorType) {
var validator = prepareValidationOfAttribute.call(this, undefined, _validator, validatorType, { omitValue: true })
this.chainer.add(new Utils.CustomEventEmitter(function(emitter) {
var next = function(err) {
if (err) {
var error = {};
error[DaoValidator.RAW_KEY_NAME] = err
var msg = ((err instanceof Error) ? err.message : err)
error[validatorType] = [msg]
emitter.emit('error', error)
} else {
emitter.emit('success')
}
}
/**
* Will run all the built-in validators.
*
* @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .settle().
* @private
*/
DaoValidator.prototype._builtinValidators = function() {
var self = this
validator.args.unshift(next);
validator.fn.apply(null, validator.args)
}.bind(this)).run())
}.bind(this))
}
// promisify all attribute invocations
var validators = [];
Utils._.forIn(this.modelInstance.rawAttributes, function(rawAttribute, field) {
if (self.options.skip.indexOf(field) >= 0) {
return
}
var validateAttributes = function() {
var self = this
, errors = {}
var value = self.modelInstance.dataValues[field]
Utils._.each(this.modelInstance.rawAttributes, function(rawAttribute, field) {
var value = self.modelInstance.dataValues[field]
, hasAllowedNull = ((rawAttribute === undefined || rawAttribute.allowNull === true) && ((value === null) || (value === undefined)))
, isSkipped = self.options.skip.length > 0 && self.options.skip.indexOf(field) !== -1
if (!rawAttribute._autoGenerated && !rawAttribute.autoIncrement) {
// perform validations based on schema
self._validateSchema(rawAttribute, field, value)
}
if (self.modelInstance.validators.hasOwnProperty(field) && !hasAllowedNull && !isSkipped) {
errors = Utils._.merge(errors, validateAttribute.call(self, value, field))
if (self.modelInstance.validators.hasOwnProperty(field)) {
validators.push(self._builtinAttrValidate.call(self, value, field))
}
})
return errors
return Promise.settle(validators)
}
var validateAttribute = function(value, field) {
// for each validator
Utils._.each(this.modelInstance.validators[field], function(details, validatorType) {
var validator = prepareValidationOfAttribute.call(this, value, details, validatorType)
this.chainer.add(new Utils.CustomEventEmitter(function(emitter) {
var next = function(err) {
if (err) {
var error = {}
error[field] = [err]
emitter.emit('error', error)
} else {
emitter.emit('success')
}
}
/**
* Will run all the custom validators.
*
* @return {Promise(Array.<Promise.PromiseInspection>)} A promise from .settle().
* @private
*/
DaoValidator.prototype._customValidators = function() {
validator.args.unshift(next);
validator.fn.apply(null, validator.args)
}.bind(this)).run())
}.bind(this)) // for each validator for this field
}
var validators = [];
var self = this;
Utils._.each(this.modelInstance.__options.validate, function(validator,
validatorType) {
var prepareValidationOfAttribute = function(value, details, validatorType, options) {
var isCustomValidator = false // if true then it's a custom validation method
, validatorFunction = null // the validation function to call
, validatorArgs = [] // extra arguments to pass to validation function
, errorMessage = "" // the error message to return if validation fails
var valprom = self._invokeCustomValidator(validator, validatorType)
// errors are handled in settling, stub this
.catch(noop)
if (typeof details === 'function') {
// it is a custom validator function?
isCustomValidator = true
validators.push(valprom)
})
var callArgs = []
var validatorArity = details.length
return Promise.settle(validators)
}
var omitValue = !!(options || {}).omitValue
if (!omitValue) {
callArgs.push(value)
}
/**
* Validate a single attribute with all the defined built-in validators.
*
* @param {*} value Anything.
* @param {string} field The field name.
* @return {Promise} A promise, will always resolve,
* auto populates error on this.error local object.
* @private
*/
DaoValidator.prototype._builtinAttrValidate = function(value, field) {
var self = this;
// check if value is null (if null not allowed the Schema pass will capture it)
if (value === null || typeof value === 'undefined') {
return Promise.resolve();
}
// check if validator is async and requires a callback
var isAsync = omitValue && validatorArity === 1 ||
!omitValue && validatorArity === 2
// Promisify each validator
var validators = [];
Utils._.forIn(this.modelInstance.validators[field], function(test,
validatorType) {
validatorFunction = function(next) {
if (isAsync) {
callArgs.push(next)
}
// Check for custom validator.
if (typeof test === 'function') {
return validators.push(self._invokeCustomValidator(test, validatorType,
true, value, field))
}
var validatorPromise = self._invokeBuiltinValidator(value, test, validatorType);
// errors are handled in settling, stub this
validatorPromise.catch(noop)
validators.push(validatorPromise)
});
return Promise.settle(validators)
.then(this._handleSettledResult.bind(this, field))
};
/**
* Prepare and invoke a custom validator.
*
* @param {Function} validator The custom validator.
* @param {string} validatorType the custom validator type (name).
* @param {boolean=} optAttrDefined Set to true if custom validator was defined
* from the Attribute
* @return {Promise} A promise.
* @private
*/
DaoValidator.prototype._invokeCustomValidator = Promise.method(function(validator, validatorType,
optAttrDefined, optValue, optField) {
var validatorFunction = null // the validation function to call
var isAsync = false
var validatorArity = validator.length;
// check if validator is async and requires a callback
var asyncArity = 1;
var errorKey = validatorType;
var invokeArgs;
if (optAttrDefined) {
asyncArity = 2;
invokeArgs = optValue;
errorKey = optField;
}
if (validatorArity === asyncArity) {
isAsync = true;
}
try {
details.apply(this.modelInstance, callArgs)
} catch(ex) {
return next(ex)
}
if (!isAsync) {
next()
}
}.bind(this)
} else {
// extract extra arguments for the validator
validatorArgs = details.hasOwnProperty("args") ? details.args : details
if (!Array.isArray(validatorArgs)) {
validatorArgs = [validatorArgs]
if (isAsync) {
if (optAttrDefined) {
validatorFunction = Promise.promisify(validator.bind(this.modelInstance, invokeArgs))
} else {
validatorArgs = validatorArgs.slice(0);
validatorFunction = Promise.promisify(validator.bind(this.modelInstance))
}
return validatorFunction()
.catch(this._pushError.bind(this, false, errorKey))
} else {
return Promise.try(validator.bind(this.modelInstance, invokeArgs))
.catch(this._pushError.bind(this, false, errorKey))
}
})
// extract the error msg
errorMessage = details.hasOwnProperty("msg") ? details.msg : 'Validation ' + validatorType + ' failed'
/**
* Prepare and invoke a build-in validator.
*
* @param {*} value Anything.
* @param {*} test The test case.
* @param {string} validatorType One of known to Sequelize validators.
* @return {Object} An object with specific keys to invoke the validator.
* @private
*/
DaoValidator.prototype._invokeBuiltinValidator = Promise.method(function(value,
test, validatorType) {
// check if Validator knows that kind of validation test
if (typeof Validator[validatorType] !== 'function') {
throw new Error('Invalid validator function: ' + validatorType)
}
// check if Validator knows that kind of validation test
if (!Utils._.isFunction(Validator[validatorType])) {
throw new Error("Invalid validator function: " + validatorType)
}
// extract extra arguments for the validator
var validatorArgs = test.hasOwnProperty('args') ? test.args : test;
// extract the error msg
var errorMessage = test.hasOwnProperty('msg') ? test.msg :
'Validation ' + validatorType + ' failed'
// bind to validator obj
validatorFunction = function(next) {
var args = Array.prototype.slice.call(arguments, 1)
if (Validator[validatorType].apply(Validator, [value].concat(args))) {
next()
} else {
next(errorMessage)
}
if (!Array.isArray(validatorArgs)) {
validatorArgs = [validatorArgs]
} else {
validatorArgs = validatorArgs.slice(0);
}
if (!Validator[validatorType].apply(Validator, [value].concat(validatorArgs))) {
throw errorMessage
}
});
/**
* Will validate a single field against its schema definition (isnull).
*
* @param {Object} rawAttribute As defined in the Schema.
* @param {string} field The field name.
* @param {*} value anything.
* @private
*/
DaoValidator.prototype._validateSchema = function(rawAttribute,
field, value) {
if (rawAttribute.allowNull === false && ((value === null) ||
(value === undefined))) {
var error = new sequelizeError.ValidationError(field + ' cannot be null')
error.path = field
error.value = value
error.type = error.message = 'notNull Violation'
if (!this.errors.hasOwnProperty(field)) {
this.errors[field] = [];
}
this.errors[field].push(error);
}
return {
fn: validatorFunction,
msg: errorMessage,
args: validatorArgs,
isCustom: isCustomValidator
};
/**
* Handles the returned result of a Promise.settle.
*
* If errors are found it populates this.error.
*
* @param {string} field The attribute name.
* @param {Array.<Promise.PromiseInspection>} Promise inspection objects.
* @private
*/
DaoValidator.prototype._handleSettledResult = function(field, promiseInspections) {
var self = this;
promiseInspections.forEach(function(promiseInspection) {
if (promiseInspection.isRejected()) {
var rejection = promiseInspection.error();
self._pushError(true, field, rejection);
}
});
};
/**
* Signs all errors retaining the original.
*
* @param {boolean} isBuiltin Determines if error is from builtin validator.
* @param {string} errorKey The error key to assign on this.errors object.
* @param {Error|string} rawError The original error.
* @private
*/
DaoValidator.prototype._pushError = function(isBuiltin, errorKey, rawError) {
if (!this.errors.hasOwnProperty(errorKey)) {
this.errors[errorKey] = [];
}
}
var error = new sequelizeError.ValidationError()
error[DaoValidator.RAW_KEY_NAME] = rawError
error.message = rawError.message || rawError || 'Validation error'
this.errors[errorKey].push(error);
};
......@@ -11,7 +11,7 @@ var error = module.exports = {}
* @constructor
*/
error.BaseError = function() {
Error.call(this)
Error.apply(this, arguments)
};
util.inherits(error.BaseError, Error)
......@@ -22,6 +22,6 @@ util.inherits(error.BaseError, Error)
* @constructor
*/
error.ValidationError = function() {
error.BaseError.call(this)
error.BaseError.apply(this, arguments)
};
util.inherits(error.ValidationError, error.BaseError)
......@@ -627,7 +627,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
username: DataTypes.STRING
}).schema('acme', '_')
, AcmeProject = self.sequelize.define('Project', {
title: DataTypes.STRING,
title: DataTypes.STRING,
active: DataTypes.BOOLEAN
}).schema('acme', '_')
, AcmeProjectUsers = self.sequelize.define('ProjectUsers', {
......@@ -835,7 +835,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
this.Task.hasMany(this.User)
this.sequelize.sync({force: true}).success(function() {
done()
done()
})
})
......@@ -938,7 +938,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
describe('primary key handling for join table', function () {
beforeEach(function (done) {
var self = this
......@@ -966,7 +966,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
it('keeps the primary key if it was added by the user', function () {
var self = this
, fk
this.UserTasks = this.sequelize.define('usertasks', {
id: {
type: Sequelize.INTEGER,
......@@ -980,14 +980,14 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
autoincrement: true,
primaryKey: true
}
});
});
this.User.hasMany(this.Task, { through: this.UserTasks })
this.Task.hasMany(this.User, { through: this.UserTasks })
this.User.hasMany(this.Task, { through: this.UserTasks2 })
this.Task.hasMany(this.User, { through: this.UserTasks2 })
expect(Object.keys(self.UserTasks.primaryKeys)).to.deep.equal(['id'])
expect(Object.keys(self.UserTasks2.primaryKeys)).to.deep.equal(['userTasksId'])
......@@ -997,7 +997,7 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
describe('through', function () {
beforeEach(function (done) {
this.User = this.sequelize.define('User', {})
......
......@@ -376,32 +376,42 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
UserNull.create({ username: 'foo2', smth: null }).error(function(err) {
expect(err).to.exist
expect(err.smth[0].path).to.equal('smth');
if (Support.dialectIsMySQL()) {
// We need to allow two different errors for MySQL, see:
// http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_trans_tables
expect(err.message).to.match(/(Column 'smth' cannot be null|Field 'smth' doesn't have a default value)/)
expect(err.smth[0].message).to.match(/notNull Violation/)
}
else if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
expect(err.smth[0].message).to.match(/notNull Violation/)
} else {
expect(err.message).to.match(/.*column "smth" violates not-null.*/)
expect(err.smth[0].message).to.match(/notNull Violation/)
}
done()
})
})
})
it("raises an error if created object breaks definition contraints", function(done) {
var UserNull = this.sequelize.define('UserWithNonNullSmth', {
username: { type: Sequelize.STRING, unique: true },
smth: { type: Sequelize.STRING, allowNull: false }
})
UserNull.create({ username: 'foo', smth: 'foo' }).success(function() {
UserNull.create({ username: 'foo', smth: 'bar' }).error(function(err) {
expect(err).to.exist
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (Support.dialectIsMySQL()) {
expect(err.message).to.match(/Duplicate entry 'foo' for key 'username'/)
} else {
expect(err.message).to.match(/.*duplicate key value violates unique constraint.*/)
}
this.sequelize.options.omitNull = false
done()
})
UserNull.sync({ force: true }).success(function() {
UserNull.create({ username: 'foo', smth: 'foo' }).success(function() {
UserNull.create({ username: 'foo', smth: 'bar' }).error(function(err) {
expect(err).to.exist
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (Support.dialectIsMySQL()) {
expect(err.message).to.match(/Duplicate entry 'foo' for key 'username'/)
} else {
expect(err.message).to.match(/.*duplicate key value violates unique constraint.*/)
}
done()
})
})
})
......@@ -417,18 +427,16 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
StringIsNullOrUrl.sync({ force: true }).success(function() {
StringIsNullOrUrl.create({ str: null }).success(function(str1) {
expect(str1.str).to.be.null
StringIsNullOrUrl.create({ str: 'http://sequelizejs.org' }).success(function(str2) {
expect(str2.str).to.equal('http://sequelizejs.org')
StringIsNullOrUrl.create({ str: '' }).error(function(err) {
expect(err).to.exist
expect(err.str[0]).to.match(/Validation isURL failed/)
expect(err.str[0].message).to.match(/Validation isURL failed/)
done()
})
})
})
}).error(done)
})
})
......@@ -944,9 +952,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
var Tasks = this.sequelize.define('Task', {
name: {
type: Sequelize.STRING,
validate: {
notNull: { args: true, msg: 'name cannot be null' }
}
allowNull: false,
},
code: {
type: Sequelize.STRING,
......@@ -961,15 +967,15 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
{name: 'foo', code: '123'},
{code: '1234'},
{name: 'bar', code: '1'}
], { validate: true }).error(function(errors) {
], { validate: true }).error(function(errors) {
expect(errors).to.not.be.null
expect(errors).to.be.instanceof(Array)
expect(errors).to.be.an('Array')
expect(errors).to.have.length(2)
expect(errors[0].record.code).to.equal('1234')
expect(errors[0].errors.name[0]).to.equal('name cannot be null')
expect(errors[0].errors.name[0].message).to.equal('notNull Violation')
expect(errors[1].record.name).to.equal('bar')
expect(errors[1].record.code).to.equal('1')
expect(errors[1].errors.code[0]).to.equal('Validation len failed')
expect(errors[1].errors.code[0].message).to.equal('Validation len failed')
done()
})
})
......@@ -980,8 +986,8 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
name: {
type: Sequelize.STRING,
validate: {
notNull: { args: true, msg: 'name cannot be null' }
}
notEmpty: true,
},
},
code: {
type: Sequelize.STRING,
......
......@@ -57,7 +57,9 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
expect(u2.username).to.equal(bio)
done()
})
})
}).error(function(err) {
done(err);
});
})
})
......@@ -853,7 +855,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
expect(err).to.be.instanceof(Object)
expect(err.validateTest).to.be.instanceof(Array)
expect(err.validateTest[0]).to.exist
expect(err.validateTest[0]).to.equal('Validation isInt failed')
expect(err.validateTest[0].message).to.equal('Validation isInt failed')
done()
})
})
......@@ -866,7 +868,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
expect(err.validateCustom).to.exist
expect(err.validateCustom).to.be.instanceof(Array)
expect(err.validateCustom[0]).to.exist
expect(err.validateCustom[0]).to.equal('Length failed.')
expect(err.validateCustom[0].message).to.equal('Length failed.')
done()
})
})
......@@ -879,7 +881,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
expect(err.validateTest).to.exist
expect(err.validateTest).to.be.instanceof(Array)
expect(err.validateTest[0]).to.exist
expect(err.validateTest[0]).to.equal('Validation isInt failed')
expect(err.validateTest[0].message).to.equal('Validation isInt failed')
done()
})
})
......@@ -1050,6 +1052,44 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
})
describe('many to many relations', function() {
var udo;
beforeEach(function(done) {
var self = this
this.User = this.sequelize.define('UserWithUsernameAndAgeAndIsAdmin', {
username: DataTypes.STRING,
age: DataTypes.INTEGER,
isAdmin: DataTypes.BOOLEAN
}, {timestamps: false})
this.Project = this.sequelize.define('NiceProject',
{ title: DataTypes.STRING }, {timestamps: false})
this.Project.hasMany(this.User)
this.User.hasMany(this.Project)
this.User.sync({ force: true }).success(function() {
self.Project.sync({ force: true }).success(function() {
self.User.create({ username: 'fnord', age: 1, isAdmin: true })
.success(function(user) {
udo = user
done()
})
})
})
})
it.skip('Should assign a property to the instance', function(done) {
// @thanpolas rethink this test, it doesn't make sense, a relation has
// to be created first in the beforeEach().
var self = this;
this.User.find({id: udo.id})
.success(function(user) {
user.NiceProjectId = 1;
expect(user.NiceProjectId).to.equal(1);
done();
})
})
})
describe('toJSON', function() {
beforeEach(function(done) {
......
......@@ -68,10 +68,6 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
fail: "a",
pass: "9.2"
}
, notNull : {
fail: null,
pass: 0
}
, isNull : {
fail: 0,
pass: null
......@@ -201,8 +197,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
failingUser.validate().done( function(err, _errors) {
expect(_errors).to.not.be.null
expect(_errors).to.be.an.instanceOf(Error);
expect(_errors.name).to.deep.eql([message])
expect(_errors).to.be.an.instanceOf(Error)
expect(_errors.name[0].message).to.equal(message)
done()
})
})
......@@ -231,7 +227,7 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
expect(arguments).to.have.length(0)
done()
}).error(function(err) {
expect(err).to.be.deep.equal({})
expect(err).to.deep.equal({})
done()
})
})
......@@ -250,7 +246,7 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
for (var i = 0; i < validatorDetails.fail.length; i++) {
applyFailTest(validatorDetails, i, validator);
}
for (var j = 0; j < validatorDetails.pass.length; j++) {
applyPassTest(validatorDetails, j, validator);
}
......@@ -291,8 +287,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
var Model = this.sequelize.define('model', {
name: {
type: Sequelize.STRING,
allowNull: false,
validate: {
notNull: true, // won't allow null
notEmpty: true // don't allow empty strings
}
}
......@@ -301,8 +297,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
Model.sync({ force: true }).success(function() {
Model.create({name: 'World'}).success(function(model) {
model.updateAttributes({name: ''}).error(function(err) {
expect(err).to.be.instanceOf(Error)
expect(err.name).to.deep.equal(['Validation notEmpty failed']);
expect(err).to.be.an.instanceOf(Error)
expect(err.name[0].message).to.equal('Validation notEmpty failed');
done()
})
})
......@@ -313,18 +309,18 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
var Model = this.sequelize.define('model', {
name: {
type: Sequelize.STRING,
allowNull: false,
validate: {
notNull: true, // won't allow null
notEmpty: true // don't allow empty strings
}
}
})
Model.sync({ force: true }).success(function() {
Model.create({name: 'World'}).success(function(model) {
Model.create({name: 'World'}).success(function() {
Model.update({name: ''}, {id: 1}).error(function(err) {
expect(err).to.be.instanceOf(Error)
expect(err.name).to.deep.equal(['Validation notEmpty failed']);
expect(err).to.be.an.instanceOf(Error)
expect(err.name[0].message).to.equal('Validation notEmpty failed')
done()
})
})
......@@ -400,8 +396,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
User.sync({ force: true }).success(function() {
User.create({id: 'helloworld'}).error(function(err) {
expect(err).to.be.instanceOf(Error)
expect(err.id).to.deep.equal(['Validation isInt failed']);
expect(err).to.be.an.instanceOf(Error)
expect(err.id[0].message).to.equal('Validation isInt failed')
done()
})
})
......@@ -421,8 +417,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
User.sync({ force: true }).success(function() {
User.create({username: 'helloworldhelloworld'}).error(function(err) {
expect(err).to.be.instanceOf(Error)
expect(err.username).to.deep.equal(['Username must be an integer!']);
expect(err).to.be.an.instanceOf(Error)
expect(err.username[0].message).to.equal('Username must be an integer!')
done()
})
})
......@@ -448,8 +444,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
it('should emit an error when we try to enter in a string for the id key with validation arguments', function(done) {
this.User.create({id: 'helloworld'}).error(function(err) {
expect(err).to.be.instanceOf(Error)
expect(err.id).to.deep.equal(['ID must be an integer!']);
expect(err).to.be.an.instanceOf(Error)
expect(err.id[0].message).to.equal('ID must be an integer!')
done()
})
})
......@@ -458,8 +454,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
var user = this.User.build({id: 'helloworld'})
user.validate().success(function(err) {
expect(err).to.be.instanceOf(Error)
expect(err.id).to.deep.equal(['ID must be an integer!']);
expect(err).to.be.an.instanceOf(Error)
expect(err.id[0].message).to.equal('ID must be an integer!')
done()
})
})
......@@ -467,13 +463,59 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
it('should emit an error when we try to .save()', function(done) {
var user = this.User.build({id: 'helloworld'})
user.save().error(function(err) {
expect(err).to.be.instanceOf(Error)
expect(err.id).to.deep.equal(['ID must be an integer!']);
expect(err).to.be.an.instanceOf(Error)
expect(err.id[0].message).to.equal('ID must be an integer!')
done()
})
})
})
})
describe('Pass all paths when validating', function() {
beforeEach(function(done) {
var self = this
var Project = this.sequelize.define('Project', {
name: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isIn: [['unknown', 'hello', 'test']]
}
},
creatorName: {
type: Sequelize.STRING,
allowNull: false,
},
cost: {
type: Sequelize.INTEGER,
allowNull: false,
},
})
var Task = this.sequelize.define('Task', {
something: Sequelize.INTEGER
})
Project.hasOne(Task)
Task.belongsTo(Project)
Project.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
self.Project = Project
self.Task = Task
done()
})
})
})
it('produce 3 errors', function(done) {
this.Project.create({}).error(function(err) {
expect(err).to.be.an.instanceOf(Error)
expect(Object.keys(err)).to.have.length(3)
done()
})
})
})
})
it('correctly validates using custom validation methods', function(done) {
......@@ -495,15 +537,16 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
var failingUser = User.build({ name : "3" })
failingUser.validate().success(function(error) {
expect(error).to.be.instanceOf(Error);
expect(error.name).to.deep.equal(["name should equal '2'"])
expect(error).to.be.an.instanceOf(Error)
expect(error.name[0].message).to.equal("name should equal '2'")
var successfulUser = User.build({ name : "2" })
successfulUser.validate().success(function() {
expect(arguments).to.have.length(0)
done()
}).error(function(err) {
expect(err).to.deep.equal({})
expect(err[0].message).to.equal()
done()
})
})
......@@ -525,8 +568,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
.validate()
.success(function(error) {
expect(error).not.to.be.null
expect(error).to.be.instanceOf(Error);
expect(error.age).to.deep.equal(["must be positive"])
expect(error).to.be.an.instanceOf(Error)
expect(error.age[0].message).to.equal("must be positive")
User.build({ age: null }).validate().success(function() {
User.build({ age: 1 }).validate().success(function() {
......@@ -563,9 +606,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
.validate()
.success(function(error) {
expect(error).not.to.be.null
expect(error).to.be.instanceOf(Error)
expect(error.xnor).to.deep.equal(['xnor failed']);
expect(error).to.be.an.instanceOf(Error)
expect(error.xnor[0].message).to.equal('xnor failed');
Foo
.build({ field1: 33, field2: null })
.validate()
......@@ -601,8 +643,8 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
.validate()
.success(function(error) {
expect(error).not.to.be.null
expect(error).to.be.instanceOf(Error)
expect(error.xnor).to.deep.equal(['xnor failed']);
expect(error).to.be.an.instanceOf(Error)
expect(error.xnor[0].message).to.equal('xnor failed')
Foo
.build({ field1: 33, field2: null })
......@@ -652,7 +694,7 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
failingBar.validate().success(function(errors) {
expect(errors).not.to.be.null
expect(errors.field).to.have.length(1)
expect(errors.field[0]).to.equal("Validation isIn failed")
expect(errors.field[0].message).to.equal("Validation isIn failed")
})
})
......
......@@ -464,7 +464,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return an error based on user', function(done) {
this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal([ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ])
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done()
})
})
......@@ -501,7 +501,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return an error based on the hook', function(done) {
this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal([ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ])
expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' )
done()
})
})
......@@ -552,7 +552,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return an error based on user', function(done) {
this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal([ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ])
expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' )
done()
})
})
......@@ -601,7 +601,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return an error based on the hook', function(done) {
this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal([ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ])
expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' )
done()
})
})
......@@ -698,7 +698,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return the user from the callback', function(done) {
this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal([ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ])
expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' )
done()
})
})
......@@ -720,7 +720,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('should return the error without the user within callback', function(done) {
this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal([ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ])
expect(err.mood[0].message).to.equal( 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' )
done()
})
})
......@@ -835,7 +835,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('#create', function(done) {
this.User.create({mood: 'creative'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal( [ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ] )
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done()
})
})
......@@ -859,7 +859,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('#create', function(done) {
this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal( [ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ] )
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done()
})
})
......@@ -940,7 +940,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal( [ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ] )
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done()
})
})
......@@ -954,7 +954,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal( [ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ] )
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done()
})
})
......@@ -1061,7 +1061,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('#create', function(done) {
this.User.create({mood: 'creative'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal( [ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ] )
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done()
})
})
......@@ -1085,7 +1085,7 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
it('#create', function(done) {
this.User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal( [ 'Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral' ] )
expect(err.mood[0].message).to.equal('Value "ecstatic" for ENUM mood is out of allowed scope. Allowed values: happy, sad, neutral')
done()
})
})
......
......@@ -124,11 +124,11 @@ if (Support.dialectIsMySQL()) {
User.sync({ force: true }).success(function() {
User.create({mood: 'happy'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal([ 'Value "happy" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver' ])
expect(err.mood[0].message).to.equal('Value "happy" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver')
var u = User.build({mood: 'SAD'})
u.save().error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.mood).to.deep.equal([ 'Value "SAD" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver' ])
expect(err.mood[0].message).to.equal('Value "SAD" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver')
done()
})
})
......
......@@ -277,7 +277,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
expect(err).to.be.an("object")
expect(err.validateTest).to.be.an("array")
expect(err.validateTest[0]).to.be.ok
expect(err.validateTest[0]).to.equal('Validation isInt failed')
expect(err.validateTest[0].message).to.equal('Validation isInt failed')
done()
});
})
......@@ -290,7 +290,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
expect(err.validateCustom).to.be.ok
expect(err.validateCustom).to.be.an("array")
expect(err.validateCustom[0]).to.be.ok
expect(err.validateCustom[0]).to.equal('Length failed.')
expect(err.validateCustom[0].message).to.equal('Length failed.')
done()
})
})
......@@ -304,7 +304,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
expect(err.validateTest).to.be.ok
expect(err.validateTest).to.be.an("array")
expect(err.validateTest[0]).to.be.ok
expect(err.validateTest[0]).to.equal('Validation isInt failed')
expect(err.validateTest[0].message).to.equal('Validation isInt failed')
done()
})
})
......@@ -322,7 +322,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
expect(user.id).to.equal(1)
expect(arguments.length).to.equal(1)
done()
})
})
})
describe('with spread', function () {
......@@ -334,7 +334,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
expect(created).to.equal(false)
expect(arguments.length).to.equal(2)
done()
})
})
})
it('user created', function (done) {
this.User
......@@ -344,7 +344,7 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
expect(created).to.equal(true)
expect(arguments.length).to.equal(2)
done()
})
})
})
it('works for functions with only one return value', function (done) {
this.User
......@@ -353,8 +353,8 @@ describe(Support.getTestDialectTeaser("Promise"), function () {
expect(user.id).to.equal(1)
expect(arguments.length).to.equal(1)
done()
})
})
})
})
})
})
})
......@@ -86,6 +86,10 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
if (dialect === 'mariadb') {
expect(err.message).to.match(/Access denied for user/)
} else if (dialect === 'postgres') {
// When the test is run with only it produces:
// Error: Error: Failed to authenticate for PostgresSQL. Please double check your settings.
// When its run with all the other tests it produces:
// Error: invalid port number: "99999"
expect(err.message).to.match(/invalid port number/)
} else {
expect(err.message).to.match(/Failed to authenticate/)
......@@ -713,7 +717,7 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
it("doesn't save an instance if value is not in the range of enums", function(done) {
this.Review.create({status: 'fnord'}).error(function(err) {
expect(err).to.be.instanceOf(Error);
expect(err.status).to.deep.equal([ 'Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished' ])
expect(err.status[0].message).to.equal('Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished')
done()
})
})
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!