bulkOperation.test.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. 'use strict';
  2. const chai = require('chai');
  3. const expect = chai.expect;
  4. const Support = require('../support');
  5. const { DataTypes } = require('@sequelize/core');
  6. const sinon = require('sinon');
  7. describe(Support.getTestDialectTeaser('Hooks'), () => {
  8. beforeEach(async function () {
  9. this.User = this.sequelize.define('User', {
  10. username: {
  11. type: DataTypes.STRING,
  12. allowNull: false,
  13. },
  14. mood: {
  15. type: DataTypes.ENUM(['happy', 'sad', 'neutral']),
  16. },
  17. });
  18. this.ParanoidUser = this.sequelize.define(
  19. 'ParanoidUser',
  20. {
  21. username: DataTypes.STRING,
  22. mood: {
  23. type: DataTypes.ENUM(['happy', 'sad', 'neutral']),
  24. },
  25. },
  26. {
  27. paranoid: true,
  28. },
  29. );
  30. await this.sequelize.sync({ force: true });
  31. });
  32. describe('#bulkCreate', () => {
  33. describe('on success', () => {
  34. it('should run hooks', async function () {
  35. const beforeBulk = sinon.spy();
  36. const afterBulk = sinon.spy();
  37. this.User.beforeBulkCreate(beforeBulk);
  38. this.User.afterBulkCreate(afterBulk);
  39. await this.User.bulkCreate([
  40. { username: 'Cheech', mood: 'sad' },
  41. { username: 'Chong', mood: 'sad' },
  42. ]);
  43. expect(beforeBulk).to.have.been.calledOnce;
  44. expect(afterBulk).to.have.been.calledOnce;
  45. });
  46. });
  47. describe('on error', () => {
  48. it('should return an error from before', async function () {
  49. this.User.beforeBulkCreate(() => {
  50. throw new Error('Whoops!');
  51. });
  52. await expect(
  53. this.User.bulkCreate([
  54. { username: 'Cheech', mood: 'sad' },
  55. { username: 'Chong', mood: 'sad' },
  56. ]),
  57. ).to.be.rejected;
  58. });
  59. it('should return an error from after', async function () {
  60. this.User.afterBulkCreate(() => {
  61. throw new Error('Whoops!');
  62. });
  63. await expect(
  64. this.User.bulkCreate([
  65. { username: 'Cheech', mood: 'sad' },
  66. { username: 'Chong', mood: 'sad' },
  67. ]),
  68. ).to.be.rejected;
  69. });
  70. });
  71. describe('with the {individualHooks: true} option', () => {
  72. beforeEach(async function () {
  73. this.User = this.sequelize.define('User', {
  74. username: {
  75. type: DataTypes.STRING,
  76. defaultValue: '',
  77. },
  78. beforeHookTest: {
  79. type: DataTypes.BOOLEAN,
  80. defaultValue: false,
  81. },
  82. aNumber: {
  83. type: DataTypes.INTEGER,
  84. defaultValue: 0,
  85. },
  86. });
  87. await this.User.sync({ force: true });
  88. });
  89. it('should run the afterCreate/beforeCreate functions for each item created successfully', async function () {
  90. let beforeBulkCreate = false;
  91. let afterBulkCreate = false;
  92. this.User.beforeBulkCreate(async () => {
  93. beforeBulkCreate = true;
  94. });
  95. this.User.afterBulkCreate(async () => {
  96. afterBulkCreate = true;
  97. });
  98. this.User.beforeCreate(async user => {
  99. user.beforeHookTest = true;
  100. });
  101. this.User.afterCreate(async user => {
  102. user.username = `User${user.id}`;
  103. });
  104. const records = await this.User.bulkCreate(
  105. [{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }],
  106. { fields: ['aNumber'], individualHooks: true },
  107. );
  108. for (const record of records) {
  109. expect(record.username).to.equal(`User${record.id}`);
  110. expect(record.beforeHookTest).to.be.true;
  111. }
  112. expect(beforeBulkCreate).to.be.true;
  113. expect(afterBulkCreate).to.be.true;
  114. });
  115. it('should run the afterCreate/beforeCreate functions for each item created with an error', async function () {
  116. let beforeBulkCreate = false;
  117. let afterBulkCreate = false;
  118. this.User.beforeBulkCreate(async () => {
  119. beforeBulkCreate = true;
  120. });
  121. this.User.afterBulkCreate(async () => {
  122. afterBulkCreate = true;
  123. });
  124. this.User.beforeCreate(async () => {
  125. throw new Error('You shall not pass!');
  126. });
  127. this.User.afterCreate(async user => {
  128. user.username = `User${user.id}`;
  129. });
  130. try {
  131. await this.User.bulkCreate([{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], {
  132. fields: ['aNumber'],
  133. individualHooks: true,
  134. });
  135. } catch (error) {
  136. expect(error).to.be.instanceOf(Error);
  137. expect(beforeBulkCreate).to.be.true;
  138. expect(afterBulkCreate).to.be.false;
  139. }
  140. });
  141. });
  142. });
  143. describe('#bulkUpdate', () => {
  144. describe('on success', () => {
  145. it('should run hooks', async function () {
  146. const beforeBulk = sinon.spy();
  147. const afterBulk = sinon.spy();
  148. this.User.beforeBulkUpdate(beforeBulk);
  149. this.User.afterBulkUpdate(afterBulk);
  150. await this.User.bulkCreate([
  151. { username: 'Cheech', mood: 'sad' },
  152. { username: 'Chong', mood: 'sad' },
  153. ]);
  154. await this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } });
  155. expect(beforeBulk).to.have.been.calledOnce;
  156. expect(afterBulk).to.have.been.calledOnce;
  157. });
  158. });
  159. describe('on error', () => {
  160. it('should return an error from before', async function () {
  161. this.User.beforeBulkUpdate(() => {
  162. throw new Error('Whoops!');
  163. });
  164. await this.User.bulkCreate([
  165. { username: 'Cheech', mood: 'sad' },
  166. { username: 'Chong', mood: 'sad' },
  167. ]);
  168. await expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be
  169. .rejected;
  170. });
  171. it('should return an error from after', async function () {
  172. this.User.afterBulkUpdate(() => {
  173. throw new Error('Whoops!');
  174. });
  175. await this.User.bulkCreate([
  176. { username: 'Cheech', mood: 'sad' },
  177. { username: 'Chong', mood: 'sad' },
  178. ]);
  179. await expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be
  180. .rejected;
  181. });
  182. });
  183. describe('with the {individualHooks: true} option', () => {
  184. beforeEach(async function () {
  185. this.User = this.sequelize.define('User', {
  186. username: {
  187. type: DataTypes.STRING,
  188. defaultValue: '',
  189. },
  190. beforeHookTest: {
  191. type: DataTypes.BOOLEAN,
  192. defaultValue: false,
  193. },
  194. aNumber: {
  195. type: DataTypes.INTEGER,
  196. defaultValue: 0,
  197. },
  198. });
  199. await this.User.sync({ force: true });
  200. });
  201. it('should run the after/before functions for each item created successfully', async function () {
  202. const beforeBulk = sinon.spy();
  203. const afterBulk = sinon.spy();
  204. this.User.beforeBulkUpdate(beforeBulk);
  205. this.User.afterBulkUpdate(afterBulk);
  206. this.User.beforeUpdate(user => {
  207. expect(user.changed()).to.not.be.empty;
  208. user.beforeHookTest = true;
  209. });
  210. this.User.afterUpdate(user => {
  211. user.username = `User${user.id}`;
  212. });
  213. await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }]);
  214. const [, records] = await this.User.update(
  215. { aNumber: 10 },
  216. { where: { aNumber: 1 }, individualHooks: true },
  217. );
  218. for (const record of records) {
  219. expect(record.username).to.equal(`User${record.id}`);
  220. expect(record.beforeHookTest).to.be.true;
  221. }
  222. expect(beforeBulk).to.have.been.calledOnce;
  223. expect(afterBulk).to.have.been.calledOnce;
  224. });
  225. it('should run the after/before functions for each item created successfully changing some data before updating', async function () {
  226. this.User.beforeUpdate(user => {
  227. expect(user.changed()).to.not.be.empty;
  228. if (user.get('id') === 1) {
  229. user.set('aNumber', user.get('aNumber') + 3);
  230. }
  231. });
  232. await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }]);
  233. const [, records] = await this.User.update(
  234. { aNumber: 10 },
  235. { where: { aNumber: 1 }, individualHooks: true },
  236. );
  237. for (const record of records) {
  238. expect(record.aNumber).to.equal(10 + (record.id === 1 ? 3 : 0));
  239. }
  240. });
  241. it('should run the after/before functions for each item created with an error', async function () {
  242. const beforeBulk = sinon.spy();
  243. const afterBulk = sinon.spy();
  244. this.User.beforeBulkUpdate(beforeBulk);
  245. this.User.afterBulkUpdate(afterBulk);
  246. this.User.beforeUpdate(() => {
  247. throw new Error('You shall not pass!');
  248. });
  249. this.User.afterUpdate(user => {
  250. user.username = `User${user.id}`;
  251. });
  252. await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], {
  253. fields: ['aNumber'],
  254. });
  255. try {
  256. await this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true });
  257. } catch (error) {
  258. expect(error).to.be.instanceOf(Error);
  259. expect(error.message).to.equal('You shall not pass!');
  260. expect(beforeBulk).to.have.been.calledOnce;
  261. expect(afterBulk).not.to.have.been.called;
  262. }
  263. });
  264. });
  265. });
  266. describe('#bulkDestroy', () => {
  267. describe('on success', () => {
  268. it('should run hooks', async function () {
  269. const beforeBulk = sinon.spy();
  270. const afterBulk = sinon.spy();
  271. this.User.beforeBulkDestroy(beforeBulk);
  272. this.User.afterBulkDestroy(afterBulk);
  273. await this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } });
  274. expect(beforeBulk).to.have.been.calledOnce;
  275. expect(afterBulk).to.have.been.calledOnce;
  276. });
  277. });
  278. describe('on error', () => {
  279. it('should return an error from before', async function () {
  280. this.User.beforeBulkDestroy(() => {
  281. throw new Error('Whoops!');
  282. });
  283. await expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be
  284. .rejected;
  285. });
  286. it('should return an error from after', async function () {
  287. this.User.afterBulkDestroy(() => {
  288. throw new Error('Whoops!');
  289. });
  290. await expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be
  291. .rejected;
  292. });
  293. });
  294. describe('with the {individualHooks: true} option', () => {
  295. beforeEach(async function () {
  296. this.User = this.sequelize.define('User', {
  297. username: {
  298. type: DataTypes.STRING,
  299. defaultValue: '',
  300. },
  301. beforeHookTest: {
  302. type: DataTypes.BOOLEAN,
  303. defaultValue: false,
  304. },
  305. aNumber: {
  306. type: DataTypes.INTEGER,
  307. defaultValue: 0,
  308. },
  309. });
  310. await this.User.sync({ force: true });
  311. });
  312. it('should run the after/before functions for each item created successfully', async function () {
  313. let beforeBulk = false;
  314. let afterBulk = false;
  315. let beforeHook = false;
  316. let afterHook = false;
  317. this.User.beforeBulkDestroy(async () => {
  318. beforeBulk = true;
  319. });
  320. this.User.afterBulkDestroy(async () => {
  321. afterBulk = true;
  322. });
  323. this.User.beforeDestroy(async () => {
  324. beforeHook = true;
  325. });
  326. this.User.afterDestroy(async () => {
  327. afterHook = true;
  328. });
  329. await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }]);
  330. await this.User.destroy({ where: { aNumber: 1 }, individualHooks: true });
  331. expect(beforeBulk).to.be.true;
  332. expect(afterBulk).to.be.true;
  333. expect(beforeHook).to.be.true;
  334. expect(afterHook).to.be.true;
  335. });
  336. it('should run the after/before functions for each item created with an error', async function () {
  337. let beforeBulk = false;
  338. let afterBulk = false;
  339. let beforeHook = false;
  340. let afterHook = false;
  341. this.User.beforeBulkDestroy(async () => {
  342. beforeBulk = true;
  343. });
  344. this.User.afterBulkDestroy(async () => {
  345. afterBulk = true;
  346. });
  347. this.User.beforeDestroy(async () => {
  348. beforeHook = true;
  349. throw new Error('You shall not pass!');
  350. });
  351. this.User.afterDestroy(async () => {
  352. afterHook = true;
  353. });
  354. await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], {
  355. fields: ['aNumber'],
  356. });
  357. try {
  358. await this.User.destroy({ where: { aNumber: 1 }, individualHooks: true });
  359. } catch (error) {
  360. expect(error).to.be.instanceOf(Error);
  361. expect(beforeBulk).to.be.true;
  362. expect(beforeHook).to.be.true;
  363. expect(afterBulk).to.be.false;
  364. expect(afterHook).to.be.false;
  365. }
  366. });
  367. });
  368. });
  369. describe('#bulkRestore', () => {
  370. beforeEach(async function () {
  371. await this.ParanoidUser.bulkCreate([
  372. { username: 'adam', mood: 'happy' },
  373. { username: 'joe', mood: 'sad' },
  374. ]);
  375. await this.ParanoidUser.truncate();
  376. });
  377. describe('on success', () => {
  378. it('should run hooks', async function () {
  379. const beforeBulk = sinon.spy();
  380. const afterBulk = sinon.spy();
  381. this.ParanoidUser.beforeBulkRestore(beforeBulk);
  382. this.ParanoidUser.afterBulkRestore(afterBulk);
  383. await this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } });
  384. expect(beforeBulk).to.have.been.calledOnce;
  385. expect(afterBulk).to.have.been.calledOnce;
  386. });
  387. });
  388. describe('on error', () => {
  389. it('should return an error from before', async function () {
  390. this.ParanoidUser.beforeBulkRestore(() => {
  391. throw new Error('Whoops!');
  392. });
  393. await expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to
  394. .be.rejected;
  395. });
  396. it('should return an error from after', async function () {
  397. this.ParanoidUser.afterBulkRestore(() => {
  398. throw new Error('Whoops!');
  399. });
  400. await expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to
  401. .be.rejected;
  402. });
  403. });
  404. describe('with the {individualHooks: true} option', () => {
  405. beforeEach(async function () {
  406. this.ParanoidUser = this.sequelize.define(
  407. 'ParanoidUser',
  408. {
  409. aNumber: {
  410. type: DataTypes.INTEGER,
  411. defaultValue: 0,
  412. },
  413. },
  414. {
  415. paranoid: true,
  416. },
  417. );
  418. await this.ParanoidUser.sync({ force: true });
  419. });
  420. it('should run the after/before functions for each item restored successfully', async function () {
  421. const beforeBulk = sinon.spy();
  422. const afterBulk = sinon.spy();
  423. const beforeHook = sinon.spy();
  424. const afterHook = sinon.spy();
  425. this.ParanoidUser.beforeBulkRestore(beforeBulk);
  426. this.ParanoidUser.afterBulkRestore(afterBulk);
  427. this.ParanoidUser.beforeRestore(beforeHook);
  428. this.ParanoidUser.afterRestore(afterHook);
  429. await this.ParanoidUser.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }]);
  430. await this.ParanoidUser.destroy({ where: { aNumber: 1 } });
  431. await this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true });
  432. expect(beforeBulk).to.have.been.calledOnce;
  433. expect(afterBulk).to.have.been.calledOnce;
  434. expect(beforeHook).to.have.been.calledThrice;
  435. expect(afterHook).to.have.been.calledThrice;
  436. });
  437. it('should run the after/before functions for each item restored with an error', async function () {
  438. const beforeBulk = sinon.spy();
  439. const afterBulk = sinon.spy();
  440. const beforeHook = sinon.spy();
  441. const afterHook = sinon.spy();
  442. this.ParanoidUser.beforeBulkRestore(beforeBulk);
  443. this.ParanoidUser.afterBulkRestore(afterBulk);
  444. this.ParanoidUser.beforeRestore(async () => {
  445. beforeHook();
  446. throw new Error('You shall not pass!');
  447. });
  448. this.ParanoidUser.afterRestore(afterHook);
  449. try {
  450. await this.ParanoidUser.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], {
  451. fields: ['aNumber'],
  452. });
  453. await this.ParanoidUser.destroy({ where: { aNumber: 1 } });
  454. await this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true });
  455. } catch (error) {
  456. expect(error).to.be.instanceOf(Error);
  457. expect(beforeBulk).to.have.been.calledOnce;
  458. expect(beforeHook).to.have.been.calledThrice;
  459. expect(afterBulk).not.to.have.been.called;
  460. expect(afterHook).not.to.have.been.called;
  461. }
  462. });
  463. });
  464. });
  465. });