'use strict'; const chai = require('chai'); const expect = chai.expect; const Support = require('../support'); const { DataTypes } = require('@sequelize/core'); const sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { beforeEach(async function () { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, allowNull: false, }, mood: { type: DataTypes.ENUM(['happy', 'sad', 'neutral']), }, }); this.ParanoidUser = this.sequelize.define( 'ParanoidUser', { username: DataTypes.STRING, mood: { type: DataTypes.ENUM(['happy', 'sad', 'neutral']), }, }, { paranoid: true, }, ); await this.sequelize.sync({ force: true }); }); describe('#bulkCreate', () => { describe('on success', () => { it('should run hooks', async function () { const beforeBulk = sinon.spy(); const afterBulk = sinon.spy(); this.User.beforeBulkCreate(beforeBulk); this.User.afterBulkCreate(afterBulk); await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' }, ]); expect(beforeBulk).to.have.been.calledOnce; expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { it('should return an error from before', async function () { this.User.beforeBulkCreate(() => { throw new Error('Whoops!'); }); await expect( this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' }, ]), ).to.be.rejected; }); it('should return an error from after', async function () { this.User.afterBulkCreate(() => { throw new Error('Whoops!'); }); await expect( this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' }, ]), ).to.be.rejected; }); }); describe('with the {individualHooks: true} option', () => { beforeEach(async function () { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, defaultValue: '', }, beforeHookTest: { type: DataTypes.BOOLEAN, defaultValue: false, }, aNumber: { type: DataTypes.INTEGER, defaultValue: 0, }, }); await this.User.sync({ force: true }); }); it('should run the afterCreate/beforeCreate functions for each item created successfully', async function () { let beforeBulkCreate = false; let afterBulkCreate = false; this.User.beforeBulkCreate(async () => { beforeBulkCreate = true; }); this.User.afterBulkCreate(async () => { afterBulkCreate = true; }); this.User.beforeCreate(async user => { user.beforeHookTest = true; }); this.User.afterCreate(async user => { user.username = `User${user.id}`; }); const records = await this.User.bulkCreate( [{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], { fields: ['aNumber'], individualHooks: true }, ); for (const record of records) { expect(record.username).to.equal(`User${record.id}`); expect(record.beforeHookTest).to.be.true; } expect(beforeBulkCreate).to.be.true; expect(afterBulkCreate).to.be.true; }); it('should run the afterCreate/beforeCreate functions for each item created with an error', async function () { let beforeBulkCreate = false; let afterBulkCreate = false; this.User.beforeBulkCreate(async () => { beforeBulkCreate = true; }); this.User.afterBulkCreate(async () => { afterBulkCreate = true; }); this.User.beforeCreate(async () => { throw new Error('You shall not pass!'); }); this.User.afterCreate(async user => { user.username = `User${user.id}`; }); try { await this.User.bulkCreate([{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], { fields: ['aNumber'], individualHooks: true, }); } catch (error) { expect(error).to.be.instanceOf(Error); expect(beforeBulkCreate).to.be.true; expect(afterBulkCreate).to.be.false; } }); }); }); describe('#bulkUpdate', () => { describe('on success', () => { it('should run hooks', async function () { const beforeBulk = sinon.spy(); const afterBulk = sinon.spy(); this.User.beforeBulkUpdate(beforeBulk); this.User.afterBulkUpdate(afterBulk); await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' }, ]); await this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } }); expect(beforeBulk).to.have.been.calledOnce; expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { it('should return an error from before', async function () { this.User.beforeBulkUpdate(() => { throw new Error('Whoops!'); }); await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' }, ]); await expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be .rejected; }); it('should return an error from after', async function () { this.User.afterBulkUpdate(() => { throw new Error('Whoops!'); }); await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' }, ]); await expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be .rejected; }); }); describe('with the {individualHooks: true} option', () => { beforeEach(async function () { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, defaultValue: '', }, beforeHookTest: { type: DataTypes.BOOLEAN, defaultValue: false, }, aNumber: { type: DataTypes.INTEGER, defaultValue: 0, }, }); await this.User.sync({ force: true }); }); it('should run the after/before functions for each item created successfully', async function () { const beforeBulk = sinon.spy(); const afterBulk = sinon.spy(); this.User.beforeBulkUpdate(beforeBulk); this.User.afterBulkUpdate(afterBulk); this.User.beforeUpdate(user => { expect(user.changed()).to.not.be.empty; user.beforeHookTest = true; }); this.User.afterUpdate(user => { user.username = `User${user.id}`; }); await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }]); const [, records] = await this.User.update( { aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }, ); for (const record of records) { expect(record.username).to.equal(`User${record.id}`); expect(record.beforeHookTest).to.be.true; } expect(beforeBulk).to.have.been.calledOnce; expect(afterBulk).to.have.been.calledOnce; }); it('should run the after/before functions for each item created successfully changing some data before updating', async function () { this.User.beforeUpdate(user => { expect(user.changed()).to.not.be.empty; if (user.get('id') === 1) { user.set('aNumber', user.get('aNumber') + 3); } }); await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }]); const [, records] = await this.User.update( { aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }, ); for (const record of records) { expect(record.aNumber).to.equal(10 + (record.id === 1 ? 3 : 0)); } }); it('should run the after/before functions for each item created with an error', async function () { const beforeBulk = sinon.spy(); const afterBulk = sinon.spy(); this.User.beforeBulkUpdate(beforeBulk); this.User.afterBulkUpdate(afterBulk); this.User.beforeUpdate(() => { throw new Error('You shall not pass!'); }); this.User.afterUpdate(user => { user.username = `User${user.id}`; }); await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'], }); try { await this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }); } catch (error) { expect(error).to.be.instanceOf(Error); expect(error.message).to.equal('You shall not pass!'); expect(beforeBulk).to.have.been.calledOnce; expect(afterBulk).not.to.have.been.called; } }); }); }); describe('#bulkDestroy', () => { describe('on success', () => { it('should run hooks', async function () { const beforeBulk = sinon.spy(); const afterBulk = sinon.spy(); this.User.beforeBulkDestroy(beforeBulk); this.User.afterBulkDestroy(afterBulk); await this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } }); expect(beforeBulk).to.have.been.calledOnce; expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { it('should return an error from before', async function () { this.User.beforeBulkDestroy(() => { throw new Error('Whoops!'); }); await expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be .rejected; }); it('should return an error from after', async function () { this.User.afterBulkDestroy(() => { throw new Error('Whoops!'); }); await expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be .rejected; }); }); describe('with the {individualHooks: true} option', () => { beforeEach(async function () { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, defaultValue: '', }, beforeHookTest: { type: DataTypes.BOOLEAN, defaultValue: false, }, aNumber: { type: DataTypes.INTEGER, defaultValue: 0, }, }); await this.User.sync({ force: true }); }); it('should run the after/before functions for each item created successfully', async function () { let beforeBulk = false; let afterBulk = false; let beforeHook = false; let afterHook = false; this.User.beforeBulkDestroy(async () => { beforeBulk = true; }); this.User.afterBulkDestroy(async () => { afterBulk = true; }); this.User.beforeDestroy(async () => { beforeHook = true; }); this.User.afterDestroy(async () => { afterHook = true; }); await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }]); await this.User.destroy({ where: { aNumber: 1 }, individualHooks: true }); expect(beforeBulk).to.be.true; expect(afterBulk).to.be.true; expect(beforeHook).to.be.true; expect(afterHook).to.be.true; }); it('should run the after/before functions for each item created with an error', async function () { let beforeBulk = false; let afterBulk = false; let beforeHook = false; let afterHook = false; this.User.beforeBulkDestroy(async () => { beforeBulk = true; }); this.User.afterBulkDestroy(async () => { afterBulk = true; }); this.User.beforeDestroy(async () => { beforeHook = true; throw new Error('You shall not pass!'); }); this.User.afterDestroy(async () => { afterHook = true; }); await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'], }); try { await this.User.destroy({ where: { aNumber: 1 }, individualHooks: true }); } catch (error) { expect(error).to.be.instanceOf(Error); expect(beforeBulk).to.be.true; expect(beforeHook).to.be.true; expect(afterBulk).to.be.false; expect(afterHook).to.be.false; } }); }); }); describe('#bulkRestore', () => { beforeEach(async function () { await this.ParanoidUser.bulkCreate([ { username: 'adam', mood: 'happy' }, { username: 'joe', mood: 'sad' }, ]); await this.ParanoidUser.truncate(); }); describe('on success', () => { it('should run hooks', async function () { const beforeBulk = sinon.spy(); const afterBulk = sinon.spy(); this.ParanoidUser.beforeBulkRestore(beforeBulk); this.ParanoidUser.afterBulkRestore(afterBulk); await this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } }); expect(beforeBulk).to.have.been.calledOnce; expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { it('should return an error from before', async function () { this.ParanoidUser.beforeBulkRestore(() => { throw new Error('Whoops!'); }); await expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to .be.rejected; }); it('should return an error from after', async function () { this.ParanoidUser.afterBulkRestore(() => { throw new Error('Whoops!'); }); await expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to .be.rejected; }); }); describe('with the {individualHooks: true} option', () => { beforeEach(async function () { this.ParanoidUser = this.sequelize.define( 'ParanoidUser', { aNumber: { type: DataTypes.INTEGER, defaultValue: 0, }, }, { paranoid: true, }, ); await this.ParanoidUser.sync({ force: true }); }); it('should run the after/before functions for each item restored successfully', async function () { const beforeBulk = sinon.spy(); const afterBulk = sinon.spy(); const beforeHook = sinon.spy(); const afterHook = sinon.spy(); this.ParanoidUser.beforeBulkRestore(beforeBulk); this.ParanoidUser.afterBulkRestore(afterBulk); this.ParanoidUser.beforeRestore(beforeHook); this.ParanoidUser.afterRestore(afterHook); await this.ParanoidUser.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }]); await this.ParanoidUser.destroy({ where: { aNumber: 1 } }); await this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true }); expect(beforeBulk).to.have.been.calledOnce; expect(afterBulk).to.have.been.calledOnce; expect(beforeHook).to.have.been.calledThrice; expect(afterHook).to.have.been.calledThrice; }); it('should run the after/before functions for each item restored with an error', async function () { const beforeBulk = sinon.spy(); const afterBulk = sinon.spy(); const beforeHook = sinon.spy(); const afterHook = sinon.spy(); this.ParanoidUser.beforeBulkRestore(beforeBulk); this.ParanoidUser.afterBulkRestore(afterBulk); this.ParanoidUser.beforeRestore(async () => { beforeHook(); throw new Error('You shall not pass!'); }); this.ParanoidUser.afterRestore(afterHook); try { await this.ParanoidUser.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'], }); await this.ParanoidUser.destroy({ where: { aNumber: 1 } }); await this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true }); } catch (error) { expect(error).to.be.instanceOf(Error); expect(beforeBulk).to.have.been.calledOnce; expect(beforeHook).to.have.been.calledThrice; expect(afterBulk).not.to.have.been.called; expect(afterHook).not.to.have.been.called; } }); }); }); });