destroy.test.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. 'use strict';
  2. const chai = require('chai');
  3. const expect = chai.expect;
  4. const sinon = require('sinon');
  5. const dayjs = require('dayjs');
  6. const Support = require('../support');
  7. const { DataTypes } = require('@sequelize/core');
  8. const dialect = Support.getTestDialect();
  9. const current = Support.sequelize;
  10. describe(Support.getTestDialectTeaser('Instance'), () => {
  11. describe('destroy', () => {
  12. if (current.dialect.supports.transactions) {
  13. it('supports transactions', async function () {
  14. const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
  15. this.sequelize,
  16. );
  17. const User = sequelize.define('User', { username: DataTypes.STRING });
  18. await User.sync({ force: true });
  19. const user = await User.create({ username: 'foo' });
  20. const t = await sequelize.startUnmanagedTransaction();
  21. await user.destroy({ transaction: t });
  22. const count1 = await User.count();
  23. const count2 = await User.count({ transaction: t });
  24. expect(count1).to.equal(1);
  25. expect(count2).to.equal(0);
  26. await t.rollback();
  27. });
  28. }
  29. it('does not set the deletedAt date in subsequent destroys if dao is paranoid', async function () {
  30. const UserDestroy = this.sequelize.define(
  31. 'UserDestroy',
  32. {
  33. name: DataTypes.STRING,
  34. bio: DataTypes.TEXT,
  35. },
  36. { paranoid: true },
  37. );
  38. await UserDestroy.sync({ force: true });
  39. const user = await UserDestroy.create({ name: 'hallo', bio: 'welt' });
  40. await user.destroy();
  41. await user.reload({ paranoid: false });
  42. const deletedAt = user.deletedAt;
  43. await user.destroy();
  44. await user.reload({ paranoid: false });
  45. expect(user.deletedAt).to.eql(deletedAt);
  46. });
  47. it('does not update deletedAt with custom default in subsequent destroys', async function () {
  48. const ParanoidUser = this.sequelize.define(
  49. 'ParanoidUser',
  50. {
  51. username: DataTypes.STRING,
  52. deletedAt: { type: DataTypes.DATE, defaultValue: new Date(0) },
  53. },
  54. { paranoid: true },
  55. );
  56. await ParanoidUser.sync({ force: true });
  57. const user1 = await ParanoidUser.create({
  58. username: 'username',
  59. });
  60. const user0 = await user1.destroy();
  61. const deletedAt = user0.deletedAt;
  62. expect(deletedAt).to.be.ok;
  63. expect(deletedAt.getTime()).to.be.ok;
  64. const user = await user0.destroy();
  65. expect(user).to.be.ok;
  66. expect(user.deletedAt).to.be.ok;
  67. expect(user.deletedAt.toISOString()).to.equal(deletedAt.toISOString());
  68. });
  69. it('deletes a record from the database if dao is not paranoid', async function () {
  70. const UserDestroy = this.sequelize.define('UserDestroy', {
  71. name: DataTypes.STRING,
  72. bio: DataTypes.TEXT,
  73. });
  74. await UserDestroy.sync({ force: true });
  75. const u = await UserDestroy.create({ name: 'hallo', bio: 'welt' });
  76. const users = await UserDestroy.findAll();
  77. expect(users.length).to.equal(1);
  78. await u.destroy();
  79. const users0 = await UserDestroy.findAll();
  80. expect(users0.length).to.equal(0);
  81. });
  82. it('allows updating soft deleted instance', async function () {
  83. const ParanoidUser = this.sequelize.define(
  84. 'ParanoidUser',
  85. {
  86. username: DataTypes.STRING,
  87. },
  88. { paranoid: true },
  89. );
  90. await ParanoidUser.sync({ force: true });
  91. const user2 = await ParanoidUser.create({
  92. username: 'username',
  93. });
  94. const user1 = await user2.destroy();
  95. expect(user1.deletedAt).to.be.ok;
  96. const deletedAt = user1.deletedAt;
  97. user1.username = 'foo';
  98. const user0 = await user1.save();
  99. expect(user0.username).to.equal('foo');
  100. expect(user0.deletedAt).to.equal(deletedAt, 'should not update deletedAt');
  101. const user = await ParanoidUser.findOne({
  102. paranoid: false,
  103. where: {
  104. username: 'foo',
  105. },
  106. });
  107. expect(user).to.be.ok;
  108. expect(user.deletedAt).to.be.ok;
  109. });
  110. it('supports custom deletedAt field', async function () {
  111. const ParanoidUser = this.sequelize.define(
  112. 'ParanoidUser',
  113. {
  114. username: DataTypes.STRING,
  115. destroyTime: DataTypes.DATE,
  116. },
  117. { paranoid: true, deletedAt: 'destroyTime' },
  118. );
  119. await ParanoidUser.sync({ force: true });
  120. const user1 = await ParanoidUser.create({
  121. username: 'username',
  122. });
  123. const user0 = await user1.destroy();
  124. expect(user0.destroyTime).to.be.ok;
  125. expect(user0.deletedAt).to.not.be.ok;
  126. const user = await ParanoidUser.findOne({
  127. paranoid: false,
  128. where: {
  129. username: 'username',
  130. },
  131. });
  132. expect(user).to.be.ok;
  133. expect(user.destroyTime).to.be.ok;
  134. expect(user.deletedAt).to.not.be.ok;
  135. });
  136. it('supports custom deletedAt database column', async function () {
  137. const ParanoidUser = this.sequelize.define(
  138. 'ParanoidUser',
  139. {
  140. username: DataTypes.STRING,
  141. deletedAt: { type: DataTypes.DATE, field: 'deleted_at' },
  142. },
  143. { paranoid: true },
  144. );
  145. await ParanoidUser.sync({ force: true });
  146. const user1 = await ParanoidUser.create({
  147. username: 'username',
  148. });
  149. const user0 = await user1.destroy();
  150. expect(user0.dataValues.deletedAt).to.be.ok;
  151. expect(user0.dataValues.deleted_at).to.not.be.ok;
  152. const user = await ParanoidUser.findOne({
  153. paranoid: false,
  154. where: {
  155. username: 'username',
  156. },
  157. });
  158. expect(user).to.be.ok;
  159. expect(user.deletedAt).to.be.ok;
  160. expect(user.deleted_at).to.not.be.ok;
  161. });
  162. it('supports custom deletedAt field and database column', async function () {
  163. const ParanoidUser = this.sequelize.define(
  164. 'ParanoidUser',
  165. {
  166. username: DataTypes.STRING,
  167. destroyTime: { type: DataTypes.DATE, field: 'destroy_time' },
  168. },
  169. { paranoid: true, deletedAt: 'destroyTime' },
  170. );
  171. await ParanoidUser.sync({ force: true });
  172. const user1 = await ParanoidUser.create({
  173. username: 'username',
  174. });
  175. const user0 = await user1.destroy();
  176. expect(user0.dataValues.destroyTime).to.be.ok;
  177. expect(user0.dataValues.destroy_time).to.not.be.ok;
  178. const user = await ParanoidUser.findOne({
  179. paranoid: false,
  180. where: {
  181. username: 'username',
  182. },
  183. });
  184. expect(user).to.be.ok;
  185. expect(user.destroyTime).to.be.ok;
  186. expect(user.destroy_time).to.not.be.ok;
  187. });
  188. it('persists other model changes when soft deleting', async function () {
  189. const ParanoidUser = this.sequelize.define(
  190. 'ParanoidUser',
  191. {
  192. username: DataTypes.STRING,
  193. },
  194. { paranoid: true },
  195. );
  196. await ParanoidUser.sync({ force: true });
  197. const user4 = await ParanoidUser.create({
  198. username: 'username',
  199. });
  200. user4.username = 'foo';
  201. const user3 = await user4.destroy();
  202. expect(user3.username).to.equal('foo');
  203. expect(user3.deletedAt).to.be.ok;
  204. const deletedAt = user3.deletedAt;
  205. const user2 = await ParanoidUser.findOne({
  206. paranoid: false,
  207. where: {
  208. username: 'foo',
  209. },
  210. });
  211. expect(user2).to.be.ok;
  212. expect(dayjs.utc(user2.deletedAt).startOf('second').toISOString()).to.equal(
  213. dayjs.utc(deletedAt).startOf('second').toISOString(),
  214. );
  215. expect(user2.username).to.equal('foo');
  216. const user1 = user2;
  217. // update model and delete again
  218. user1.username = 'bar';
  219. const user0 = await user1.destroy();
  220. expect(dayjs.utc(user0.deletedAt).startOf('second').toISOString()).to.equal(
  221. dayjs.utc(deletedAt).startOf('second').toISOString(),
  222. 'should not updated deletedAt when destroying multiple times',
  223. );
  224. const user = await ParanoidUser.findOne({
  225. paranoid: false,
  226. where: {
  227. username: 'bar',
  228. },
  229. });
  230. expect(user).to.be.ok;
  231. expect(dayjs.utc(user.deletedAt).startOf('second').toISOString()).to.equal(
  232. dayjs.utc(deletedAt).startOf('second').toISOString(),
  233. );
  234. expect(user.username).to.equal('bar');
  235. });
  236. it('is disallowed if no primary key is present', async function () {
  237. const Foo = this.sequelize.define('Foo', {}, { noPrimaryKey: true });
  238. await Foo.sync({ force: true });
  239. const instance = await Foo.create({});
  240. await expect(instance.destroy()).to.be.rejectedWith(
  241. 'but the model does not have a primary key attribute definition.',
  242. );
  243. });
  244. it('allows sql logging of delete statements', async function () {
  245. const UserDelete = this.sequelize.define('UserDelete', {
  246. name: DataTypes.STRING,
  247. bio: DataTypes.TEXT,
  248. });
  249. const logging = sinon.spy();
  250. await UserDelete.sync({ force: true });
  251. const u = await UserDelete.create({ name: 'hallo', bio: 'welt' });
  252. const users = await UserDelete.findAll();
  253. expect(users.length).to.equal(1);
  254. await u.destroy({ logging });
  255. expect(logging.callCount).to.equal(1, 'should call logging');
  256. const sql = logging.firstCall.args[0];
  257. expect(sql).to.exist;
  258. expect(sql.toUpperCase()).to.include('DELETE');
  259. });
  260. it('allows sql logging of update statements', async function () {
  261. const UserDelete = this.sequelize.define(
  262. 'UserDelete',
  263. {
  264. name: DataTypes.STRING,
  265. bio: DataTypes.TEXT,
  266. },
  267. { paranoid: true },
  268. );
  269. const logging = sinon.spy();
  270. await UserDelete.sync({ force: true });
  271. const u = await UserDelete.create({ name: 'hallo', bio: 'welt' });
  272. const users = await UserDelete.findAll();
  273. expect(users.length).to.equal(1);
  274. await u.destroy({ logging });
  275. expect(logging.callCount).to.equal(1, 'should call logging');
  276. const sql = logging.firstCall.args[0];
  277. expect(sql).to.exist;
  278. expect(sql.toUpperCase()).to.include('UPDATE');
  279. });
  280. it('should not call save hooks when soft deleting', async function () {
  281. const beforeSave = sinon.spy();
  282. const afterSave = sinon.spy();
  283. const ParanoidUser = this.sequelize.define(
  284. 'ParanoidUser',
  285. {
  286. username: DataTypes.STRING,
  287. },
  288. {
  289. paranoid: true,
  290. hooks: {
  291. beforeSave,
  292. afterSave,
  293. },
  294. },
  295. );
  296. await ParanoidUser.sync({ force: true });
  297. const user0 = await ParanoidUser.create({
  298. username: 'username',
  299. });
  300. // clear out calls from .create
  301. beforeSave.resetHistory();
  302. afterSave.resetHistory();
  303. const result0 = await user0.destroy();
  304. expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave');
  305. expect(afterSave.callCount).to.equal(0, 'should not call afterSave');
  306. const user = result0;
  307. const result = await user.destroy({ hooks: true });
  308. expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave even if `hooks: true`');
  309. expect(afterSave.callCount).to.equal(0, 'should not call afterSave even if `hooks: true`');
  310. await result;
  311. });
  312. it('delete a record of multiple primary keys table', async function () {
  313. const MultiPrimary = this.sequelize.define('MultiPrimary', {
  314. bilibili: {
  315. type: DataTypes.STRING,
  316. primaryKey: true,
  317. },
  318. guruguru: {
  319. type: DataTypes.STRING,
  320. primaryKey: true,
  321. },
  322. });
  323. await MultiPrimary.sync({ force: true });
  324. await MultiPrimary.create({ bilibili: 'bl', guruguru: 'gu' });
  325. const m2 = await MultiPrimary.create({ bilibili: 'bl', guruguru: 'ru' });
  326. const ms = await MultiPrimary.findAll();
  327. expect(ms.length).to.equal(2);
  328. await m2.destroy({
  329. logging(sql) {
  330. expect(sql).to.exist;
  331. expect(sql.toUpperCase()).to.include('DELETE');
  332. expect(sql).to.include('ru');
  333. expect(sql).to.include('bl');
  334. },
  335. });
  336. const ms0 = await MultiPrimary.findAll();
  337. expect(ms0.length).to.equal(1);
  338. expect(ms0[0].bilibili).to.equal('bl');
  339. expect(ms0[0].guruguru).to.equal('gu');
  340. });
  341. if (dialect.startsWith('postgres')) {
  342. it('converts Infinity in where clause to a timestamp', async function () {
  343. const Date = this.sequelize.define(
  344. 'Date',
  345. {
  346. date: {
  347. type: DataTypes.DATE,
  348. primaryKey: true,
  349. },
  350. deletedAt: {
  351. type: DataTypes.DATE,
  352. defaultValue: Number.POSITIVE_INFINITY,
  353. },
  354. },
  355. { paranoid: true },
  356. );
  357. await this.sequelize.sync({ force: true });
  358. const date = await Date.build({ date: Number.POSITIVE_INFINITY }).save();
  359. await date.destroy();
  360. });
  361. }
  362. });
  363. });