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
This commit is contained in:
Bert Casier 2015-11-04 11:05:24 +01:00
parent 46bdb063c4
commit 0f3f27af51
2 changed files with 578 additions and 9 deletions

View File

@ -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;
};

View File

@ -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));
});
});
});
});
});