// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Node module: loopback-datasource-juggler // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT 'use strict'; // This test written in mocha+should.js /* global getSchema:false */ var should = require('./init.js'); var j = require('../'), Schema = j.Schema, AbstractClass = j.AbstractClass, Hookable = j.Hookable, db, User; describe('hooks', function() { before(function(done) { db = getSchema(); User = db.define('User', { email: {type: String, index: true}, name: String, password: String, state: String, }); db.automigrate('User', done); }); describe('initialize', function() { afterEach(function() { User.afterInitialize = null; }); it('should be triggered on new', function(done) { User.afterInitialize = function() { done(); }; new User; }); it('should be triggered on create', function(done) { var user; User.afterInitialize = function() { if (this.name === 'Nickolay') { this.name += ' Rozental'; } }; User.create({name: 'Nickolay'}, function(err, u) { u.id.should.be.ok; u.name.should.equal('Nickolay Rozental'); done(); }); }); }); describe('create', function() { afterEach(removeHooks('Create')); it('should be triggered on create', function(done) { addHooks('Create', done); User.create(); }); it('should not be triggered on new', function() { User.beforeCreate = function(next) { should.fail('This should not be called'); next(); }; var u = new User; }); it('should be triggered on new+save', function(done) { addHooks('Create', done); (new User).save(); }); it('afterCreate should not be triggered on failed create', function(done) { var old = User.dataSource.connector.create; User.dataSource.connector.create = function(modelName, id, cb) { cb(new Error('error')); }; User.afterCreate = function() { throw new Error('shouldn\'t be called'); }; User.create(function(err, user) { User.dataSource.connector.create = old; done(); }); }); it('afterCreate should not be triggered on failed beforeCreate', function(done) { User.beforeCreate = function(next, data) { // Skip next() next(new Error('fail in beforeCreate')); }; var old = User.dataSource.connector.create; User.dataSource.connector.create = function(modelName, id, cb) { throw new Error('shouldn\'t be called'); }; User.afterCreate = function() { throw new Error('shouldn\'t be called'); }; User.create(function(err, user) { User.dataSource.connector.create = old; done(); }); }); }); describe('save', function() { afterEach(removeHooks('Save')); it('should be triggered on create', function(done) { addHooks('Save', done); User.create(); }); it('should be triggered on new+save', function(done) { addHooks('Save', done); (new User).save(); }); it('should be triggered on updateAttributes', function(done) { User.create(function(err, user) { addHooks('Save', done); user.updateAttributes({name: 'Anatoliy'}); }); }); it('should be triggered on save', function(done) { User.create(function(err, user) { addHooks('Save', done); user.name = 'Hamburger'; user.save(); }); }); it('should save full object', function(done) { User.create(function(err, user) { User.beforeSave = function(next, data) { data.should.have.keys('id', 'name', 'email', 'password', 'state'); done(); }; user.save(); }); }); it('should save actual modifications to database', function(done) { User.beforeSave = function(next, data) { data.password = 'hash'; next(); }; User.destroyAll(function() { User.create({ email: 'james.bond@example.com', password: '53cr3t', }, function() { User.findOne({ where: {email: 'james.bond@example.com'}, }, function(err, jb) { jb.password.should.equal('hash'); done(); }); }); }); }); it('should save actual modifications on updateAttributes', function(done) { User.beforeSave = function(next, data) { data.password = 'hash'; next(); }; User.destroyAll(function() { User.create({ email: 'james.bond@example.com', }, function(err, u) { u.updateAttribute('password', 'new password', function(e, u) { should.not.exist(e); should.exist(u); u.password.should.equal('hash'); User.findOne({ where: {email: 'james.bond@example.com'}, }, function(err, jb) { jb.password.should.equal('hash'); done(); }); }); }); }); }); it('beforeSave should be able to skip next', function(done) { User.create(function(err, user) { User.beforeSave = function(next, data) { next(null, 'XYZ'); }; user.save(function(err, result) { result.should.be.eql('XYZ'); done(); }); }); }); }); describe('update', function() { afterEach(removeHooks('Update')); it('should not be triggered on create', function() { User.beforeUpdate = function(next) { should.fail('This should not be called'); next(); }; User.create(); }); it('should not be triggered on new+save', function() { User.beforeUpdate = function(next) { should.fail('This should not be called'); next(); }; (new User).save(); }); it('should be triggered on updateAttributes', function(done) { User.create(function(err, user) { addHooks('Update', done); user.updateAttributes({name: 'Anatoliy'}); }); }); it('should be triggered on save', function(done) { User.create(function(err, user) { addHooks('Update', done); user.name = 'Hamburger'; user.save(); }); }); it('should update limited set of fields', function(done) { User.create(function(err, user) { User.beforeUpdate = function(next, data) { data.should.have.keys('name', 'email'); done(); }; user.updateAttributes({name: 1, email: 2}); }); }); it('should not trigger after-hook on failed save', function(done) { User.afterUpdate = function() { should.fail('afterUpdate shouldn\'t be called'); }; User.create(function(err, user) { var save = User.dataSource.connector.save; User.dataSource.connector.save = function(modelName, id, cb) { User.dataSource.connector.save = save; cb(new Error('Error')); }; user.save(function(err) { done(); }); }); }); }); describe('destroy', function() { afterEach(removeHooks('Destroy')); it('should be triggered on destroy', function(done) { var hook = 'not called'; User.beforeDestroy = function(next) { hook = 'called'; next(); }; User.afterDestroy = function(next) { hook.should.eql('called'); next(); }; User.create(function(err, user) { user.destroy(done); }); }); it('should not trigger after-hook on failed destroy', function(done) { var destroy = User.dataSource.connector.destroy; User.dataSource.connector.destroy = function(modelName, id, cb) { cb(new Error('error')); }; User.afterDestroy = function() { should.fail('afterDestroy shouldn\'t be called'); }; User.create(function(err, user) { user.destroy(function(err) { User.dataSource.connector.destroy = destroy; done(); }); }); }); }); describe('lifecycle', function() { var life = [], user; before(function(done) { User.beforeSave = function(d) { life.push('beforeSave'); d(); }; User.beforeCreate = function(d) { life.push('beforeCreate'); d(); }; User.beforeUpdate = function(d) { life.push('beforeUpdate'); d(); }; User.beforeDestroy = function(d) { life.push('beforeDestroy'); d(); }; User.beforeValidate = function(d) { life.push('beforeValidate'); d(); }; User.afterInitialize = function() { life.push('afterInitialize'); }; User.afterSave = function(d) { life.push('afterSave'); d(); }; User.afterCreate = function(d) { life.push('afterCreate'); d(); }; User.afterUpdate = function(d) { life.push('afterUpdate'); d(); }; User.afterDestroy = function(d) { life.push('afterDestroy'); d(); }; User.afterValidate = function(d) { life.push('afterValidate'); d(); }; User.create(function(e, u) { user = u; life = []; done(); }); }); beforeEach(function() { life = []; }); it('should describe create sequence', function(done) { User.create(function() { life.should.eql([ 'afterInitialize', 'beforeValidate', 'afterValidate', 'beforeCreate', 'beforeSave', 'afterSave', 'afterCreate', ]); done(); }); }); it('should describe new+save sequence', function(done) { var u = new User; u.save(function() { life.should.eql([ 'afterInitialize', 'beforeValidate', 'afterValidate', 'beforeCreate', 'beforeSave', 'afterSave', 'afterCreate', ]); done(); }); }); it('should describe updateAttributes sequence', function(done) { user.updateAttributes({name: 'Antony'}, function() { life.should.eql([ 'beforeValidate', 'afterValidate', 'beforeSave', 'beforeUpdate', 'afterUpdate', 'afterSave', ]); done(); }); }); it('should describe isValid sequence', function(done) { should.not.exist( user.constructor._validations, 'Expected user to have no validations, but she have' ); user.isValid(function(valid) { valid.should.be.true; life.should.eql([ 'beforeValidate', 'afterValidate', ]); done(); }); }); it('should describe destroy sequence', function(done) { user.destroy(function() { life.should.eql([ 'beforeDestroy', 'afterDestroy', ]); done(); }); }); }); }); function addHooks(name, done) { var called = false, random = String(Math.floor(Math.random() * 1000)); User['before' + name] = function(next, data) { called = true; data.email = random; next(); }; User['after' + name] = function(next) { (new Boolean(called)).should.equal(true); this.should.have.property('email', random); done(); }; } function removeHooks(name) { return function() { User['after' + name] = null; User['before' + name] = null; }; }