transaction.test.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. import type { InferAttributes, InferCreationAttributes } from '@sequelize/core';
  2. import {
  3. ConstraintChecking,
  4. DataTypes,
  5. IsolationLevel,
  6. Model,
  7. Transaction,
  8. TransactionNestMode,
  9. TransactionType,
  10. } from '@sequelize/core';
  11. import { Attribute, NotNull } from '@sequelize/core/decorators-legacy';
  12. import { assert, expect } from 'chai';
  13. import delay from 'delay';
  14. import type { SinonStub } from 'sinon';
  15. import sinon from 'sinon';
  16. import {
  17. beforeAll2,
  18. createMultiTransactionalTestSequelizeInstance,
  19. createSingleTransactionalTestSequelizeInstance,
  20. getTestDialect,
  21. getTestDialectTeaser,
  22. sequelize,
  23. setResetMode,
  24. } from '../support';
  25. const dialectName = sequelize.dialect.name;
  26. describe(getTestDialectTeaser('Sequelize#transaction'), () => {
  27. if (!sequelize.dialect.supports.transactions) {
  28. return;
  29. }
  30. let stubs: SinonStub[] = [];
  31. afterEach(() => {
  32. for (const stub of stubs) {
  33. stub.restore();
  34. }
  35. stubs = [];
  36. });
  37. describe('nested managed transactions', () => {
  38. it('reuses the parent transaction by default', async () => {
  39. await sequelize.transaction(async transaction1 => {
  40. await sequelize.transaction({ transaction: transaction1 }, async transaction2 => {
  41. expect(transaction1 === transaction2).to.equal(
  42. true,
  43. 'transaction1 and transaction2 should be the same',
  44. );
  45. });
  46. });
  47. });
  48. it('requires compatible options if nestMode is set to "reuse"', async () => {
  49. await sequelize.transaction(async transaction1 => {
  50. if (sequelize.dialect.supports.startTransaction.transactionType) {
  51. await expect(
  52. sequelize.transaction(
  53. { transaction: transaction1, type: TransactionType.EXCLUSIVE },
  54. async () => {
  55. /* noop */
  56. },
  57. ),
  58. ).to.be.rejectedWith(
  59. 'Requested transaction type (EXCLUSIVE) is not compatible with the one of the existing transaction (DEFERRED)',
  60. );
  61. } else {
  62. await expect(
  63. sequelize.transaction(
  64. { transaction: transaction1, type: TransactionType.EXCLUSIVE },
  65. async () => {
  66. /* noop */
  67. },
  68. ),
  69. ).to.be.rejectedWith(
  70. `The ${sequelize.dialect.name} dialect does not support transaction types.`,
  71. );
  72. }
  73. await expect(
  74. sequelize.transaction(
  75. { transaction: transaction1, isolationLevel: IsolationLevel.READ_UNCOMMITTED },
  76. async () => {
  77. /* noop */
  78. },
  79. ),
  80. ).to.be.rejectedWith(
  81. 'Requested isolation level (READ UNCOMMITTED) is not compatible with the one of the existing transaction (unspecified)',
  82. );
  83. await expect(
  84. sequelize.transaction(
  85. { transaction: transaction1, constraintChecking: ConstraintChecking.IMMEDIATE },
  86. async () => {
  87. /* noop */
  88. },
  89. ),
  90. ).to.be.rejectedWith(
  91. 'Requested transaction constraintChecking (IMMEDIATE) is not compatible with the one of the existing transaction (none)',
  92. );
  93. await expect(
  94. sequelize.transaction({ transaction: transaction1, readOnly: true }, async () => {
  95. /* noop */
  96. }),
  97. ).to.be.rejectedWith(
  98. 'Requested a transaction in read-only mode, which is not compatible with the existing read/write transaction',
  99. );
  100. });
  101. });
  102. it('creates a savepoint if nestMode is set to "savepoint"', async () => {
  103. await sequelize.transaction(async transaction1 => {
  104. await sequelize.transaction(
  105. { transaction: transaction1, nestMode: TransactionNestMode.savepoint },
  106. async transaction2 => {
  107. expect(transaction1 === transaction2).to.equal(
  108. false,
  109. 'transaction1 and transaction2 should not be the same',
  110. );
  111. expect(transaction2.parent === transaction1).to.equal(
  112. true,
  113. 'transaction2.parent should be transaction1',
  114. );
  115. },
  116. );
  117. });
  118. });
  119. it('requires compatible options if nestMode is set to "savepoint"', async () => {
  120. await sequelize.transaction(async transaction1 => {
  121. const commonOptions = {
  122. transaction: transaction1,
  123. nestMode: TransactionNestMode.savepoint,
  124. };
  125. if (sequelize.dialect.supports.startTransaction.transactionType) {
  126. await expect(
  127. sequelize.transaction(
  128. { ...commonOptions, type: TransactionType.EXCLUSIVE },
  129. async () => {
  130. /* noop */
  131. },
  132. ),
  133. ).to.be.rejectedWith(
  134. 'Requested transaction type (EXCLUSIVE) is not compatible with the one of the existing transaction (DEFERRED)',
  135. );
  136. } else {
  137. await expect(
  138. sequelize.transaction(
  139. { ...commonOptions, type: TransactionType.EXCLUSIVE },
  140. async () => {
  141. /* noop */
  142. },
  143. ),
  144. ).to.be.rejectedWith(
  145. `The ${sequelize.dialect.name} dialect does not support transaction types.`,
  146. );
  147. }
  148. await expect(
  149. sequelize.transaction(
  150. { ...commonOptions, isolationLevel: IsolationLevel.READ_UNCOMMITTED },
  151. async () => {
  152. /* noop */
  153. },
  154. ),
  155. ).to.be.rejectedWith(
  156. 'Requested isolation level (READ UNCOMMITTED) is not compatible with the one of the existing transaction (unspecified)',
  157. );
  158. await expect(
  159. sequelize.transaction(
  160. { ...commonOptions, constraintChecking: ConstraintChecking.IMMEDIATE },
  161. async () => {
  162. /* noop */
  163. },
  164. ),
  165. ).to.be.rejectedWith(
  166. 'Requested transaction constraintChecking (IMMEDIATE) is not compatible with the one of the existing transaction (none)',
  167. );
  168. await expect(
  169. sequelize.transaction({ ...commonOptions, readOnly: true }, async () => {
  170. /* noop */
  171. }),
  172. ).to.be.rejectedWith(
  173. 'Requested a transaction in read-only mode, which is not compatible with the existing read/write transaction',
  174. );
  175. });
  176. });
  177. // sqlite cannot have more than one transaction at the same time, so separate is not available.
  178. if (dialectName !== 'sqlite3') {
  179. it('creates a new transaction if nestMode is set to "separate"', async () => {
  180. await sequelize.transaction(async transaction1 => {
  181. await sequelize.transaction(
  182. { transaction: transaction1, nestMode: TransactionNestMode.separate },
  183. async transaction2 => {
  184. expect(transaction1 === transaction2).to.equal(
  185. false,
  186. 'transaction1 and transaction2 should not be the same',
  187. );
  188. expect(transaction1.parent === null).to.equal(
  189. true,
  190. 'transaction1.parent should be null',
  191. );
  192. expect(transaction2.parent === null).to.equal(
  193. true,
  194. 'transaction2.parent should be null',
  195. );
  196. },
  197. );
  198. });
  199. });
  200. it('does not care about option compatibility when nestMode is set to "separate"', async () => {
  201. await sequelize.transaction(async transaction1 => {
  202. await sequelize.transaction(
  203. {
  204. transaction: transaction1,
  205. nestMode: TransactionNestMode.separate,
  206. type: sequelize.dialect.supports.startTransaction.transactionType
  207. ? TransactionType.EXCLUSIVE
  208. : undefined,
  209. isolationLevel: IsolationLevel.READ_UNCOMMITTED,
  210. constraintChecking: sequelize.dialect.supports.constraints.deferrable
  211. ? ConstraintChecking.DEFERRED
  212. : undefined,
  213. readOnly: true,
  214. },
  215. async () => {
  216. /* noop */
  217. },
  218. );
  219. });
  220. });
  221. }
  222. it(`defaults nestMode to sequelize's defaultTransactionNestMode option`, async () => {
  223. const customSequelize = await createSingleTransactionalTestSequelizeInstance(sequelize, {
  224. defaultTransactionNestMode: TransactionNestMode.savepoint,
  225. });
  226. await customSequelize.transaction(async transaction1 => {
  227. await customSequelize.transaction({ transaction: transaction1 }, async transaction2 => {
  228. expect(transaction1 === transaction2).to.equal(
  229. false,
  230. 'transaction1 and transaction2 should not be the same',
  231. );
  232. expect(transaction2.parent === transaction1).to.equal(
  233. true,
  234. 'transaction2.parent should be transaction1',
  235. );
  236. });
  237. });
  238. });
  239. });
  240. describe('Isolation Levels', () => {
  241. setResetMode('truncate');
  242. const vars = beforeAll2(async () => {
  243. const transactionSequelize = await createMultiTransactionalTestSequelizeInstance(sequelize);
  244. class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
  245. @Attribute(DataTypes.STRING)
  246. @NotNull
  247. declare name: string;
  248. @Attribute(DataTypes.INTEGER)
  249. @NotNull
  250. declare age: number;
  251. }
  252. transactionSequelize.addModels([User]);
  253. await transactionSequelize.sync({ force: true });
  254. return { transactionSequelize, User };
  255. });
  256. after(async () => {
  257. return vars.transactionSequelize.close();
  258. });
  259. beforeEach(async () => {
  260. await vars.User.create({ name: 'John Doe', age: 21 });
  261. });
  262. if (sequelize.dialect.supports.settingIsolationLevelDuringTransaction) {
  263. it('should allow setting the isolation level during a transaction', async () => {
  264. const { User, transactionSequelize } = vars;
  265. await transactionSequelize.transaction(async transaction => {
  266. await transaction.setIsolationLevel(IsolationLevel.READ_UNCOMMITTED);
  267. await User.update({ age: 22 }, { where: { name: 'John Doe' }, transaction });
  268. });
  269. if (dialectName !== 'sqlite3') {
  270. await transactionSequelize.transaction(async transaction => {
  271. await transaction.setIsolationLevel(IsolationLevel.READ_COMMITTED);
  272. const johnDoe = await User.findOne({ where: { name: 'John Doe' }, transaction });
  273. assert(johnDoe, 'John Doe should exist');
  274. expect(johnDoe.age).to.equal(22);
  275. });
  276. await transactionSequelize.transaction(async transaction => {
  277. await transaction.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
  278. const users = await User.findAll({ transaction });
  279. expect(users.length).to.equal(1);
  280. expect(users[0].name).to.equal('John Doe');
  281. expect(users[0].age).to.equal(22);
  282. });
  283. }
  284. await transactionSequelize.transaction(async transaction => {
  285. await transaction.setIsolationLevel(IsolationLevel.SERIALIZABLE);
  286. await User.create({ name: 'Jane Doe', age: 21 }, { transaction });
  287. });
  288. });
  289. }
  290. // SQLite only supports read uncommitted and serializable.
  291. if (dialectName !== 'sqlite3') {
  292. it('should read the most recent committed rows when using the READ COMMITTED isolation level', async () => {
  293. const { User, transactionSequelize } = vars;
  294. await transactionSequelize.transaction(
  295. { isolationLevel: IsolationLevel.READ_COMMITTED },
  296. async transaction => {
  297. const users0 = await User.findAll({ transaction });
  298. expect(users0).to.have.lengthOf(1);
  299. await User.create({ name: 'Jane Doe', age: 21 }); // Create a User outside of the transaction
  300. const users = await User.findAll({ transaction });
  301. expect(users).to.have.lengthOf(2); // We SHOULD see the created user inside the transaction
  302. },
  303. );
  304. });
  305. }
  306. // These dialects do not allow dirty reads with isolation level "READ UNCOMMITTED".
  307. if (!['postgres', 'sqlite3'].includes(dialectName)) {
  308. it('should allow dirty read with isolation level "READ UNCOMMITTED"', async () => {
  309. const { User, transactionSequelize } = vars;
  310. const t1 = await transactionSequelize.startUnmanagedTransaction({
  311. isolationLevel: IsolationLevel.READ_UNCOMMITTED,
  312. });
  313. try {
  314. await User.update({ age: 22 }, { where: { name: 'John Doe' }, transaction: t1 });
  315. await transactionSequelize.transaction(
  316. { isolationLevel: IsolationLevel.READ_UNCOMMITTED },
  317. async transaction => {
  318. const johnDoe = await User.findOne({ where: { name: 'John Doe' }, transaction });
  319. assert(johnDoe, 'John Doe should exist');
  320. expect(johnDoe.age).to.equal(22);
  321. },
  322. );
  323. } finally {
  324. await t1.rollback();
  325. const johnDoe = await User.findOne({ where: { name: 'John Doe' } });
  326. assert(johnDoe, 'John Doe should exist');
  327. expect(johnDoe.age).to.equal(21);
  328. }
  329. });
  330. }
  331. // SQLite only supports read uncommitted and serializable.
  332. if (dialectName !== 'sqlite3') {
  333. it('should prevent dirty read with isolation level "READ COMMITTED"', async () => {
  334. const { User, transactionSequelize } = vars;
  335. const t1 = await transactionSequelize.startUnmanagedTransaction({
  336. isolationLevel: IsolationLevel.READ_COMMITTED,
  337. });
  338. try {
  339. await User.update({ age: 22 }, { where: { name: 'John Doe' }, transaction: t1 });
  340. await transactionSequelize.transaction(
  341. { isolationLevel: IsolationLevel.READ_COMMITTED },
  342. async transaction => {
  343. const johnDoe = await User.findOne({ where: { name: 'John Doe' }, transaction });
  344. assert(johnDoe, 'John Doe should exist');
  345. expect(johnDoe.age).to.equal(21);
  346. },
  347. );
  348. } finally {
  349. await t1.rollback();
  350. const johnDoe = await User.findOne({ where: { name: 'John Doe' } });
  351. assert(johnDoe, 'John Doe should exist');
  352. expect(johnDoe.age).to.equal(21);
  353. }
  354. });
  355. }
  356. // SQLite only supports read uncommitted and serializable.
  357. if (dialectName !== 'sqlite3') {
  358. it('should allow non-repeatable read with isolation level "READ COMMITTED"', async () => {
  359. const { User, transactionSequelize } = vars;
  360. const t1 = await transactionSequelize.startUnmanagedTransaction({
  361. isolationLevel: IsolationLevel.READ_COMMITTED,
  362. });
  363. try {
  364. const johnDoe = await User.findOne({ where: { name: 'John Doe' }, transaction: t1 });
  365. assert(johnDoe, 'John Doe should exist');
  366. expect(johnDoe.name).to.equal('John Doe');
  367. expect(johnDoe.age).to.equal(21);
  368. await transactionSequelize.transaction(
  369. { isolationLevel: IsolationLevel.READ_COMMITTED },
  370. async transaction => {
  371. await User.update({ age: 22 }, { where: { name: 'John Doe' }, transaction });
  372. },
  373. );
  374. const johnDoe1 = await User.findOne({ where: { name: 'John Doe' }, transaction: t1 });
  375. assert(johnDoe1, 'John Doe should exist');
  376. expect(johnDoe1.name).to.equal('John Doe');
  377. expect(johnDoe1.age).to.equal(22);
  378. await t1.commit();
  379. } catch (error) {
  380. await t1.rollback();
  381. throw error;
  382. }
  383. });
  384. }
  385. // These dialects do not allow phantom reads with isolation level "REPEATABLE READ" as they use snapshot rather than locking.
  386. if (['mariadb', 'mysql', 'postgres'].includes(dialectName)) {
  387. it('should not read newly committed rows when using the REPEATABLE READ isolation level', async () => {
  388. const { User, transactionSequelize } = vars;
  389. await transactionSequelize.transaction(
  390. { isolationLevel: IsolationLevel.REPEATABLE_READ },
  391. async transaction => {
  392. const users0 = await User.findAll({ transaction });
  393. expect(users0).to.have.lengthOf(1);
  394. await User.create({ name: 'Jane Doe', age: 21 }); // Create a User outside of the transaction
  395. const users = await User.findAll({ transaction });
  396. expect(users).to.have.lengthOf(1); // We SHOULD NOT see the created user inside the transaction
  397. },
  398. );
  399. });
  400. // SQLite only supports read uncommitted and serializable.
  401. } else if (dialectName !== 'sqlite3') {
  402. it('should allow phantom read with isolation level "REPEATABLE READ"', async () => {
  403. const { User, transactionSequelize } = vars;
  404. const t1 = await transactionSequelize.startUnmanagedTransaction({
  405. isolationLevel: IsolationLevel.REPEATABLE_READ,
  406. });
  407. try {
  408. const users = await User.findAll({ transaction: t1 });
  409. expect(users.length).to.equal(1);
  410. expect(users[0].name).to.equal('John Doe');
  411. expect(users[0].age).to.equal(21);
  412. await transactionSequelize.transaction(
  413. { isolationLevel: IsolationLevel.REPEATABLE_READ },
  414. async transaction => {
  415. await User.create({ name: 'Jane Doe', age: 21 }, { transaction });
  416. },
  417. );
  418. const users2 = await User.findAll({ transaction: t1 });
  419. expect(users2.length).to.equal(2);
  420. expect(users2[0].name).to.equal('John Doe');
  421. expect(users2[0].age).to.equal(21);
  422. expect(users2[1].name).to.equal('Jane Doe');
  423. expect(users2[1].age).to.equal(21);
  424. await t1.commit();
  425. } catch (error) {
  426. await t1.rollback();
  427. throw error;
  428. }
  429. });
  430. }
  431. // PostgreSQL is excluded because it detects Serialization Failure on commit instead of acquiring locks on the read rows
  432. if (!['postgres'].includes(dialectName)) {
  433. it('should block updates after reading a row using SERIALIZABLE', async () => {
  434. const { User, transactionSequelize } = vars;
  435. const transactionSpy = sinon.spy();
  436. const transaction = await transactionSequelize.startUnmanagedTransaction({
  437. isolationLevel: IsolationLevel.SERIALIZABLE,
  438. });
  439. await User.findAll({ transaction });
  440. await Promise.all([
  441. // Update should not succeed before transaction has committed
  442. User.update({ age: 25 }, { where: { name: 'John Doe' } }).then(() => {
  443. expect(transactionSpy).to.have.been.called;
  444. expect(transaction.finished).to.equal('commit');
  445. }),
  446. delay(4000)
  447. .then(transactionSpy)
  448. .then(async () => transaction.commit()),
  449. ]);
  450. });
  451. }
  452. });
  453. describe('Transaction#commit', () => {
  454. it('returns a promise that resolves once the transaction has been committed', async () => {
  455. const t = await sequelize.startUnmanagedTransaction();
  456. await expect(t.commit()).to.eventually.equal(undefined);
  457. });
  458. // we cannot close a sqlite connection, but there also cannot be a network error with sqlite.
  459. // so this test is not necessary for that dialect.
  460. if (dialectName !== 'sqlite3') {
  461. it('does not pollute the pool with broken connections if commit fails', async () => {
  462. const initialPoolSize = sequelize.pool.size;
  463. stubs.push(
  464. sinon
  465. .stub(sequelize.queryInterface, '_commitTransaction')
  466. .rejects(new Error('Oh no, an error!')),
  467. );
  468. const t = await sequelize.startUnmanagedTransaction();
  469. await expect(t.commit()).to.be.rejectedWith('Oh no, an error!');
  470. // connection should have been destroyed
  471. expect(sequelize.pool.size).to.eq(Math.max(0, initialPoolSize - 1));
  472. });
  473. }
  474. });
  475. describe('Transaction#rollback', () => {
  476. it('returns a promise that resolves once the transaction has been rolled back', async () => {
  477. const t = await sequelize.startUnmanagedTransaction();
  478. await expect(t.rollback()).to.eventually.equal(undefined);
  479. });
  480. // we cannot close a sqlite connection, but there also cannot be a network error with sqlite.
  481. // so this test is not necessary for that dialect.
  482. if (dialectName !== 'sqlite3') {
  483. it('does not pollute the pool with broken connections if the rollback fails', async () => {
  484. const initialPoolSize = sequelize.pool.size;
  485. stubs.push(
  486. sinon
  487. .stub(sequelize.queryInterface, '_rollbackTransaction')
  488. .rejects(new Error('Oh no, an error!')),
  489. );
  490. const t = await sequelize.startUnmanagedTransaction();
  491. await expect(t.rollback()).to.be.rejectedWith('Oh no, an error!');
  492. // connection should have been destroyed
  493. expect(sequelize.pool.size).to.eq(Math.max(0, initialPoolSize - 1));
  494. });
  495. }
  496. });
  497. if (getTestDialect() !== 'sqlite3' && getTestDialect() !== 'db2') {
  498. it('works for long running transactions', async () => {
  499. const sequelize2 = await createSingleTransactionalTestSequelizeInstance(sequelize);
  500. interface IUser extends Model<InferAttributes<IUser>, InferCreationAttributes<IUser>> {
  501. name: string | null;
  502. }
  503. const User = sequelize2.define<IUser>(
  504. 'User',
  505. {
  506. name: DataTypes.STRING,
  507. },
  508. { timestamps: false },
  509. );
  510. await sequelize2.sync({ force: true });
  511. const t = await sequelize2.startUnmanagedTransaction();
  512. let query: string;
  513. switch (getTestDialect()) {
  514. case 'postgres':
  515. query = 'select pg_sleep(2);';
  516. break;
  517. case 'sqlite3':
  518. query = 'select sqlite3_sleep(2000);';
  519. break;
  520. case 'mssql':
  521. query = "WAITFOR DELAY '00:00:02';";
  522. break;
  523. default:
  524. query = 'select sleep(2);';
  525. break;
  526. }
  527. await sequelize2.query(query, { transaction: t });
  528. await User.create({ name: 'foo' });
  529. await sequelize2.query(query, { transaction: t });
  530. await t.commit();
  531. const users = await User.findAll();
  532. expect(users.length).to.equal(1);
  533. expect(users[0].name).to.equal('foo');
  534. });
  535. }
  536. describe('complex long running example', () => {
  537. it('works with promise syntax', async () => {
  538. const sequelize2 = await createSingleTransactionalTestSequelizeInstance(sequelize);
  539. const Test = sequelize2.define('Test', {
  540. id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
  541. name: { type: DataTypes.STRING },
  542. });
  543. await sequelize2.sync({ force: true });
  544. const transaction = await sequelize2.startUnmanagedTransaction();
  545. expect(transaction).to.be.instanceOf(Transaction);
  546. await Test.create({ name: 'Peter' }, { transaction });
  547. await delay(1000);
  548. await transaction.commit();
  549. const count = await Test.count();
  550. expect(count).to.equal(1);
  551. });
  552. });
  553. describe('concurrency: having tables with uniqueness constraints', () => {
  554. it('triggers the error event for the second transactions', async () => {
  555. const sequelize2 = await createSingleTransactionalTestSequelizeInstance(sequelize);
  556. const User = sequelize2.define(
  557. 'User',
  558. {
  559. name: { type: DataTypes.STRING, unique: true },
  560. },
  561. {
  562. timestamps: false,
  563. },
  564. );
  565. await User.sync({ force: true });
  566. const t1 = await sequelize2.startUnmanagedTransaction();
  567. const t2 = await sequelize2.startUnmanagedTransaction();
  568. await User.create({ name: 'omnom' }, { transaction: t1 });
  569. await Promise.all([
  570. (async () => {
  571. try {
  572. return await User.create({ name: 'omnom' }, { transaction: t2 });
  573. } catch (error) {
  574. expect(error).to.be.ok;
  575. return t2.rollback();
  576. }
  577. })(),
  578. delay(100).then(async () => {
  579. return t1.commit();
  580. }),
  581. ]);
  582. });
  583. });
  584. });