sequelize.test.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  1. 'use strict';
  2. const cloneDeep = require('lodash/cloneDeep');
  3. const { assert, expect } = require('chai');
  4. const { ConnectionError, DataTypes, literal, Transaction } = require('@sequelize/core');
  5. const {
  6. allowDeprecationsInSuite,
  7. beforeAll2,
  8. beforeEach2,
  9. createMultiTransactionalTestSequelizeInstance,
  10. createSequelizeInstance,
  11. createSingleTestSequelizeInstance,
  12. getTestDialect,
  13. getTestDialectTeaser,
  14. rand,
  15. sequelize: current,
  16. } = require('./support');
  17. const sinon = require('sinon');
  18. const { CONFIG } = require('../config/config');
  19. const dialect = getTestDialect();
  20. const qq = str => {
  21. if (['postgres', 'mssql', 'db2', 'ibmi'].includes(dialect)) {
  22. return `"${str}"`;
  23. }
  24. if (['mysql', 'mariadb', 'sqlite3'].includes(dialect)) {
  25. return `\`${str}\``;
  26. }
  27. return str;
  28. };
  29. const badUsernameConfig = {
  30. postgres: {
  31. user: 'bad_user',
  32. },
  33. db2: {
  34. username: 'bad_user',
  35. },
  36. ibmi: {
  37. username: 'bad_user',
  38. },
  39. mariadb: {
  40. user: 'bad_user',
  41. },
  42. mysql: {
  43. user: 'bad_user',
  44. },
  45. mssql: {
  46. authentication: {
  47. ...CONFIG.mssql.authentication,
  48. type: 'default',
  49. options: {
  50. ...CONFIG.mssql.authentication.options,
  51. userName: 'bad_user',
  52. },
  53. },
  54. },
  55. snowflake: {
  56. account: 'bad_account',
  57. },
  58. };
  59. const noPasswordConfig = {
  60. postgres: {
  61. password: null,
  62. },
  63. db2: {
  64. password: null,
  65. },
  66. ibmi: {
  67. password: null,
  68. },
  69. mariadb: {
  70. password: null,
  71. },
  72. mysql: {
  73. password: null,
  74. },
  75. mssql: {
  76. authentication: {
  77. ...CONFIG.mssql.authentication,
  78. type: 'default',
  79. options: {
  80. ...CONFIG.mssql.authentication.options,
  81. password: '',
  82. },
  83. },
  84. },
  85. snowflake: {
  86. password: null,
  87. },
  88. };
  89. const badAddressConfig = {
  90. postgres: {
  91. port: 9999,
  92. },
  93. mysql: {
  94. port: 9999,
  95. },
  96. mssql: {
  97. port: 9999,
  98. },
  99. mariadb: {
  100. port: 9999,
  101. },
  102. sqlite3: {},
  103. snowflake: {
  104. accessUrl: 'https://bad-address',
  105. },
  106. db2: {
  107. port: 9999,
  108. },
  109. ibmi: {
  110. system: 'bad-address',
  111. },
  112. };
  113. describe(getTestDialectTeaser('Sequelize'), () => {
  114. if (dialect !== 'sqlite3') {
  115. describe('authenticate', () => {
  116. describe('with valid credentials', () => {
  117. it('triggers the success event', async function () {
  118. await this.sequelize.authenticate();
  119. });
  120. });
  121. describe('with an invalid connection', () => {
  122. beforeEach(function () {
  123. this.sequelizeWithInvalidConnection = createSingleTestSequelizeInstance(
  124. badAddressConfig[dialect],
  125. );
  126. });
  127. it('rejects', async function () {
  128. await expect(this.sequelizeWithInvalidConnection.authenticate()).to.be.rejectedWith(
  129. ConnectionError,
  130. );
  131. });
  132. it('triggers the actual adapter error', async function () {
  133. const error = await expect(this.sequelizeWithInvalidConnection.authenticate()).to.be
  134. .rejected;
  135. console.log(error.message);
  136. expect(
  137. error.message.includes('connect ECONNREFUSED') ||
  138. error.message.includes('Connection refused') ||
  139. error.message.includes('Could not connect') ||
  140. error.message.includes('invalid port number') ||
  141. error.message.match(/should be >=? 0 and < 65536/) ||
  142. error.message.includes('Login failed for user') ||
  143. error.message.includes('A communication error has been detected') ||
  144. error.message.includes('must be > 0 and < 65536') ||
  145. error.message.includes('Error connecting to the database'),
  146. ).to.be.ok;
  147. });
  148. });
  149. describe('with invalid credentials', () => {
  150. beforeEach(function () {
  151. this.sequelizeWithInvalidCredentials = createSingleTestSequelizeInstance(
  152. badUsernameConfig[dialect],
  153. );
  154. });
  155. it('rejects', async function () {
  156. await expect(this.sequelizeWithInvalidCredentials.authenticate()).to.be.rejectedWith(
  157. ConnectionError,
  158. );
  159. });
  160. if (dialect !== 'db2') {
  161. it('triggers the error event when using replication', async () => {
  162. const sequelize = createSequelizeInstance({
  163. dialect,
  164. replication: {
  165. read: [badUsernameConfig[dialect]],
  166. },
  167. });
  168. await expect(sequelize.authenticate()).to.be.rejected;
  169. await sequelize.close();
  170. });
  171. }
  172. });
  173. });
  174. describe('validate', () => {
  175. it('is an alias for .authenticate()', function () {
  176. expect(this.sequelize.validate).to.equal(this.sequelize.authenticate);
  177. });
  178. });
  179. }
  180. describe('getDialect', () => {
  181. allowDeprecationsInSuite(['SEQUELIZE0031']);
  182. it('returns the defined dialect', function () {
  183. expect(this.sequelize.getDialect()).to.equal(dialect);
  184. });
  185. });
  186. describe('isDefined', () => {
  187. allowDeprecationsInSuite(['SEQUELIZE0029']);
  188. it("returns false if the dao wasn't defined before", function () {
  189. expect(this.sequelize.isDefined('Project')).to.be.false;
  190. });
  191. it('returns true if the dao was defined before', function () {
  192. this.sequelize.define('Project', {
  193. name: DataTypes.STRING,
  194. });
  195. expect(this.sequelize.isDefined('Project')).to.be.true;
  196. });
  197. });
  198. describe('model', () => {
  199. allowDeprecationsInSuite(['SEQUELIZE0028']);
  200. it('throws an error if the dao being accessed is undefined', function () {
  201. expect(() => {
  202. this.sequelize.model('Project');
  203. }).to.throw(`Model 'Project' was not added to this Sequelize instance`);
  204. });
  205. it('returns the dao factory defined by daoName', function () {
  206. const project = this.sequelize.define('Project', {
  207. name: DataTypes.STRING,
  208. });
  209. expect(this.sequelize.model('Project')).to.equal(project);
  210. });
  211. });
  212. describe('define', () => {
  213. it('adds a new dao to the dao manager', function () {
  214. const count = this.sequelize.models.size;
  215. this.sequelize.define('foo', { title: DataTypes.STRING });
  216. expect(this.sequelize.models.size).to.equal(count + 1);
  217. });
  218. it('adds a new dao to sequelize.models', function () {
  219. expect(this.sequelize.models.get('bar')).to.equal(undefined);
  220. const Bar = this.sequelize.define('bar', { title: DataTypes.STRING });
  221. expect(this.sequelize.models.get('bar')).to.equal(Bar);
  222. });
  223. it('overwrites global options', () => {
  224. const sequelize = createSingleTestSequelizeInstance({
  225. define: { collate: 'utf8_general_ci' },
  226. });
  227. const DAO = sequelize.define('foo', { bar: DataTypes.STRING }, { collate: 'utf8_bin' });
  228. expect(DAO.options.collate).to.equal('utf8_bin');
  229. });
  230. it('overwrites global rowFormat options', () => {
  231. const sequelize = createSingleTestSequelizeInstance({
  232. define: { rowFormat: 'compact' },
  233. });
  234. const DAO = sequelize.define('foo', { bar: DataTypes.STRING }, { rowFormat: 'default' });
  235. expect(DAO.options.rowFormat).to.equal('default');
  236. });
  237. it('inherits global collate option', () => {
  238. const sequelize = createSingleTestSequelizeInstance({
  239. define: { collate: 'utf8_general_ci' },
  240. });
  241. const DAO = sequelize.define('foo', { bar: DataTypes.STRING });
  242. expect(DAO.options.collate).to.equal('utf8_general_ci');
  243. });
  244. it('inherits global rowFormat option', () => {
  245. const sequelize = createSingleTestSequelizeInstance({
  246. define: { rowFormat: 'default' },
  247. });
  248. const DAO = sequelize.define('foo', { bar: DataTypes.STRING });
  249. expect(DAO.options.rowFormat).to.equal('default');
  250. });
  251. it('uses the passed tableName', async function () {
  252. const Photo = this.sequelize.define(
  253. 'Foto',
  254. { name: DataTypes.STRING },
  255. { tableName: 'photos' },
  256. );
  257. await Photo.sync({ force: true });
  258. const result = await this.sequelize.queryInterface.listTables();
  259. const tableNames = result.map(v => v.tableName);
  260. expect(tableNames).to.include('photos');
  261. });
  262. });
  263. describe('truncate', () => {
  264. it('truncates all models', async function () {
  265. const Project = this.sequelize.define(`project${rand()}`, {
  266. id: {
  267. type: DataTypes.INTEGER,
  268. primaryKey: true,
  269. autoIncrement: true,
  270. },
  271. title: DataTypes.STRING,
  272. });
  273. await this.sequelize.sync({ force: true });
  274. const project = await Project.create({ title: 'bla' });
  275. expect(project).to.exist;
  276. expect(project.title).to.equal('bla');
  277. expect(project.id).to.equal(1);
  278. await this.sequelize.truncate();
  279. const projects = await Project.findAll({});
  280. expect(projects).to.exist;
  281. expect(projects).to.have.length(0);
  282. });
  283. });
  284. describe('sync', () => {
  285. it('synchronizes all models', async function () {
  286. const Project = this.sequelize.define(`project${rand()}`, {
  287. title: DataTypes.STRING,
  288. });
  289. const Task = this.sequelize.define(`task${rand()}`, { title: DataTypes.STRING });
  290. await Project.sync({ force: true });
  291. await Task.sync({ force: true });
  292. await Project.create({ title: 'bla' });
  293. const task = await Task.create({ title: 'bla' });
  294. expect(task).to.exist;
  295. expect(task.title).to.equal('bla');
  296. });
  297. it('works with correct database credentials', async function () {
  298. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  299. await User.sync();
  300. expect(true).to.be.true;
  301. });
  302. if (dialect !== 'sqlite3' && dialect !== 'db2') {
  303. it('fails for incorrect connection even when no models are defined', async () => {
  304. const sequelize = createSingleTestSequelizeInstance(badUsernameConfig[dialect]);
  305. await expect(sequelize.sync({ force: true })).to.be.rejected;
  306. });
  307. it('fails when no password is provided', async () => {
  308. const sequelizeWithInvalidCredentials = createSingleTestSequelizeInstance(
  309. noPasswordConfig[dialect],
  310. );
  311. const User2 = sequelizeWithInvalidCredentials.define('User', {
  312. name: DataTypes.STRING,
  313. bio: DataTypes.TEXT,
  314. });
  315. try {
  316. await User2.sync();
  317. expect.fail();
  318. } catch (error) {
  319. switch (dialect) {
  320. case 'postgres': {
  321. assert(
  322. [
  323. 'fe_sendauth: no password supplied',
  324. 'password authentication failed for user "sequelize_test"',
  325. 'SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string',
  326. ].some(fragment => error.message.includes(fragment)),
  327. );
  328. break;
  329. }
  330. case 'mssql': {
  331. expect(error.message).to.include("Login failed for user 'SA'.");
  332. break;
  333. }
  334. case 'db2': {
  335. expect(error.message).to.include('A communication error has been detected');
  336. break;
  337. }
  338. case 'ibmi': {
  339. expect(error.message).to.equal('[odbc] Error connecting to the database');
  340. expect(error.cause.odbcErrors[0].message).to.include(
  341. 'Data source name not found and no default driver specified',
  342. );
  343. break;
  344. }
  345. default: {
  346. expect(error.message.toString()).to.match(/.*Access denied.*/);
  347. }
  348. }
  349. }
  350. });
  351. it('returns an error correctly if unable to sync a foreign key referenced model', async function () {
  352. this.sequelize.define('Application', {
  353. authorID: {
  354. type: DataTypes.BIGINT,
  355. allowNull: false,
  356. references: {
  357. model: 'User',
  358. key: 'id',
  359. },
  360. },
  361. });
  362. await expect(this.sequelize.sync()).to.be.rejected;
  363. });
  364. }
  365. it('return the sequelize instance after syncing', async function () {
  366. const sequelize = await this.sequelize.sync();
  367. expect(sequelize).to.deep.equal(this.sequelize);
  368. });
  369. it('return the single dao after syncing', async function () {
  370. const block = this.sequelize.define(
  371. 'block',
  372. {
  373. id: { type: DataTypes.INTEGER, primaryKey: true },
  374. name: DataTypes.STRING,
  375. },
  376. {
  377. tableName: 'block',
  378. timestamps: false,
  379. paranoid: false,
  380. },
  381. );
  382. const result = await block.sync();
  383. expect(result).to.deep.equal(block);
  384. });
  385. it('handles alter: true with underscore correctly', async function () {
  386. this.sequelize.define(
  387. 'access_metric',
  388. {
  389. user_id: {
  390. type: DataTypes.INTEGER,
  391. },
  392. },
  393. {
  394. underscored: true,
  395. },
  396. );
  397. await this.sequelize.sync({
  398. alter: true,
  399. });
  400. });
  401. describe("doesn't emit logging when explicitly saying not to", () => {
  402. const vars = beforeEach2(() => {
  403. const spy = sinon.spy();
  404. const sequelize = createSequelizeInstance({
  405. logging: spy,
  406. });
  407. const User = sequelize.define('UserTest', { username: DataTypes.STRING });
  408. return { spy, sequelize, User };
  409. });
  410. afterEach(() => {
  411. return vars.sequelize.close();
  412. });
  413. it('through Sequelize.sync()', async () => {
  414. await vars.sequelize.sync({ force: true, logging: false });
  415. expect(vars.spy.notCalled).to.be.true;
  416. });
  417. it('through Model.sync()', async () => {
  418. vars.spy.resetHistory();
  419. await vars.User.sync({ force: true, logging: false });
  420. expect(vars.spy.notCalled).to.be.true;
  421. });
  422. });
  423. });
  424. describe('Model.drop', () => {
  425. it('drops the table corresponding to the model', async function () {
  426. const User = this.sequelize.define('Users', { username: DataTypes.STRING });
  427. await User.sync({ force: true });
  428. await User.drop();
  429. });
  430. });
  431. describe('define', () => {
  432. describe('table', () => {
  433. const attributeList = [
  434. { id: { type: DataTypes.INTEGER, primaryKey: true } },
  435. { id: { type: DataTypes.STRING, allowNull: true, primaryKey: true } },
  436. {
  437. id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  438. },
  439. ];
  440. if (current.dialect.supports.dataTypes.BIGINT) {
  441. attributeList.push(
  442. { id: { type: DataTypes.BIGINT, primaryKey: true } },
  443. {
  444. id: { type: DataTypes.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true },
  445. },
  446. );
  447. }
  448. for (const customAttributes of attributeList) {
  449. it('should be able to override options on the default attributes', async function () {
  450. const Picture = this.sequelize.define('picture', cloneDeep(customAttributes));
  451. await Picture.sync({ force: true });
  452. for (const attribute of Object.keys(customAttributes)) {
  453. for (const option of Object.keys(customAttributes[attribute])) {
  454. const optionValue = customAttributes[attribute][option];
  455. if (
  456. typeof optionValue === 'function' &&
  457. optionValue() instanceof DataTypes.ABSTRACT
  458. ) {
  459. expect(Picture.getAttributes()[attribute][option] instanceof optionValue).to.be.ok;
  460. } else {
  461. expect(Picture.getAttributes()[attribute][option]).to.equal(optionValue);
  462. }
  463. }
  464. }
  465. });
  466. }
  467. });
  468. if (current.dialect.supports.transactions) {
  469. describe('transaction', () => {
  470. const vars = beforeAll2(async () => {
  471. const sequelizeWithTransaction =
  472. await createMultiTransactionalTestSequelizeInstance(current);
  473. return { sequelizeWithTransaction };
  474. });
  475. after(() => {
  476. return vars.sequelizeWithTransaction.close();
  477. });
  478. it('returns a transaction object', async () => {
  479. const t = await vars.sequelizeWithTransaction.startUnmanagedTransaction();
  480. expect(t).to.be.instanceOf(Transaction);
  481. await t.commit();
  482. });
  483. if (dialect === 'sqlite3') {
  484. it('correctly scopes transaction from other connections', async function () {
  485. const TransactionTest = vars.sequelizeWithTransaction.define(
  486. 'TransactionTest',
  487. { name: DataTypes.STRING },
  488. { timestamps: false },
  489. );
  490. const count = async transaction => {
  491. const sql = vars.sequelizeWithTransaction.queryGenerator.selectQuery(
  492. 'TransactionTests',
  493. { attributes: [[literal('count(*)'), 'cnt']] },
  494. );
  495. const result = await vars.sequelizeWithTransaction.query(sql, {
  496. plain: true,
  497. transaction,
  498. });
  499. return result.cnt;
  500. };
  501. await TransactionTest.sync({ force: true });
  502. const t1 = await vars.sequelizeWithTransaction.startUnmanagedTransaction();
  503. this.t1 = t1;
  504. await vars.sequelizeWithTransaction.query(
  505. `INSERT INTO ${qq('TransactionTests')} (${qq('name')})
  506. VALUES ('foo');`,
  507. { transaction: t1 },
  508. );
  509. await expect(count()).to.eventually.equal(0);
  510. await expect(count(this.t1)).to.eventually.equal(1);
  511. await this.t1.commit();
  512. await expect(count()).to.eventually.equal(1);
  513. });
  514. } else {
  515. it('correctly handles multiple transactions', async function () {
  516. const TransactionTest = vars.sequelizeWithTransaction.define(
  517. 'TransactionTest',
  518. { name: DataTypes.STRING },
  519. { timestamps: false },
  520. );
  521. const aliasesMapping = new Map([['_0', 'cnt']]);
  522. const count = async transaction => {
  523. const sql = vars.sequelizeWithTransaction.queryGenerator.selectQuery(
  524. 'TransactionTests',
  525. { attributes: [[literal('count(*)'), 'cnt']] },
  526. );
  527. const result = await vars.sequelizeWithTransaction.query(sql, {
  528. plain: true,
  529. transaction,
  530. aliasesMapping,
  531. });
  532. return Number.parseInt(result.cnt, 10);
  533. };
  534. await TransactionTest.sync({ force: true });
  535. const t1 = await vars.sequelizeWithTransaction.startUnmanagedTransaction();
  536. this.t1 = t1;
  537. await vars.sequelizeWithTransaction.query(
  538. `INSERT INTO ${qq('TransactionTests')} (${qq('name')})
  539. VALUES ('foo');`,
  540. { transaction: t1 },
  541. );
  542. const t2 = await vars.sequelizeWithTransaction.startUnmanagedTransaction();
  543. this.t2 = t2;
  544. await vars.sequelizeWithTransaction.query(
  545. `INSERT INTO ${qq('TransactionTests')} (${qq('name')})
  546. VALUES ('bar');`,
  547. { transaction: t2 },
  548. );
  549. await expect(count()).to.eventually.equal(0);
  550. await expect(count(this.t1)).to.eventually.equal(1);
  551. await expect(count(this.t2)).to.eventually.equal(1);
  552. await this.t2.rollback();
  553. await expect(count()).to.eventually.equal(0);
  554. await this.t1.commit();
  555. await expect(count()).to.eventually.equal(1);
  556. });
  557. }
  558. it('supports nested transactions using savepoints', async () => {
  559. const User = vars.sequelizeWithTransaction.define('Users', {
  560. username: DataTypes.STRING,
  561. });
  562. await User.sync({ force: true });
  563. const t1 = await vars.sequelizeWithTransaction.startUnmanagedTransaction();
  564. const user = await User.create({ username: 'foo' }, { transaction: t1 });
  565. const t2 = await vars.sequelizeWithTransaction.startUnmanagedTransaction({
  566. transaction: t1,
  567. });
  568. await user.update({ username: 'bar' }, { transaction: t2 });
  569. await t2.commit();
  570. const newUser = await user.reload({ transaction: t1 });
  571. expect(newUser.username).to.equal('bar');
  572. await t1.commit();
  573. });
  574. describe('supports rolling back to savepoints', () => {
  575. beforeEach(async function () {
  576. this.User = vars.sequelizeWithTransaction.define('user', {});
  577. await vars.sequelizeWithTransaction.sync({ force: true });
  578. });
  579. it('rolls back to the first savepoint, undoing everything', async function () {
  580. const transaction = await vars.sequelizeWithTransaction.startUnmanagedTransaction();
  581. this.transaction = transaction;
  582. const sp1 = await vars.sequelizeWithTransaction.startUnmanagedTransaction({
  583. transaction,
  584. });
  585. this.sp1 = sp1;
  586. await this.User.create({}, { transaction: this.transaction });
  587. const sp2 = await vars.sequelizeWithTransaction.startUnmanagedTransaction({
  588. transaction: this.transaction,
  589. });
  590. this.sp2 = sp2;
  591. await this.User.create({}, { transaction: this.transaction });
  592. const users0 = await this.User.findAll({ transaction: this.transaction });
  593. expect(users0).to.have.length(2);
  594. await this.sp1.rollback();
  595. const users = await this.User.findAll({ transaction: this.transaction });
  596. expect(users).to.have.length(0);
  597. await this.transaction.rollback();
  598. });
  599. it('rolls back to the most recent savepoint, only undoing recent changes', async function () {
  600. const transaction = await vars.sequelizeWithTransaction.startUnmanagedTransaction();
  601. this.transaction = transaction;
  602. const sp1 = await vars.sequelizeWithTransaction.startUnmanagedTransaction({
  603. transaction,
  604. });
  605. this.sp1 = sp1;
  606. await this.User.create({}, { transaction: this.transaction });
  607. const sp2 = await vars.sequelizeWithTransaction.startUnmanagedTransaction({
  608. transaction: this.transaction,
  609. });
  610. this.sp2 = sp2;
  611. await this.User.create({}, { transaction: this.transaction });
  612. const users0 = await this.User.findAll({ transaction: this.transaction });
  613. expect(users0).to.have.length(2);
  614. await this.sp2.rollback();
  615. const users = await this.User.findAll({ transaction: this.transaction });
  616. expect(users).to.have.length(1);
  617. await this.transaction.rollback();
  618. });
  619. });
  620. it('supports rolling back a nested transaction', async () => {
  621. const User = vars.sequelizeWithTransaction.define('Users', {
  622. username: DataTypes.STRING,
  623. });
  624. await User.sync({ force: true });
  625. const t1 = await vars.sequelizeWithTransaction.startUnmanagedTransaction();
  626. const user = await User.create({ username: 'foo' }, { transaction: t1 });
  627. const t2 = await vars.sequelizeWithTransaction.startUnmanagedTransaction({
  628. transaction: t1,
  629. });
  630. await user.update({ username: 'bar' }, { transaction: t2 });
  631. await t2.rollback();
  632. const newUser = await user.reload({ transaction: t1 });
  633. expect(newUser.username).to.equal('foo');
  634. await t1.commit();
  635. });
  636. it('supports rolling back outermost transaction', async () => {
  637. const User = vars.sequelizeWithTransaction.define('Users', {
  638. username: DataTypes.STRING,
  639. });
  640. await User.sync({ force: true });
  641. const t1 = await vars.sequelizeWithTransaction.startUnmanagedTransaction();
  642. const user = await User.create({ username: 'foo' }, { transaction: t1 });
  643. const t2 = await vars.sequelizeWithTransaction.startUnmanagedTransaction({
  644. transaction: t1,
  645. });
  646. await user.update({ username: 'bar' }, { transaction: t2 });
  647. await t1.rollback();
  648. const users = await User.findAll();
  649. expect(users.length).to.equal(0);
  650. });
  651. });
  652. }
  653. });
  654. describe('fetchDatabaseVersion', () => {
  655. it('should database/dialect version', async function () {
  656. const version = await this.sequelize.fetchDatabaseVersion();
  657. expect(typeof version).to.equal('string');
  658. expect(version).to.be.ok;
  659. });
  660. });
  661. describe('getDatabaseVersion', () => {
  662. it('throws if no database version is set internally', () => {
  663. expect(() => {
  664. // ensures the version hasn't been loaded by another test yet
  665. const sequelize = createSingleTestSequelizeInstance();
  666. sequelize.getDatabaseVersion();
  667. }).to.throw(
  668. 'The current database version is unknown. Please call `sequelize.authenticate()` first to fetch it, or manually configure it through options.',
  669. );
  670. });
  671. it('returns the database version if loaded', async function () {
  672. await this.sequelize.authenticate();
  673. const version = this.sequelize.getDatabaseVersion();
  674. expect(typeof version).to.equal('string');
  675. expect(version).to.be.ok;
  676. });
  677. });
  678. describe('paranoid deletedAt non-null default value', () => {
  679. it('should use defaultValue of deletedAt in paranoid clause and restore', async function () {
  680. const epochObj = new Date(0);
  681. const epoch = Number(epochObj);
  682. const User = this.sequelize.define(
  683. 'user',
  684. {
  685. username: DataTypes.STRING,
  686. deletedAt: {
  687. type: DataTypes.DATE,
  688. defaultValue: epochObj,
  689. },
  690. },
  691. {
  692. paranoid: true,
  693. },
  694. );
  695. await this.sequelize.sync({ force: true });
  696. const user = await User.create({ username: 'user1' });
  697. expect(Number(user.deletedAt)).to.equal(epoch);
  698. const user0 = await User.findOne({
  699. where: {
  700. username: 'user1',
  701. },
  702. });
  703. expect(user0).to.exist;
  704. expect(Number(user0.deletedAt)).to.equal(epoch);
  705. const destroyedUser = await user0.destroy();
  706. expect(destroyedUser.deletedAt).to.exist;
  707. expect(Number(destroyedUser.deletedAt)).not.to.equal(epoch);
  708. const fetchedDestroyedUser = await User.findByPk(destroyedUser.id, { paranoid: false });
  709. expect(fetchedDestroyedUser.deletedAt).to.exist;
  710. expect(Number(fetchedDestroyedUser.deletedAt)).not.to.equal(epoch);
  711. const restoredUser = await fetchedDestroyedUser.restore();
  712. expect(Number(restoredUser.deletedAt)).to.equal(epoch);
  713. await User.destroy({
  714. where: {
  715. username: 'user1',
  716. },
  717. });
  718. const count = await User.count();
  719. expect(count).to.equal(0);
  720. await User.restore();
  721. const nonDeletedUsers = await User.findAll();
  722. expect(nonDeletedUsers.length).to.equal(1);
  723. for (const u of nonDeletedUsers) {
  724. expect(Number(u.deletedAt)).to.equal(epoch);
  725. }
  726. });
  727. });
  728. });