123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- 'use strict';
- const noop = require('lodash/noop');
- const chai = require('chai');
- const sinon = require('sinon');
- const expect = chai.expect;
- const Support = require('../support');
- const { DataTypes, Sequelize } = require('@sequelize/core');
- const sequelize = Support.sequelize;
- describe(Support.getTestDialectTeaser('Hooks'), () => {
- beforeEach(function () {
- this.Model = sequelize.define('m');
- });
- it('does not expose non-model hooks', function () {
- for (const badHook of [
- 'beforeDefine',
- 'afterDefine',
- 'beforeConnect',
- 'afterConnect',
- 'beforePoolAcquire',
- 'afterPoolAcquire',
- 'beforeDisconnect',
- 'afterDisconnect',
- 'beforeInit',
- 'afterInit',
- ]) {
- expect(this.Model).to.not.have.property(badHook);
- }
- });
- describe('arguments', () => {
- it('hooks can modify passed arguments', async function () {
- this.Model.addHook('beforeCreate', options => {
- options.answer = 41;
- });
- const options = {};
- await this.Model.runHooks('beforeCreate', options);
- expect(options.answer).to.equal(41);
- });
- });
- describe('proxies', () => {
- beforeEach(() => {
- sinon.stub(sequelize, 'queryRaw').resolves([
- {
- _previousDataValues: {},
- dataValues: { id: 1, name: 'abc' },
- },
- ]);
- });
- afterEach(() => {
- sequelize.queryRaw.restore();
- });
- describe('defined by options.hooks', () => {
- beforeEach(function () {
- this.beforeSaveHook = sinon.spy();
- this.afterSaveHook = sinon.spy();
- this.afterCreateHook = sinon.spy();
- this.Model = sequelize.define(
- 'm',
- {
- name: DataTypes.STRING,
- },
- {
- hooks: {
- beforeSave: this.beforeSaveHook,
- afterSave: this.afterSaveHook,
- afterCreate: this.afterCreateHook,
- },
- },
- );
- });
- it('calls beforeSave/afterSave', async function () {
- await this.Model.create({});
- expect(this.afterCreateHook).to.have.been.calledOnce;
- expect(this.beforeSaveHook).to.have.been.calledOnce;
- expect(this.afterSaveHook).to.have.been.calledOnce;
- });
- });
- describe('defined by addHook method', () => {
- beforeEach(function () {
- this.beforeSaveHook = sinon.spy();
- this.afterSaveHook = sinon.spy();
- this.Model = sequelize.define('m', {
- name: DataTypes.STRING,
- });
- this.Model.addHook('beforeSave', this.beforeSaveHook);
- this.Model.addHook('afterSave', this.afterSaveHook);
- });
- it('calls beforeSave/afterSave', async function () {
- await this.Model.create({});
- expect(this.beforeSaveHook).to.have.been.calledOnce;
- expect(this.afterSaveHook).to.have.been.calledOnce;
- });
- });
- describe('defined by hook method', () => {
- beforeEach(function () {
- this.beforeSaveHook = sinon.spy();
- this.afterSaveHook = sinon.spy();
- this.Model = sequelize.define('m', {
- name: DataTypes.STRING,
- });
- this.Model.addHook('beforeSave', this.beforeSaveHook);
- this.Model.addHook('afterSave', this.afterSaveHook);
- });
- it('calls beforeSave/afterSave', async function () {
- await this.Model.create({});
- expect(this.beforeSaveHook).to.have.been.calledOnce;
- expect(this.afterSaveHook).to.have.been.calledOnce;
- });
- });
- });
- describe('multiple hooks', () => {
- beforeEach(function () {
- this.hook1 = sinon.spy();
- this.hook2 = sinon.spy();
- this.hook3 = sinon.spy();
- });
- describe('runs all hooks on success', () => {
- afterEach(function () {
- expect(this.hook1).to.have.been.calledOnce;
- expect(this.hook2).to.have.been.calledOnce;
- expect(this.hook3).to.have.been.calledOnce;
- });
- it('using addHook', async function () {
- this.Model.addHook('beforeCreate', this.hook1);
- this.Model.addHook('beforeCreate', this.hook2);
- this.Model.addHook('beforeCreate', this.hook3);
- await this.Model.runHooks('beforeCreate');
- });
- it('using function', async function () {
- this.Model.beforeCreate(this.hook1);
- this.Model.beforeCreate(this.hook2);
- this.Model.beforeCreate(this.hook3);
- await this.Model.runHooks('beforeCreate');
- });
- it('using define', async function () {
- await sequelize
- .define(
- 'M',
- {},
- {
- hooks: {
- beforeCreate: [this.hook1, this.hook2, this.hook3],
- },
- },
- )
- .runHooks('beforeCreate');
- });
- it('using a mixture', async function () {
- const Model = sequelize.define(
- 'M',
- {},
- {
- hooks: {
- beforeCreate: this.hook1,
- },
- },
- );
- Model.beforeCreate(this.hook2);
- Model.addHook('beforeCreate', this.hook3);
- await Model.runHooks('beforeCreate');
- });
- });
- it('stops execution when a hook throws', async function () {
- this.Model.beforeCreate(() => {
- this.hook1();
- throw new Error('No!');
- });
- this.Model.beforeCreate(this.hook2);
- await expect(this.Model.runHooks('beforeCreate')).to.be.rejected;
- expect(this.hook1).to.have.been.calledOnce;
- expect(this.hook2).not.to.have.been.called;
- });
- it('stops execution when a hook rejects', async function () {
- this.Model.beforeCreate(async () => {
- this.hook1();
- throw new Error('No!');
- });
- this.Model.beforeCreate(this.hook2);
- await expect(this.Model.runHooks('beforeCreate')).to.be.rejected;
- expect(this.hook1).to.have.been.calledOnce;
- expect(this.hook2).not.to.have.been.called;
- });
- });
- describe('global hooks', () => {
- describe('using addHook', () => {
- it('invokes the global hook', async function () {
- const globalHook = sinon.spy();
- sequelize.addHook('beforeUpdate', globalHook);
- await this.Model.runHooks('beforeUpdate');
- expect(globalHook).to.have.been.calledOnce;
- });
- it('invokes the global hook, when the model also has a hook', async () => {
- const globalHookBefore = sinon.spy();
- const globalHookAfter = sinon.spy();
- const localHook = sinon.spy();
- sequelize.addHook('beforeUpdate', globalHookBefore);
- const Model = sequelize.define(
- 'm',
- {},
- {
- hooks: {
- beforeUpdate: localHook,
- },
- },
- );
- sequelize.addHook('beforeUpdate', globalHookAfter);
- await Model.runHooks('beforeUpdate');
- expect(globalHookBefore).to.have.been.calledOnce;
- expect(globalHookAfter).to.have.been.calledOnce;
- expect(localHook).to.have.been.calledOnce;
- expect(localHook).to.have.been.calledBefore(globalHookBefore);
- expect(localHook).to.have.been.calledBefore(globalHookAfter);
- });
- });
- it('registers both the global define hook, and the local hook', async () => {
- const globalHook = sinon.spy();
- const sequelize = Support.createSequelizeInstance({
- define: {
- hooks: {
- beforeCreate: globalHook,
- },
- },
- });
- const localHook = sinon.spy();
- const Model = sequelize.define(
- 'M',
- {},
- {
- hooks: {
- beforeUpdate: noop, // Just to make sure we can define other hooks without overwriting the global one
- beforeCreate: localHook,
- },
- },
- );
- await Model.runHooks('beforeCreate');
- expect(globalHook).to.have.been.calledOnce;
- expect(localHook).to.have.been.calledOnce;
- });
- });
- describe('#removeHook', () => {
- it('should remove hook', async function () {
- const hook1 = sinon.spy();
- const hook2 = sinon.spy();
- this.Model.addHook('beforeCreate', 'myHook', hook1);
- this.Model.beforeCreate('myHook2', hook2);
- await this.Model.runHooks('beforeCreate');
- expect(hook1).to.have.been.calledOnce;
- expect(hook2).to.have.been.calledOnce;
- hook1.resetHistory();
- hook2.resetHistory();
- this.Model.removeHook('beforeCreate', 'myHook');
- this.Model.removeHook('beforeCreate', 'myHook2');
- await this.Model.runHooks('beforeCreate');
- expect(hook1).not.to.have.been.called;
- expect(hook2).not.to.have.been.called;
- });
- it('should not remove other hooks', async function () {
- const hook1 = sinon.spy();
- const hook2 = sinon.spy();
- const hook3 = sinon.spy();
- const hook4 = sinon.spy();
- this.Model.addHook('beforeCreate', hook1);
- this.Model.addHook('beforeCreate', 'myHook', hook2);
- this.Model.beforeCreate('myHook2', hook3);
- this.Model.beforeCreate(hook4);
- await this.Model.runHooks('beforeCreate');
- expect(hook1).to.have.been.calledOnce;
- expect(hook2).to.have.been.calledOnce;
- expect(hook3).to.have.been.calledOnce;
- expect(hook4).to.have.been.calledOnce;
- hook1.resetHistory();
- hook2.resetHistory();
- hook3.resetHistory();
- hook4.resetHistory();
- this.Model.removeHook('beforeCreate', 'myHook');
- await this.Model.runHooks('beforeCreate');
- expect(hook1).to.have.been.calledOnce;
- expect(hook2).not.to.have.been.called;
- expect(hook3).to.have.been.calledOnce;
- expect(hook4).to.have.been.calledOnce;
- });
- });
- describe('#addHook', () => {
- it('should add additional hook when previous exists', async function () {
- const hook1 = sinon.spy();
- const hook2 = sinon.spy();
- const Model = this.sequelize.define(
- 'Model',
- {},
- {
- hooks: { beforeCreate: hook1 },
- },
- );
- Model.addHook('beforeCreate', hook2);
- await Model.runHooks('beforeCreate');
- expect(hook1).to.have.been.calledOnce;
- expect(hook2).to.have.been.calledOnce;
- });
- });
- describe('promises', () => {
- it('can return a promise', async function () {
- this.Model.beforeBulkCreate(async () => {
- // This space intentionally left blank
- });
- await expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled;
- });
- it('can return undefined', async function () {
- this.Model.beforeBulkCreate(() => {
- // This space intentionally left blank
- });
- await expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled;
- });
- it('can return an error by rejecting', async function () {
- this.Model.beforeCreate(async () => {
- throw new Error('Forbidden');
- });
- await expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden');
- });
- it('can return an error by throwing', async function () {
- this.Model.beforeCreate(() => {
- throw new Error('Forbidden');
- });
- await expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden');
- });
- });
- describe('sync hooks', () => {
- beforeEach(function () {
- this.hook1 = sinon.spy();
- this.hook2 = sinon.spy();
- this.hook3 = sinon.spy();
- this.hook4 = sinon.spy();
- });
- it('runs all beforInit/afterInit hooks', function () {
- Sequelize.addHook('beforeInit', 'h1', this.hook1);
- Sequelize.addHook('beforeInit', 'h2', this.hook2);
- Sequelize.addHook('afterInit', 'h3', this.hook3);
- Sequelize.addHook('afterInit', 'h4', this.hook4);
- Support.createSequelizeInstance();
- expect(this.hook1).to.have.been.calledOnce;
- expect(this.hook2).to.have.been.calledOnce;
- expect(this.hook3).to.have.been.calledOnce;
- expect(this.hook4).to.have.been.calledOnce;
- // cleanup hooks on Sequelize
- Sequelize.removeHook('beforeInit', 'h1');
- Sequelize.removeHook('beforeInit', 'h2');
- Sequelize.removeHook('afterInit', 'h3');
- Sequelize.removeHook('afterInit', 'h4');
- Support.createSequelizeInstance();
- // check if hooks were removed
- expect(this.hook1).to.have.been.calledOnce;
- expect(this.hook2).to.have.been.calledOnce;
- expect(this.hook3).to.have.been.calledOnce;
- expect(this.hook4).to.have.been.calledOnce;
- });
- it('runs all beforDefine/afterDefine hooks', function () {
- const sequelize = Support.createSequelizeInstance();
- sequelize.addHook('beforeDefine', this.hook1);
- sequelize.addHook('beforeDefine', this.hook2);
- sequelize.addHook('afterDefine', this.hook3);
- sequelize.addHook('afterDefine', this.hook4);
- sequelize.define('Test', {});
- expect(this.hook1).to.have.been.calledOnce;
- expect(this.hook2).to.have.been.calledOnce;
- expect(this.hook3).to.have.been.calledOnce;
- expect(this.hook4).to.have.been.calledOnce;
- });
- });
- describe('#removal', () => {
- before(() => {
- sinon.stub(sequelize, 'queryRaw').resolves([
- {
- _previousDataValues: {},
- dataValues: { id: 1, name: 'abc' },
- },
- ]);
- });
- after(() => {
- sequelize.queryRaw.restore();
- });
- it('should be able to remove by name', async () => {
- const User = sequelize.define('User');
- const hook1 = sinon.spy();
- const hook2 = sinon.spy();
- User.addHook('beforeCreate', 'sasuke', hook1);
- User.addHook('beforeCreate', 'naruto', hook2);
- await User.create({ username: 'makunouchi' });
- expect(hook1).to.have.been.calledOnce;
- expect(hook2).to.have.been.calledOnce;
- User.removeHook('beforeCreate', 'sasuke');
- await User.create({ username: 'sendo' });
- expect(hook1).to.have.been.calledOnce;
- expect(hook2).to.have.been.calledTwice;
- });
- it('should be able to remove by reference', async () => {
- const User = sequelize.define('User');
- const hook1 = sinon.spy();
- const hook2 = sinon.spy();
- User.addHook('beforeCreate', hook1);
- User.addHook('beforeCreate', hook2);
- await User.create({ username: 'makunouchi' });
- expect(hook1).to.have.been.calledOnce;
- expect(hook2).to.have.been.calledOnce;
- User.removeHook('beforeCreate', hook1);
- await User.create({ username: 'sendo' });
- expect(hook1).to.have.been.calledOnce;
- expect(hook2).to.have.been.calledTwice;
- });
- });
- });
|