From 0f3f27af513fd1332d5e5de11591e237d3dcb273 Mon Sep 17 00:00:00 2001 From: Bert Casier <bert@ernie.be> Date: Wed, 4 Nov 2015 11:05:24 +0100 Subject: [PATCH] Make automatic validation optional Make automatic validation optional on all CRUD methods in a loopback model. This can be done in 2 ways - set `automaticValidation` in the model settings - set `validate` on the options passed when calling the crud methods The options take precedence on the model setting. By default the automatic validation remains true to be backwards compatible --- lib/dao.js | 73 ++++- test/optional-validation.test.js | 514 +++++++++++++++++++++++++++++++ 2 files changed, 578 insertions(+), 9 deletions(-) create mode 100644 test/optional-validation.test.js diff --git a/lib/dao.js b/lib/dao.js index fc096aed..4a812e45 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -257,6 +257,16 @@ DataAccessObject.create = function (data, options, cb) { data = obj.toObject(true); + // options has precedence on model-setting + if (options.validate === false) { + return create(); + } + + // only when options.validate is not set, take model-setting into consideration + if (options.validate === undefined && Model.settings.automaticValidation === false) { + return create(); + } + // validation required obj.isValid(function (valid) { if (valid) { @@ -457,12 +467,25 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data var connector = self.getConnector(); - if (Model.settings.validateUpsert === false) { + var doValidate = undefined; + if (options.validate === undefined) { + if (Model.settings.validateUpsert === undefined) { + if (Model.settings.automaticValidation !== undefined) { + doValidate = Model.settings.automaticValidation; + } + } else { + doValidate = Model.settings.validateUpsert + } + } else { + doValidate = options.validate; + } + + if (doValidate === false) { callConnector(); } else { inst.isValid(function(valid) { if (!valid) { - if (Model.settings.validateUpsert) { + if (doValidate) { // backwards compatibility with validateUpsert:undefined return cb(new ValidationError(inst), inst); } else { // TODO(bajtos) Remove validateUpsert:undefined in v3.0 @@ -732,6 +755,16 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) var obj = ctx.instance; var data = obj.toObject(true); + // options has precedence on model-setting + if (options.validate === false) { + _findOrCreate(query, data, obj); + } + + // only when options.validate is not set, take model-setting into consideration + if (options.validate === undefined && Model.settings.automaticValidation === false) { + _findOrCreate(query, data, obj); + } + // validation required obj.isValid(function (valid) { if (valid) { @@ -1876,8 +1909,13 @@ DataAccessObject.prototype.save = function (options, cb) { var hookState = {}; if (options.validate === undefined) { - options.validate = true; + if (Model.settings.automaticValidation === undefined) { + options.validate = true; + } else { + options.validate = Model.settings.automaticValidation; + } } + if (options.throws === undefined) { options.throws = false; } @@ -2422,15 +2460,32 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op data = removeUndefined(result); } + var doValidate = true; + if (options.validate === undefined) { + if (Model.settings.automaticValidation !== undefined) { + doValidate = Model.settings.automaticValidation; + } + } else { + doValidate = options.validate; + } + // update instance's properties inst.setAttributes(data); - inst.isValid(function (valid) { - if (!valid) { - cb(new ValidationError(inst), inst); - return; - } + if (doValidate){ + inst.isValid(function (valid) { + if (!valid) { + cb(new ValidationError(inst), inst); + return; + } + triggerSave(); + }, data); + } else { + triggerSave(); + } + + function triggerSave(){ inst.trigger('save', function (saveDone) { inst.trigger('update', function (done) { var typedData = {}; @@ -2506,7 +2561,7 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op }); }, data, cb); }, data, cb); - }, data); + } }); return cb.promise; }; diff --git a/test/optional-validation.test.js b/test/optional-validation.test.js new file mode 100644 index 00000000..ac432eb6 --- /dev/null +++ b/test/optional-validation.test.js @@ -0,0 +1,514 @@ +// This test written in mocha+should.js +var should = require('./init.js'); +var db, User, options, whereCount = 0; +var j = require('../'); +var ValidationError = j.ValidationError; + +var INITIAL_NAME = 'Bert'; +var NEW_NAME = 'Ernie'; +var INVALID_DATA = {name: null}; +var VALID_DATA = {name: INITIAL_NAME}; + +describe('optional-validation', function () { + + before(function (done) { + db = getSchema(); + User = db.define('User', { + seq: {type: Number, index: true}, + name: {type: String, index: true, sort: true}, + email: {type: String, index: true}, + birthday: {type: Date, index: true}, + role: {type: String, index: true}, + order: {type: Number, index: true, sort: true}, + vip: {type: Boolean} + }, { forceId: true, strict: true }); + + db.automigrate(['User'], done); + + }); + + beforeEach(function (done) { + User.destroyAll(function () { + delete User.validations; + User.validatesPresenceOf('name'); + done(); + }); + }); + + function expectValidationError(done) { + return function (err, result) { + should.exist(err); + err.should.be.instanceOf(Error); + err.should.be.instanceOf(ValidationError); + done(); + }; + } + + function expectCreateSuccess(data, done) { + if (done === undefined && typeof data === 'function') { + done = data; + data = { name: INITIAL_NAME }; + } + return function(err, instance) { + if (err) return done(err); + instance.should.be.instanceOf(User); + if (data.name) { + instance.name.should.eql(data.name || INITIAL_NAME); + } else { + should.not.exist(instance.name); + } + done(); + }; + } + + function expectChangeSuccess(data, done) { + if (done === undefined && typeof data === 'function') { + done = data; + data = { name: NEW_NAME }; + } + return function(err, instance) { + if (err) return done(err); + instance.should.be.instanceOf(User); + if (data.name) { + instance.name.should.eql(data.name || NEW_NAME); + } else { + should.not.exist(instance.name); + } + done(); + }; + } + + function createUserAndChangeName(name, cb) { + User.create(VALID_DATA, {validate: true}, function (err, d) { + d.name = name; + cb(err, d); + }); + } + + function createUser(cb) { + User.create(VALID_DATA, {validate: true}, cb); + } + + function callUpdateOrCreateWithExistingUserId(name, options, cb){ + User.create({'name': 'Groover'}, function(err, user){ + if (err) return cb(err); + var data = {name: name}; + data.id = user.id; + User.updateOrCreate(data, options, cb); + }); + } + + function getNewWhere() { + return {name: 'DoesNotExist' + (whereCount++)}; + } + + describe('no model setting', function () { + + describe('method create', function() { + it('should throw on create with validate:true with invalid data', function (done) { + User.create(INVALID_DATA, {validate: true}, expectValidationError(done)); + }); + + it('should NOT throw on create with validate:false with invalid data', function (done) { + User.create(INVALID_DATA, {validate: false}, expectCreateSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on create with validate:true with valid data', function (done) { + User.create(VALID_DATA, {validate: true}, expectCreateSuccess(done)); + }); + + it('should NOT throw on create with validate:false with valid data', function (done) { + User.create(VALID_DATA, {validate: false}, expectCreateSuccess(done)); + }); + + it('should throw on create with invalid data', function (done) { + User.create(INVALID_DATA, expectValidationError(done)); + }); + + it('should NOT throw on create with valid data', function (done) { + User.create(VALID_DATA, expectCreateSuccess(done)); + }); + }); + + describe('method findOrCreate', function() { + it('should throw on findOrCreate with validate:true with invalid data', function (done) { + User.findOrCreate(getNewWhere(), INVALID_DATA, {validate: true}, expectValidationError(done)); + }); + + it('should NOT throw on findOrCreate with validate:false with invalid data', function (done) { + User.findOrCreate(getNewWhere(), INVALID_DATA, {validate: false}, expectCreateSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on findOrCreate with validate:true with valid data', function (done) { + User.findOrCreate(getNewWhere(), VALID_DATA, {validate: true}, expectCreateSuccess(done)); + }); + + it('should NOT throw on findOrCreate with validate:false with valid data', function (done) { + User.findOrCreate(getNewWhere(), VALID_DATA, {validate: false}, expectCreateSuccess(done)); + }); + + it('should throw on findOrCreate with invalid data', function (done) { + User.findOrCreate(getNewWhere(), INVALID_DATA, expectValidationError(done)); + }); + + it('should NOT throw on findOrCreate with valid data', function (done) { + User.findOrCreate(getNewWhere(), VALID_DATA, expectCreateSuccess(done)); + }); + }); + + describe('method updateOrCreate on existing data', function() { + it('should throw on updateOrCreate(id) with validate:true with invalid data', function (done) { + callUpdateOrCreateWithExistingUserId(null, {validate: true}, expectValidationError(done)); + }); + + it('should NOT throw on updateOrCreate(id) with validate:false with invalid data', function (done) { + callUpdateOrCreateWithExistingUserId(null, {validate: false}, expectChangeSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on updateOrCreate(id) with validate:true with valid data', function (done) { + callUpdateOrCreateWithExistingUserId(NEW_NAME, {validate: true}, expectChangeSuccess(done)); + }); + + it('should NOT throw on updateOrCreate(id) with validate:false with valid data', function (done) { + callUpdateOrCreateWithExistingUserId(NEW_NAME, {validate: false}, expectChangeSuccess(done)); + }); + + // backwards compatible with validateUpsert + it('should NOT throw on updateOrCreate(id) with invalid data', function (done) { + callUpdateOrCreateWithExistingUserId(null, expectChangeSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on updateOrCreate(id) with valid data', function (done) { + callUpdateOrCreateWithExistingUserId(NEW_NAME, expectChangeSuccess(done)); + }); + }); + + describe('method save', function() { + it('should throw on save with {validate:true} with invalid data', function (done) { + createUserAndChangeName(null, function (err, d) { + d.save({validate: true}, expectValidationError(done)); + }); + }); + + it('should NOT throw on save with {validate:false} with invalid data', function (done) { + createUserAndChangeName(null, function (err, d) { + d.save({validate: false}, expectChangeSuccess(INVALID_DATA, done)); + }); + }); + + it('should NOT throw on save with {validate:true} with valid data', function (done) { + createUserAndChangeName(NEW_NAME, function (err, d) { + d.save({validate: true}, expectChangeSuccess(done)); + }); + }); + + it('should NOT throw on save with {validate:false} with valid data', function (done) { + createUserAndChangeName(NEW_NAME, function (err, d) { + d.save({validate: false}, expectChangeSuccess(done)); + }); + }); + + it('should throw on save(cb) with invalid data', function (done) { + createUserAndChangeName(null, function (err, d) { + d.save(expectValidationError(done)); + }); + }); + + it('should NOT throw on save(cb) with valid data', function (done) { + createUserAndChangeName(NEW_NAME, function (err, d) { + d.save(expectChangeSuccess(done)); + }); + }); + }); + + describe('method updateAttributes', function() { + it('should throw on updateAttributes with {validate:true} with invalid data', function (done) { + createUser(function (err, d) { + d.updateAttributes(INVALID_DATA, {validate: true}, expectValidationError(done)); + }); + }); + + it('should NOT throw on updateAttributes with {validate:false} with invalid data', function (done) { + createUser(function (err, d) { + d.updateAttributes(INVALID_DATA, {validate: false}, expectChangeSuccess(INVALID_DATA, done)); + }); + }); + + it('should NOT throw on updateAttributes with {validate:true} with valid data', function (done) { + createUser(function (err, d) { + d.updateAttributes({'name': NEW_NAME}, {validate: true}, expectChangeSuccess(done)); + }); + }); + + it('should NOT throw on updateAttributes with {validate:false} with valid data', function (done) { + createUser(function (err, d) { + d.updateAttributes({'name': NEW_NAME}, {validate: false}, expectChangeSuccess(done)); + }); + }); + + it('should throw on updateAttributes(cb) with invalid data', function (done) { + createUser(function (err, d) { + d.updateAttributes(INVALID_DATA, expectValidationError(done)); + }); + }); + + it('should NOT throw on updateAttributes(cb) with valid data', function (done) { + createUser(function (err, d) { + d.updateAttributes({'name': NEW_NAME}, expectChangeSuccess(done)); + }); + }); + }); + + }); + + describe('model setting: automaticValidation: false', function () { + + before(function (done) { + User.settings.automaticValidation = false; + done(); + }); + + describe('method create', function() { + it('should throw on create with validate:true with invalid data', function (done) { + User.create(INVALID_DATA, {validate: true}, expectValidationError(done)); + }); + + it('should NOT throw on create with validate:false with invalid data', function (done) { + User.create(INVALID_DATA, {validate: false}, expectCreateSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on create with validate:true with valid data', function (done) { + User.create(VALID_DATA, {validate: true}, expectCreateSuccess(done)); + }); + + it('should NOT throw on create with validate:false with valid data', function (done) { + User.create(VALID_DATA, {validate: false}, expectCreateSuccess(done)); + }); + + it('should NOT throw on create with invalid data', function (done) { + User.create(INVALID_DATA, expectCreateSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on create with valid data', function (done) { + User.create(VALID_DATA, expectCreateSuccess(done)); + }); + }); + + describe('method findOrCreate', function() { + it('should throw on findOrCreate with validate:true with invalid data', function (done) { + User.findOrCreate(getNewWhere(), INVALID_DATA, {validate: true}, expectValidationError(done)); + }); + + it('should NOT throw on findOrCreate with validate:false with invalid data', function (done) { + User.findOrCreate(getNewWhere(), INVALID_DATA, {validate: false}, expectCreateSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on findOrCreate with validate:true with valid data', function (done) { + User.findOrCreate(getNewWhere(), VALID_DATA, {validate: true}, expectCreateSuccess(done)); + }); + + it('should NOT throw on findOrCreate with validate:false with valid data', function (done) { + User.findOrCreate(getNewWhere(), VALID_DATA, {validate: false}, expectCreateSuccess(done)); + }); + + it('should NOT throw on findOrCreate with invalid data', function (done) { + User.findOrCreate(getNewWhere(), INVALID_DATA, expectCreateSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on findOrCreate with valid data', function (done) { + User.findOrCreate(getNewWhere(), VALID_DATA, expectCreateSuccess(done)); + }); + }); + + describe('method updateOrCreate on existing data', function() { + it('should throw on updateOrCreate(id) with validate:true with invalid data', function (done) { + callUpdateOrCreateWithExistingUserId(null, {validate: true}, expectValidationError(done)); + }); + + it('should NOT throw on updateOrCreate(id) with validate:false with invalid data', function (done) { + callUpdateOrCreateWithExistingUserId(null, {validate: false}, expectChangeSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on updateOrCreate(id) with validate:true with valid data', function (done) { + callUpdateOrCreateWithExistingUserId(NEW_NAME, {validate: true}, expectChangeSuccess(done)); + }); + + it('should NOT throw on updateOrCreate(id) with validate:false with valid data', function (done) { + callUpdateOrCreateWithExistingUserId(NEW_NAME, {validate: false}, expectChangeSuccess(done)); + }); + + it('should NOT throw on updateOrCreate(id) with invalid data', function (done) { + callUpdateOrCreateWithExistingUserId(null, expectChangeSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on updateOrCreate(id) with valid data', function (done) { + callUpdateOrCreateWithExistingUserId(NEW_NAME, expectChangeSuccess(done)); + }); + }); + + describe('method save', function() { + it('should throw on save with {validate:true} with invalid data', function (done) { + createUserAndChangeName(null, function (err, d) { + d.save({validate: true}, expectValidationError(done)); + }); + }); + + it('should NOT throw on save with {validate:false} with invalid data', function (done) { + createUserAndChangeName(null, function (err, d) { + d.save({validate: false}, expectChangeSuccess(INVALID_DATA, done)); + }); + }); + + it('should NOT throw on save with {validate:true} with valid data', function (done) { + createUserAndChangeName(NEW_NAME, function (err, d) { + d.save({validate: true}, expectChangeSuccess(done)); + }); + }); + + it('should NOT throw on save with {validate:false} with valid data', function (done) { + createUserAndChangeName(NEW_NAME, function (err, d) { + d.save({validate: false}, expectChangeSuccess(done)); + }); + }); + + it('should NOT throw on save(cb) with invalid data', function (done) { + createUserAndChangeName(null, function (err, d) { + d.save(expectChangeSuccess(INVALID_DATA, done)); + }); + }); + + it('should NOT throw on save(cb) with valid data', function (done) { + createUserAndChangeName(NEW_NAME, function (err, d) { + d.save(expectChangeSuccess(done)); + }); + }); + }); + + }); + + describe('model setting: automaticValidation: true', function () { + + before(function (done) { + User.settings.automaticValidation = true; + done(); + }); + + describe('method create', function() { + it('should throw on create with validate:true with invalid data', function (done) { + User.create(INVALID_DATA, {validate: true}, expectValidationError(done)); + }); + + it('should NOT throw on create with validate:false with invalid data', function (done) { + User.create(INVALID_DATA, {validate: false}, expectCreateSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on create with validate:true with valid data', function (done) { + User.create(VALID_DATA, {validate: true}, expectCreateSuccess(done)); + }); + + it('should NOT throw on create with validate:false with valid data', function (done) { + User.create(VALID_DATA, {validate: false}, expectCreateSuccess(done)); + }); + + it('should throw on create with invalid data', function (done) { + User.create(INVALID_DATA, expectValidationError(done)); + }); + + it('should NOT throw on create with valid data', function (done) { + User.create(VALID_DATA, expectCreateSuccess(done)); + }); + }); + + describe('method findOrCreate', function() { + it('should throw on findOrCreate with validate:true with invalid data', function (done) { + User.findOrCreate(getNewWhere(), INVALID_DATA, {validate: true}, expectValidationError(done)); + }); + + it('should NOT throw on findOrCreate with validate:false with invalid data', function (done) { + User.findOrCreate(getNewWhere(), INVALID_DATA, {validate: false}, expectCreateSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on findOrCreate with validate:true with valid data', function (done) { + User.findOrCreate(getNewWhere(), VALID_DATA, {validate: true}, expectCreateSuccess(done)); + }); + + it('should NOT throw on findOrCreate with validate:false with valid data', function (done) { + User.findOrCreate(getNewWhere(), VALID_DATA, {validate: false}, expectCreateSuccess(done)); + }); + + it('should throw on findOrCreate with invalid data', function (done) { + User.findOrCreate(getNewWhere(), INVALID_DATA, expectValidationError(done)); + }); + + it('should NOT throw on findOrCreate with valid data', function (done) { + User.findOrCreate(getNewWhere(), VALID_DATA, expectCreateSuccess(done)); + }); + }); + + describe('method updateOrCreate on existing data', function() { + it('should throw on updateOrCreate(id) with validate:true with invalid data', function (done) { + callUpdateOrCreateWithExistingUserId(null, {validate: true}, expectValidationError(done)); + }); + + it('should NOT throw on updateOrCreate(id) with validate:false with invalid data', function (done) { + callUpdateOrCreateWithExistingUserId(null, {validate: false}, expectChangeSuccess(INVALID_DATA, done)); + }); + + it('should NOT throw on updateOrCreate(id) with validate:true with valid data', function (done) { + callUpdateOrCreateWithExistingUserId(NEW_NAME, {validate: true}, expectChangeSuccess(done)); + }); + + it('should NOT throw on updateOrCreate(id) with validate:false with valid data', function (done) { + callUpdateOrCreateWithExistingUserId(NEW_NAME, {validate: false}, expectChangeSuccess(done)); + }); + + it('should throw on updateOrCreate(id) with invalid data', function (done) { + callUpdateOrCreateWithExistingUserId(null, expectValidationError(done)); + }); + + it('should NOT throw on updateOrCreate(id) with valid data', function (done) { + callUpdateOrCreateWithExistingUserId(NEW_NAME, expectChangeSuccess(done)); + }); + }); + + describe('method save', function() { + it('should throw on save with {validate:true} with invalid data', function (done) { + createUserAndChangeName(null, function (err, d) { + d.save(options, expectValidationError(done)); + }); + }); + + it('should NOT throw on save with {validate:false} with invalid data', function (done) { + createUserAndChangeName(null, function (err, d) { + d.save({validate: false}, expectChangeSuccess(INVALID_DATA, done)); + }); + }); + + it('should NOT throw on save with {validate:true} with valid data', function (done) { + createUserAndChangeName(NEW_NAME, function (err, d) { + d.save({validate: true}, expectChangeSuccess(done)); + }); + }); + + it('should NOT throw on save with {validate:false} with valid data', function (done) { + createUserAndChangeName(NEW_NAME, function (err, d) { + d.save({validate: false}, expectChangeSuccess(done)); + }); + }); + + it('should throw on save(cb) with invalid data', function (done) { + createUserAndChangeName(null, function (err, d) { + d.save(expectValidationError(done)); + }); + }); + + it('should NOT throw on save(cb) with valid data', function (done) { + createUserAndChangeName(NEW_NAME, function (err, d) { + d.save(expectChangeSuccess(done)); + }); + }); + }); + + }); + +});