diff --git a/lib/dao.js b/lib/dao.js index d314a288..e2318d18 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -2689,7 +2689,21 @@ DataAccessObject.updateAll = function(where, data, options, cb) { Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); - doUpdate(ctx.where, ctx.data); + + data = ctx.data; + var inst = data; + if (!(data instanceof Model)) { + inst = new Model(data, {applyDefaultValues: false}); + } + + // validation required + inst.isValid(function(valid) { + if (valid) { + doUpdate(ctx.where, ctx.data); + } else { + cb(new ValidationError(inst), inst); + } + }, options); }); }); diff --git a/test/manipulation.test.js b/test/manipulation.test.js index b6673115..3bef0926 100644 --- a/test/manipulation.test.js +++ b/test/manipulation.test.js @@ -2259,6 +2259,8 @@ describe('manipulation', function() { }); it('should not coerce invalid values provided in where conditions', function(done) { + // remove the validations since update/updateAll validates the model + delete Person.validations; Person.update({name: 'Brett Boe'}, {dob: 'notadate'}, function(err) { should.exist(err); err.message.should.equal('Invalid date: notadate'); diff --git a/test/validations.test.js b/test/validations.test.js index 6f3d6ac2..c75d6853 100644 --- a/test/validations.test.js +++ b/test/validations.test.js @@ -26,7 +26,7 @@ function getValidAttributes() { } describe('validations', function() { - var User, Entry; + var User, Entry, Employee; before(function(done) { db = getSchema(); @@ -47,8 +47,16 @@ describe('validations', function() { id: {type: 'string', id: true, generated: false}, name: {type: 'string'}, }); + Employee = db.define('Employee', { + id: {type: Number, id: true, generated: false}, + name: {type: String}, + age: {type: Number}, + }); Entry.validatesUniquenessOf('id'); - db.automigrate(done); + db.automigrate(function(err) { + should.not.exist(err); + Employee.create(empData, done); + }); }); beforeEach(function(done) { @@ -58,8 +66,8 @@ describe('validations', function() { }); }); - after(function() { - // db.disconnect(); + after(function(done) { + Employee.destroyAll(done); }); describe('commons', function() { @@ -390,6 +398,51 @@ describe('validations', function() { user.domain = 'domain'; user.isValid().should.be.true; }); + + describe('validate presence on update', function() { + before(function(done) { + Employee.destroyAll(function(err) { + should.not.exist(err); + delete Employee.validations; + db.automigrate('Employee', function(err) { + should.not.exist(err); + Employee.create(empData, function(err, inst) { + should.not.exist(err); + should.exist(inst); + Employee.validatesPresenceOf('name', 'age'); + done(); + }); + }); + }); + }); + + it('succeeds when validate condition is met', function(done) { + var data = {name: 'Foo-new', age: 5}; + Employee.updateAll({id: 1}, data, + function(err, emp) { + should.not.exist(err); + should.exist(emp); + should.equal(emp.count, 1); + Employee.find({where: {id: 1}}, function(err, emp) { + should.not.exist(err); + should.exist(emp); + data.id = 1; + should.deepEqual(data, emp[0].toObject()); + done(); + }); + }); + }); + + it('throws err when validate condition is not met', function(done) { + Employee.updateAll({where: {id: 1}}, {name: 'Foo-new'}, + function(err, emp) { + should.exist(err); + should.equal(err.statusCode, 422); + should.equal(err.details.messages.age[0], 'can\'t be blank'); + done(); + }); + }); + }); }); describe('absence', function() { @@ -402,6 +455,52 @@ describe('validations', function() { u = new User({reserved: 'foo', locked: false}); u.isValid().should.be.true; }); + + describe('validate absence on update', function() { + before(function(done) { + Employee.destroyAll(function(err) { + should.not.exist(err); + delete Employee.validations; + db.automigrate('Employee', function(err) { + should.not.exist(err); + Employee.create(empData, function(err, inst) { + should.not.exist(err); + should.exist(inst); + Employee.validatesAbsenceOf('name'); + done(); + }); + }); + }); + }); + + it('succeeds when validate condition is met', function(done) { + var data = {age: 5}; + Employee.updateAll({id: 1}, data, + function(err, emp) { + should.not.exist(err); + should.exist(emp); + should.equal(emp.count, 1); + Employee.find({where: {id: 1}}, function(err, emp) { + should.not.exist(err); + should.exist(emp); + data.id = 1; + data.name = 'Foo'; + should.deepEqual(data, emp[0].toObject()); + done(); + }); + }); + }); + + it('throws err when validate condition is not met', function(done) { + Employee.updateAll({where: {id: 1}}, {name: 'Foo-new', age: 5}, + function(err, emp) { + should.exist(err); + should.equal(err.statusCode, 422); + should.equal(err.details.messages.name[0], 'can\'t be set'); + done(); + }); + }); + }); }); describe('uniqueness', function() { @@ -613,6 +712,51 @@ describe('validations', function() { }); })).should.be.false(); }); + + describe('validate uniqueness on update', function() { + before(function(done) { + Employee.destroyAll(function(err) { + should.not.exist(err); + delete Employee.validations; + db.automigrate('Employee', function(err) { + should.not.exist(err); + Employee.create(empData, function(err, inst) { + should.not.exist(err); + should.exist(inst); + Employee.validatesUniquenessOf('name'); + done(); + }); + }); + }); + }); + + it('succeeds when validate condition is met', function(done) { + var data = {name: 'Foo-new', age: 5}; + Employee.updateAll({id: 1}, data, + function(err, emp) { + should.not.exist(err); + should.exist(emp); + should.equal(emp.count, 1); + Employee.find({where: {id: 1}}, function(err, emp) { + should.not.exist(err); + should.exist(emp); + data.id = 1; + should.deepEqual(data, emp[0].toObject()); + done(); + }); + }); + }); + + it('throws err when validate condition is not met', function(done) { + Employee.updateAll({where: {id: 1}}, {name: 'Bar', age: 5}, + function(err, emp) { + should.exist(err); + should.equal(err.statusCode, 422); + should.equal(err.details.messages.name[0], 'is not unique'); + done(); + }); + }); + }); }); describe('format', function() { @@ -642,6 +786,51 @@ describe('validations', function() { var u = new User({email: null}); u.isValid().should.be.false; }); + + describe('validate format on update', function() { + before(function(done) { + Employee.destroyAll(function(err) { + should.not.exist(err); + delete Employee.validations; + db.automigrate('Employee', function(err) { + should.not.exist(err); + Employee.create(empData, function(err, inst) { + should.not.exist(err); + should.exist(inst); + Employee.validatesFormatOf('name', {with: /^\w+\s\w+$/, allowNull: false}); + done(); + }); + }); + }); + }); + + it('succeeds when validate condition is met', function(done) { + var data = {name: 'Foo Mo', age: 5}; + Employee.updateAll({id: 1}, data, + function(err, emp) { + should.not.exist(err); + should.exist(emp); + should.equal(emp.count, 1); + Employee.find({where: {id: 1}}, function(err, emp) { + should.not.exist(err); + should.exist(emp); + data.id = 1; + should.deepEqual(data, emp[0].toObject()); + done(); + }); + }); + }); + + it('throws err when validate condition is not met', function(done) { + Employee.updateAll({where: {id: 1}}, {name: '45foo', age: 5}, + function(err, emp) { + should.exist(err); + should.equal(err.statusCode, 422); + should.equal(err.details.messages.name[0], 'is invalid'); + done(); + }); + }); + }); }); describe('numericality', function() { @@ -703,6 +892,51 @@ describe('validations', function() { user.isValid().should.be.false(); user.errors.should.match({age: /is not an integer/}); }); + + describe('validate numericality on update', function() { + before(function(done) { + Employee.destroyAll(function(err) { + should.not.exist(err); + delete Employee.validations; + db.automigrate('Employee', function(err) { + should.not.exist(err); + Employee.create(empData, function(err, inst) { + should.not.exist(err); + should.exist(inst); + Employee.validatesNumericalityOf('age'); + done(); + }); + }); + }); + }); + + it('succeeds when validate condition is met', function(done) { + var data = {name: 'Foo-new', age: 5}; + Employee.updateAll({id: 1}, data, + function(err, emp) { + should.not.exist(err); + should.exist(emp); + should.equal(emp.count, 1); + Employee.find({where: {id: 1}}, function(err, emp) { + should.not.exist(err); + should.exist(emp); + data.id = 1; + should.deepEqual(data, emp[0].toObject()); + done(); + }); + }); + }); + + it('throws err when validate condition is not met', function(done) { + Employee.updateAll({where: {id: 1}}, {age: {someAge: 5}}, + function(err, emp) { + should.exist(err); + should.equal(err.statusCode, 422); + should.equal(err.details.messages.age[0], 'is not a number'); + done(); + }); + }); + }); }); describe('inclusion', function() { @@ -774,6 +1008,52 @@ describe('validations', function() { function getErrorDetails(err) { return err.message.replace(/^.*Details: /, ''); } + + describe('validate inclusion on update', function() { + before(function(done) { + Employee.destroyAll(function(err) { + should.not.exist(err); + delete Employee.validations; + db.automigrate('Employee', function(err) { + should.not.exist(err); + Employee.create(empData, function(err, inst) { + should.not.exist(err); + should.exist(inst); + Employee.validatesInclusionOf('name', {in: ['Foo-new']}); + done(); + }); + }); + }); + }); + + it('succeeds when validate condition is met', function(done) { + var data = {name: 'Foo-new', age: 5}; + Employee.updateAll({id: 1}, data, + function(err, emp) { + should.not.exist(err); + should.exist(emp); + should.equal(emp.count, 1); + Employee.find({where: {id: 1}}, function(err, emp) { + should.not.exist(err); + should.exist(emp); + data.id = 1; + should.deepEqual(data, emp[0].toObject()); + done(); + }); + }); + }); + + it('throws err when validate condition is not met', function(done) { + Employee.updateAll({where: {id: 1}}, {name: 'Foo-new2', age: 5}, + function(err, emp) { + should.exist(err); + should.equal(err.statusCode, 422); + should.equal(err.details.messages.name[0], 'is not included in ' + + 'the list'); + done(); + }); + }); + }); }); describe('exclusion', function() { @@ -826,10 +1106,100 @@ describe('validations', function() { done(); }); }); + + describe('validate exclusion on update', function() { + before(function(done) { + Employee.destroyAll(function(err) { + should.not.exist(err); + delete Employee.validations; + db.automigrate('Employee', function(err) { + should.not.exist(err); + Employee.create(empData, function(err, inst) { + should.not.exist(err); + should.exist(inst); + Employee.validatesExclusionOf('name', {in: ['Bob']}); + done(); + }); + }); + }); + }); + + it('succeeds when validate condition is met', function(done) { + var data = {name: 'Foo-new', age: 5}; + Employee.updateAll({id: 1}, data, + function(err, emp) { + should.not.exist(err); + should.exist(emp); + should.equal(emp.count, 1); + Employee.find({where: {id: 1}}, function(err, emp) { + should.not.exist(err); + should.exist(emp); + data.id = 1; + should.deepEqual(data, emp[0].toObject()); + done(); + }); + }); + }); + + it('throws err when validate condition is not met', function(done) { + Employee.updateAll({where: {id: 1}}, {name: 'Bob', age: 5}, + function(err, emp) { + should.exist(err); + should.equal(err.statusCode, 422); + should.equal(err.details.messages.name[0], 'is reserved'); + done(); + }); + }); + }); }); describe('length', function() { it('should validate length'); + + describe('validate length on update', function() { + before(function(done) { + Employee.destroyAll(function(err) { + should.not.exist(err); + delete Employee.validations; + db.automigrate('Employee', function(err) { + should.not.exist(err); + Employee.create(empData, function(err, inst) { + should.not.exist(err); + should.exist(inst); + Employee.validatesLengthOf('name', {min: 5}); + done(); + }); + }); + }); + }); + + it('succeeds when validate condition is met', function(done) { + var data = {name: 'Foo-new', age: 5}; + Employee.updateAll({id: 1}, data, + function(err, emp) { + should.not.exist(err); + should.exist(emp); + should.equal(emp.count, 1); + Employee.find({where: {id: 1}}, function(err, emp) { + should.not.exist(err); + should.exist(emp); + data.id = 1; + should.deepEqual(data, emp[0].toObject()); + done(); + }); + }); + }); + + it('throws err when validate condition is not met', function(done) { + Employee.updateAll({where: {id: 1}}, {name: 'Bob', age: 5}, + function(err, emp) { + should.exist(err); + should.equal(err.statusCode, 422); + should.equal(err.details.messages.name[0], 'too short'); + done(); + }); + }); + }); }); describe('custom', function() { @@ -938,6 +1308,7 @@ describe('validations', function() { return err.message.replace(/^.*Details: /, ''); } }); + describe('date', function() { it('should validate a date object', function() { User.validatesDateOf('updatedAt'); @@ -1013,3 +1384,17 @@ describe('validations', function() { }); }); }); + +var empData = [{ + id: 1, + name: 'Foo', + age: 1, +}, { + id: 2, + name: 'Bar', + age: 2, +}, { + id: 3, + name: 'Baz', + age: 3, +}];