field.test.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. 'use strict';
  2. const chai = require('chai');
  3. const sinon = require('sinon');
  4. const expect = chai.expect;
  5. const Support = require('../../support');
  6. const { DataTypes, Sequelize } = require('@sequelize/core');
  7. const dialect = Support.getTestDialect();
  8. describe(Support.getTestDialectTeaser('Model'), () => {
  9. before(function () {
  10. this.clock = sinon.useFakeTimers();
  11. });
  12. after(function () {
  13. this.clock.restore();
  14. });
  15. describe('attributes', () => {
  16. describe('field', () => {
  17. beforeEach(async function () {
  18. const queryInterface = this.sequelize.queryInterface;
  19. this.User = this.sequelize.define(
  20. 'user',
  21. {
  22. id: {
  23. type: DataTypes.INTEGER,
  24. allowNull: false,
  25. primaryKey: true,
  26. autoIncrement: true,
  27. field: 'userId',
  28. },
  29. name: {
  30. type: DataTypes.STRING,
  31. field: 'full_name',
  32. },
  33. taskCount: {
  34. type: DataTypes.INTEGER,
  35. field: 'task_count',
  36. defaultValue: 0,
  37. allowNull: false,
  38. },
  39. },
  40. {
  41. tableName: 'users',
  42. timestamps: false,
  43. },
  44. );
  45. this.Task = this.sequelize.define(
  46. 'task',
  47. {
  48. id: {
  49. type: DataTypes.INTEGER,
  50. allowNull: false,
  51. primaryKey: true,
  52. autoIncrement: true,
  53. field: 'taskId',
  54. },
  55. title: {
  56. type: DataTypes.STRING,
  57. field: 'name',
  58. },
  59. },
  60. {
  61. tableName: 'tasks',
  62. timestamps: false,
  63. },
  64. );
  65. this.Comment = this.sequelize.define(
  66. 'comment',
  67. {
  68. id: {
  69. type: DataTypes.INTEGER,
  70. allowNull: false,
  71. primaryKey: true,
  72. autoIncrement: true,
  73. field: 'commentId',
  74. },
  75. text: { type: DataTypes.STRING, field: 'comment_text' },
  76. notes: { type: DataTypes.STRING, field: 'notes' },
  77. likes: { type: DataTypes.INTEGER, field: 'like_count' },
  78. createdAt: { field: 'created_at' },
  79. updatedAt: { field: 'updated_at' },
  80. },
  81. {
  82. tableName: 'comments',
  83. timestamps: true,
  84. },
  85. );
  86. this.User.hasMany(this.Task, {
  87. foreignKey: 'user_id',
  88. });
  89. this.Task.belongsTo(this.User, {
  90. foreignKey: 'user_id',
  91. });
  92. this.Task.hasMany(this.Comment, {
  93. foreignKey: 'task_id',
  94. });
  95. this.Comment.belongsTo(this.Task, {
  96. foreignKey: 'task_id',
  97. });
  98. this.User.belongsToMany(this.Comment, {
  99. foreignKey: 'userId',
  100. otherKey: 'commentId',
  101. through: 'userComments',
  102. });
  103. await Promise.all([
  104. queryInterface.createTable('users', {
  105. userId: {
  106. type: DataTypes.INTEGER,
  107. allowNull: false,
  108. primaryKey: true,
  109. autoIncrement: true,
  110. },
  111. full_name: {
  112. type: DataTypes.STRING,
  113. },
  114. task_count: {
  115. type: DataTypes.INTEGER,
  116. allowNull: false,
  117. defaultValue: 0,
  118. },
  119. }),
  120. queryInterface.createTable('tasks', {
  121. taskId: {
  122. type: DataTypes.INTEGER,
  123. allowNull: false,
  124. primaryKey: true,
  125. autoIncrement: true,
  126. },
  127. user_id: {
  128. type: DataTypes.INTEGER,
  129. },
  130. name: {
  131. type: DataTypes.STRING,
  132. },
  133. }),
  134. queryInterface.createTable('comments', {
  135. commentId: {
  136. type: DataTypes.INTEGER,
  137. allowNull: false,
  138. primaryKey: true,
  139. autoIncrement: true,
  140. },
  141. task_id: {
  142. type: DataTypes.INTEGER,
  143. },
  144. comment_text: {
  145. type: DataTypes.STRING,
  146. },
  147. notes: {
  148. type: DataTypes.STRING,
  149. },
  150. like_count: {
  151. type: DataTypes.INTEGER,
  152. },
  153. created_at: {
  154. type: DataTypes.DATE,
  155. allowNull: false,
  156. },
  157. updated_at: {
  158. type: DataTypes.DATE,
  159. },
  160. }),
  161. queryInterface.createTable('userComments', {
  162. commentId: {
  163. type: DataTypes.INTEGER,
  164. },
  165. userId: {
  166. type: DataTypes.INTEGER,
  167. },
  168. }),
  169. ]);
  170. });
  171. describe('primaryKey', () => {
  172. describe('in combination with allowNull', () => {
  173. beforeEach(async function () {
  174. this.ModelUnderTest = this.sequelize.define('ModelUnderTest', {
  175. identifier: {
  176. primaryKey: true,
  177. type: DataTypes.STRING,
  178. allowNull: false,
  179. },
  180. });
  181. await this.ModelUnderTest.sync({ force: true });
  182. });
  183. it('sets the column to not allow null', async function () {
  184. const fields = await this.ModelUnderTest.describe();
  185. expect(fields.identifier).to.include({ allowNull: false });
  186. });
  187. });
  188. it('should support instance.destroy()', async function () {
  189. const user = await this.User.create();
  190. await user.destroy();
  191. });
  192. it('should support Model.destroy()', async function () {
  193. const user = await this.User.create();
  194. await this.User.destroy({
  195. where: {
  196. id: user.get('id'),
  197. },
  198. });
  199. });
  200. });
  201. describe('field and attribute name is the same', () => {
  202. beforeEach(async function () {
  203. await this.Comment.bulkCreate([{ notes: 'Number one' }, { notes: 'Number two' }]);
  204. });
  205. it('bulkCreate should work', async function () {
  206. const comments = await this.Comment.findAll();
  207. expect(comments[0].notes).to.equal('Number one');
  208. expect(comments[1].notes).to.equal('Number two');
  209. });
  210. it('find with where should work', async function () {
  211. const comments = await this.Comment.findAll({ where: { notes: 'Number one' } });
  212. expect(comments).to.have.length(1);
  213. expect(comments[0].notes).to.equal('Number one');
  214. });
  215. it('reload should work', async function () {
  216. const comment = await this.Comment.findByPk(1);
  217. await comment.reload();
  218. });
  219. it('save should work', async function () {
  220. const comment1 = await this.Comment.create({ notes: 'my note' });
  221. comment1.notes = 'new note';
  222. const comment0 = await comment1.save();
  223. const comment = await comment0.reload();
  224. expect(comment.notes).to.equal('new note');
  225. });
  226. });
  227. it('increment should work', async function () {
  228. await this.Comment.truncate();
  229. const comment1 = await this.Comment.create({ note: 'oh boy, here I go again', likes: 23 });
  230. const comment0 = await comment1.increment('likes');
  231. const comment = await comment0.reload();
  232. expect(comment.likes).to.equal(24);
  233. });
  234. it('decrement should work', async function () {
  235. await this.Comment.truncate();
  236. const comment1 = await this.Comment.create({ note: 'oh boy, here I go again', likes: 23 });
  237. const comment0 = await comment1.decrement('likes');
  238. const comment = await comment0.reload();
  239. expect(comment.likes).to.equal(22);
  240. });
  241. it('sum should work', async function () {
  242. await this.Comment.truncate();
  243. await this.Comment.create({ note: 'oh boy, here I go again', likes: 23 });
  244. const likes = await this.Comment.sum('likes');
  245. expect(likes).to.equal(23);
  246. });
  247. it('should create, fetch and update with alternative field names from a simple model', async function () {
  248. await this.User.create({
  249. name: 'Foobar',
  250. });
  251. const user0 = await this.User.findOne({
  252. limit: 1,
  253. });
  254. expect(user0.get('name')).to.equal('Foobar');
  255. await user0.update({
  256. name: 'Barfoo',
  257. });
  258. const user = await this.User.findOne({
  259. limit: 1,
  260. });
  261. expect(user.get('name')).to.equal('Barfoo');
  262. });
  263. it('should bulk update', async function () {
  264. const Entity = this.sequelize.define('Entity', {
  265. strField: { type: DataTypes.STRING, field: 'str_field' },
  266. });
  267. await this.sequelize.sync({ force: true });
  268. await Entity.create({ strField: 'foo' });
  269. await Entity.update({ strField: 'bar' }, { where: { strField: 'foo' } });
  270. const entity = await Entity.findOne({
  271. where: {
  272. strField: 'bar',
  273. },
  274. });
  275. expect(entity).to.be.ok;
  276. expect(entity.get('strField')).to.equal('bar');
  277. });
  278. it('should not contain the field properties after create', async function () {
  279. const Model = this.sequelize.define(
  280. 'test',
  281. {
  282. id: {
  283. type: DataTypes.INTEGER,
  284. field: 'test_id',
  285. autoIncrement: true,
  286. primaryKey: true,
  287. validate: {
  288. min: 1,
  289. },
  290. },
  291. title: {
  292. allowNull: false,
  293. type: DataTypes.STRING(255),
  294. field: 'test_title',
  295. },
  296. },
  297. {
  298. timestamps: true,
  299. underscored: true,
  300. freezeTableName: true,
  301. },
  302. );
  303. await Model.sync({ force: true });
  304. const data = await Model.create({ title: 'test' });
  305. expect(data.get('test_title')).to.be.an('undefined');
  306. expect(data.get('test_id')).to.be.an('undefined');
  307. });
  308. it('should make the aliased auto incremented primary key available after create', async function () {
  309. const user = await this.User.create({
  310. name: 'Barfoo',
  311. });
  312. expect(user.get('id')).to.be.ok;
  313. });
  314. it('should work with where on includes for find', async function () {
  315. const user = await this.User.create({
  316. name: 'Barfoo',
  317. });
  318. const task0 = await user.createTask({
  319. title: 'DatDo',
  320. });
  321. await task0.createComment({
  322. text: 'Comment',
  323. });
  324. const task = await this.Task.findOne({
  325. include: [{ model: this.Comment }, { model: this.User }],
  326. where: { title: 'DatDo' },
  327. });
  328. expect(task.get('title')).to.equal('DatDo');
  329. expect(task.get('comments')[0].get('text')).to.equal('Comment');
  330. expect(task.get('user')).to.be.ok;
  331. });
  332. it('should work with where on includes for findAll', async function () {
  333. const user = await this.User.create({
  334. name: 'Foobar',
  335. });
  336. const task = await user.createTask({
  337. title: 'DoDat',
  338. });
  339. await task.createComment({
  340. text: 'Comment',
  341. });
  342. const users = await this.User.findAll({
  343. include: [
  344. {
  345. model: this.Task,
  346. where: { title: 'DoDat' },
  347. include: [{ model: this.Comment }],
  348. },
  349. ],
  350. });
  351. for (const user of users) {
  352. expect(user.get('name')).to.be.ok;
  353. expect(user.get('tasks')[0].get('title')).to.equal('DoDat');
  354. expect(user.get('tasks')[0].get('comments')).to.be.ok;
  355. }
  356. });
  357. it('should work with increment', async function () {
  358. const user = await this.User.create();
  359. await user.increment('taskCount');
  360. });
  361. it('should work with a simple where', async function () {
  362. await this.User.create({
  363. name: 'Foobar',
  364. });
  365. const user = await this.User.findOne({
  366. where: {
  367. name: 'Foobar',
  368. },
  369. });
  370. expect(user).to.be.ok;
  371. });
  372. it('should work with a where or', async function () {
  373. await this.User.create({
  374. name: 'Foobar',
  375. });
  376. const user = await this.User.findOne({
  377. where: this.sequelize.or(
  378. {
  379. name: 'Foobar',
  380. },
  381. {
  382. name: 'Lollerskates',
  383. },
  384. ),
  385. });
  386. expect(user).to.be.ok;
  387. });
  388. it('should work with bulkCreate and findAll', async function () {
  389. await this.User.bulkCreate([
  390. {
  391. name: 'Abc',
  392. },
  393. {
  394. name: 'Bcd',
  395. },
  396. {
  397. name: 'Cde',
  398. },
  399. ]);
  400. const users = await this.User.findAll();
  401. for (const user of users) {
  402. expect(['Abc', 'Bcd', 'Cde'].includes(user.get('name'))).to.be.true;
  403. }
  404. });
  405. it('should support renaming of sequelize method fields', async function () {
  406. const Test = this.sequelize.define('test', {
  407. someProperty: DataTypes.VIRTUAL, // Since we specify the AS part as a part of the literal string, not with sequelize syntax, we have to tell sequelize about the field
  408. });
  409. await this.sequelize.sync({ force: true });
  410. await Test.create({});
  411. let findAttributes;
  412. if (dialect === 'mssql') {
  413. findAttributes = [
  414. Sequelize.literal(
  415. 'CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "someProperty"',
  416. ),
  417. [
  418. Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT)'),
  419. 'someProperty2',
  420. ],
  421. ];
  422. } else if (['db2', 'ibmi'].includes(dialect)) {
  423. findAttributes = [
  424. Sequelize.literal('1 AS "someProperty"'),
  425. [Sequelize.literal('1'), 'someProperty2'],
  426. ];
  427. } else {
  428. findAttributes = [
  429. Sequelize.literal('EXISTS(SELECT 1) AS "someProperty"'),
  430. [Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2'],
  431. ];
  432. }
  433. const tests = await Test.findAll({
  434. attributes: findAttributes,
  435. });
  436. expect(tests[0].get('someProperty')).to.be.ok;
  437. expect(tests[0].get('someProperty2')).to.be.ok;
  438. });
  439. it('should sync foreign keys with custom field names', async function () {
  440. await this.sequelize.sync({ force: true });
  441. const attrs = this.Task.tableAttributes;
  442. expect(attrs.user_id.references.table).to.deep.equal({
  443. tableName: 'users',
  444. schema: this.sequelize.dialect.getDefaultSchema(),
  445. delimiter: '.',
  446. });
  447. expect(attrs.user_id.references.key).to.equal('userId');
  448. });
  449. it('should find the value of an attribute with a custom field name', async function () {
  450. await this.User.create({ name: 'test user' });
  451. const user = await this.User.findOne({ where: { name: 'test user' } });
  452. expect(user.name).to.equal('test user');
  453. });
  454. it('field names that are the same as property names should create, update, and read correctly', async function () {
  455. await this.Comment.create({
  456. notes: 'Foobar',
  457. });
  458. const comment0 = await this.Comment.findOne({
  459. limit: 1,
  460. });
  461. expect(comment0.get('notes')).to.equal('Foobar');
  462. await comment0.update({
  463. notes: 'Barfoo',
  464. });
  465. const comment = await this.Comment.findOne({
  466. limit: 1,
  467. });
  468. expect(comment.get('notes')).to.equal('Barfoo');
  469. });
  470. it('should work with a belongsTo association getter', async function () {
  471. const userId = Math.floor(Math.random() * 100_000);
  472. const [user, task] = await Promise.all([
  473. this.User.create({
  474. id: userId,
  475. }),
  476. this.Task.create({
  477. user_id: userId,
  478. }),
  479. ]);
  480. const [userA, userB] = await Promise.all([user, task.getUser()]);
  481. expect(userA.get('id')).to.equal(userB.get('id'));
  482. expect(userA.get('id')).to.equal(userId);
  483. expect(userB.get('id')).to.equal(userId);
  484. });
  485. it('should work with paranoid instance.destroy()', async function () {
  486. const User = this.sequelize.define(
  487. 'User',
  488. {
  489. deletedAt: {
  490. type: DataTypes.DATE,
  491. field: 'deleted_at',
  492. },
  493. },
  494. {
  495. timestamps: true,
  496. paranoid: true,
  497. },
  498. );
  499. await User.sync({ force: true });
  500. const user = await User.create();
  501. await user.destroy();
  502. this.clock.tick(1000);
  503. const users = await User.findAll();
  504. expect(users.length).to.equal(0);
  505. });
  506. it('should work with paranoid Model.destroy()', async function () {
  507. const User = this.sequelize.define(
  508. 'User',
  509. {
  510. deletedAt: {
  511. type: DataTypes.DATE,
  512. field: 'deleted_at',
  513. },
  514. },
  515. {
  516. timestamps: true,
  517. paranoid: true,
  518. },
  519. );
  520. await User.sync({ force: true });
  521. const user = await User.create();
  522. await User.destroy({ where: { id: user.get('id') } });
  523. const users = await User.findAll();
  524. expect(users.length).to.equal(0);
  525. });
  526. it('should work with `belongsToMany` association `count`', async function () {
  527. const user = await this.User.create({
  528. name: 'John',
  529. });
  530. const commentCount = await user.countComments();
  531. await expect(commentCount).to.equal(0);
  532. });
  533. it('should work with `hasMany` association `count`', async function () {
  534. const user = await this.User.create({
  535. name: 'John',
  536. });
  537. const taskCount = await user.countTasks();
  538. await expect(taskCount).to.equal(0);
  539. });
  540. });
  541. });
  542. });