update.test.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  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, sql } = require('@sequelize/core');
  7. const current = Support.sequelize;
  8. describe('Model#update', () => {
  9. beforeEach(async function () {
  10. this.User = this.sequelize.define('User', {
  11. username: { type: DataTypes.STRING },
  12. uuidv1: { type: DataTypes.UUID, defaultValue: sql.uuidV1 },
  13. uuidv4: { type: DataTypes.UUID, defaultValue: sql.uuidV4 },
  14. touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
  15. aNumber: { type: DataTypes.INTEGER },
  16. bNumber: { type: DataTypes.INTEGER },
  17. aDate: { type: DataTypes.DATE },
  18. validateTest: {
  19. type: DataTypes.INTEGER,
  20. allowNull: true,
  21. validate: { isInt: true },
  22. },
  23. validateCustom: {
  24. type: DataTypes.STRING,
  25. allowNull: true,
  26. validate: { len: { msg: 'Length failed.', args: [1, 20] } },
  27. },
  28. validateSideEffect: {
  29. type: DataTypes.VIRTUAL,
  30. allowNull: true,
  31. validate: { isInt: true },
  32. set(val) {
  33. this.setDataValue('validateSideEffect', val);
  34. this.setDataValue('validateSideAffected', val * 2);
  35. },
  36. },
  37. validateSideAffected: {
  38. type: DataTypes.INTEGER,
  39. allowNull: true,
  40. validate: { isInt: true },
  41. },
  42. dateAllowNullTrue: {
  43. type: DataTypes.DATE,
  44. allowNull: true,
  45. },
  46. });
  47. await this.User.sync({ force: true });
  48. });
  49. context('Fake Timers Suite', () => {
  50. before(function () {
  51. this.clock = sinon.useFakeTimers();
  52. });
  53. after(function () {
  54. this.clock.restore();
  55. });
  56. it('should update timestamps with milliseconds', async function () {
  57. const User = this.sequelize.define(
  58. `User${Support.rand()}`,
  59. {
  60. name: DataTypes.STRING,
  61. bio: DataTypes.TEXT,
  62. email: DataTypes.STRING,
  63. },
  64. {
  65. timestamps: true,
  66. },
  67. );
  68. this.clock.tick(2100); // move the clock forward 2100 ms.
  69. await User.sync({ force: true });
  70. const user0 = await User.create({
  71. name: 'snafu',
  72. email: 'email',
  73. });
  74. const user = await user0.reload();
  75. expect(user.get('name')).to.equal('snafu');
  76. expect(user.get('email')).to.equal('email');
  77. const testDate = new Date();
  78. testDate.setTime(2100);
  79. expect(user.get('createdAt')).to.equalTime(testDate);
  80. });
  81. it('does not update timestamps when option "silent=true" is used', async function () {
  82. const user = await this.User.create({ username: 'user' });
  83. const updatedAt = user.updatedAt;
  84. this.clock.tick(1000);
  85. await user.update(
  86. {
  87. username: 'userman',
  88. },
  89. {
  90. silent: true,
  91. },
  92. );
  93. expect(user.updatedAt).to.equalTime(updatedAt);
  94. });
  95. it(`doesn't update primary keys or timestamps`, async function () {
  96. const User = this.sequelize.define(`User${Support.rand()}`, {
  97. name: DataTypes.STRING,
  98. bio: DataTypes.TEXT,
  99. identifier: { type: DataTypes.STRING, primaryKey: true },
  100. });
  101. await User.sync({ force: true });
  102. const user = await User.create({
  103. name: 'snafu',
  104. identifier: 'identifier',
  105. });
  106. const oldCreatedAt = user.createdAt;
  107. const oldUpdatedAt = user.updatedAt;
  108. const oldIdentifier = user.identifier;
  109. this.clock.tick(1000);
  110. const user0 = await user.update({
  111. name: 'foobar',
  112. createdAt: new Date(2000, 1, 1),
  113. identifier: 'another identifier',
  114. });
  115. expect(new Date(user0.createdAt)).to.equalDate(new Date(oldCreatedAt));
  116. expect(new Date(user0.updatedAt)).to.not.equalTime(new Date(oldUpdatedAt));
  117. expect(user0.identifier).to.equal(oldIdentifier);
  118. });
  119. });
  120. if (current.dialect.supports.transactions) {
  121. it('supports transactions', async function () {
  122. const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
  123. this.sequelize,
  124. );
  125. const User = sequelize.define('User', { username: DataTypes.STRING });
  126. await User.sync({ force: true });
  127. const user = await User.create({ username: 'foo' });
  128. const t = await sequelize.startUnmanagedTransaction();
  129. await user.update({ username: 'bar' }, { transaction: t });
  130. const users1 = await User.findAll();
  131. const users2 = await User.findAll({ transaction: t });
  132. expect(users1[0].username).to.equal('foo');
  133. expect(users2[0].username).to.equal('bar');
  134. await t.rollback();
  135. });
  136. }
  137. it('should update fields that are not specified on create', async function () {
  138. const User = this.sequelize.define(`User${Support.rand()}`, {
  139. name: DataTypes.STRING,
  140. bio: DataTypes.TEXT,
  141. email: DataTypes.STRING,
  142. });
  143. await User.sync({ force: true });
  144. const user1 = await User.create(
  145. {
  146. name: 'snafu',
  147. email: 'email',
  148. },
  149. {
  150. fields: ['name', 'email'],
  151. },
  152. );
  153. const user0 = await user1.update({ bio: 'swag' });
  154. const user = await user0.reload();
  155. expect(user.get('name')).to.equal('snafu');
  156. expect(user.get('email')).to.equal('email');
  157. expect(user.get('bio')).to.equal('swag');
  158. });
  159. it('should succeed in updating when values are unchanged (without timestamps)', async function () {
  160. const User = this.sequelize.define(
  161. `User${Support.rand()}`,
  162. {
  163. name: DataTypes.STRING,
  164. bio: DataTypes.TEXT,
  165. email: DataTypes.STRING,
  166. },
  167. {
  168. timestamps: false,
  169. },
  170. );
  171. await User.sync({ force: true });
  172. const user1 = await User.create(
  173. {
  174. name: 'snafu',
  175. email: 'email',
  176. },
  177. {
  178. fields: ['name', 'email'],
  179. },
  180. );
  181. const user0 = await user1.update({
  182. name: 'snafu',
  183. email: 'email',
  184. });
  185. const user = await user0.reload();
  186. expect(user.get('name')).to.equal('snafu');
  187. expect(user.get('email')).to.equal('email');
  188. });
  189. it('should only save passed attributes', async function () {
  190. const user = this.User.build();
  191. await user.save();
  192. user.set('validateTest', 5);
  193. expect(user.changed('validateTest')).to.be.ok;
  194. await user.update({
  195. validateCustom: '1',
  196. });
  197. expect(user.changed('validateTest')).to.be.ok;
  198. expect(user.validateTest).to.equal(5);
  199. await user.reload();
  200. expect(user.validateTest).to.not.be.equal(5);
  201. });
  202. it('should save attributes affected by setters', async function () {
  203. const user = await this.User.create();
  204. await user.update({ validateSideEffect: 5 });
  205. expect(user.validateSideEffect).to.equal(5);
  206. await user.reload();
  207. expect(user.validateSideAffected).to.equal(10);
  208. expect(user.validateSideEffect).not.to.be.ok;
  209. });
  210. it('fails if the update was made to a new record which is not persisted', async function () {
  211. const Foo = this.sequelize.define(
  212. 'Foo',
  213. {
  214. name: { type: DataTypes.STRING },
  215. },
  216. { noPrimaryKey: true },
  217. );
  218. await Foo.sync({ force: true });
  219. const instance = Foo.build({ name: 'FooBar' }, { isNewRecord: true });
  220. await expect(instance.update()).to.be.rejectedWith(
  221. 'You attempted to update an instance that is not persisted.',
  222. );
  223. });
  224. describe('hooks', () => {
  225. it('should update attributes added in hooks when default fields are used', async function () {
  226. const User = this.sequelize.define(`User${Support.rand()}`, {
  227. name: DataTypes.STRING,
  228. bio: DataTypes.TEXT,
  229. email: DataTypes.STRING,
  230. });
  231. User.beforeUpdate(instance => {
  232. instance.set('email', 'B');
  233. });
  234. await User.sync({ force: true });
  235. const user0 = await User.create({
  236. name: 'A',
  237. bio: 'A',
  238. email: 'A',
  239. });
  240. await user0.update({
  241. name: 'B',
  242. bio: 'B',
  243. });
  244. const user = await User.findOne({});
  245. expect(user.get('name')).to.equal('B');
  246. expect(user.get('bio')).to.equal('B');
  247. expect(user.get('email')).to.equal('B');
  248. });
  249. it('should update attributes changed in hooks when default fields are used', async function () {
  250. const User = this.sequelize.define(`User${Support.rand()}`, {
  251. name: DataTypes.STRING,
  252. bio: DataTypes.TEXT,
  253. email: DataTypes.STRING,
  254. });
  255. User.beforeUpdate(instance => {
  256. instance.set('email', 'C');
  257. });
  258. await User.sync({ force: true });
  259. const user0 = await User.create({
  260. name: 'A',
  261. bio: 'A',
  262. email: 'A',
  263. });
  264. await user0.update({
  265. name: 'B',
  266. bio: 'B',
  267. email: 'B',
  268. });
  269. const user = await User.findOne({});
  270. expect(user.get('name')).to.equal('B');
  271. expect(user.get('bio')).to.equal('B');
  272. expect(user.get('email')).to.equal('C');
  273. });
  274. it('should work on a model with an attribute named length', async function () {
  275. const Box = this.sequelize.define('box', {
  276. length: DataTypes.INTEGER,
  277. width: DataTypes.INTEGER,
  278. height: DataTypes.INTEGER,
  279. });
  280. await Box.sync({ force: true });
  281. const box0 = await Box.create({
  282. length: 1,
  283. width: 2,
  284. height: 3,
  285. });
  286. await box0.update({
  287. length: 4,
  288. width: 5,
  289. height: 6,
  290. });
  291. const box = await Box.findOne({});
  292. expect(box.get('length')).to.equal(4);
  293. expect(box.get('width')).to.equal(5);
  294. expect(box.get('height')).to.equal(6);
  295. });
  296. it('runs validation', async function () {
  297. const user = await this.User.create({ aNumber: 0 });
  298. const error = await expect(user.update({ validateTest: 'hello' })).to.be.rejectedWith(
  299. Sequelize.ValidationError,
  300. );
  301. expect(error).to.exist;
  302. expect(error).to.be.instanceof(Object);
  303. expect(error.get('validateTest')).to.exist;
  304. expect(error.get('validateTest')).to.be.instanceof(Array);
  305. expect(error.get('validateTest')[1]).to.exist;
  306. expect(error.get('validateTest')[1].message).to.equal(
  307. 'Validation isInt on validateTest failed',
  308. );
  309. });
  310. it('should validate attributes added in hooks when default fields are used', async function () {
  311. const User = this.sequelize.define(`User${Support.rand()}`, {
  312. name: DataTypes.STRING,
  313. bio: DataTypes.TEXT,
  314. email: {
  315. type: DataTypes.STRING,
  316. validate: {
  317. isEmail: true,
  318. },
  319. },
  320. });
  321. User.beforeUpdate(instance => {
  322. instance.set('email', 'B');
  323. });
  324. await User.sync({ force: true });
  325. const user0 = await User.create({
  326. name: 'A',
  327. bio: 'A',
  328. email: 'valid.email@gmail.com',
  329. });
  330. await expect(
  331. user0.update({
  332. name: 'B',
  333. }),
  334. ).to.be.rejectedWith(Sequelize.ValidationError);
  335. const user = await User.findOne({});
  336. expect(user.get('email')).to.equal('valid.email@gmail.com');
  337. });
  338. it('should validate attributes changed in hooks when default fields are used', async function () {
  339. const User = this.sequelize.define(`User${Support.rand()}`, {
  340. name: DataTypes.STRING,
  341. bio: DataTypes.TEXT,
  342. email: {
  343. type: DataTypes.STRING,
  344. validate: {
  345. isEmail: true,
  346. },
  347. },
  348. });
  349. User.beforeUpdate(instance => {
  350. instance.set('email', 'B');
  351. });
  352. await User.sync({ force: true });
  353. const user0 = await User.create({
  354. name: 'A',
  355. bio: 'A',
  356. email: 'valid.email@gmail.com',
  357. });
  358. await expect(
  359. user0.update({
  360. name: 'B',
  361. email: 'still.valid.email@gmail.com',
  362. }),
  363. ).to.be.rejectedWith(Sequelize.ValidationError);
  364. const user = await User.findOne({});
  365. expect(user.get('email')).to.equal('valid.email@gmail.com');
  366. });
  367. });
  368. it('should not set attributes that are not specified by fields', async function () {
  369. const User = this.sequelize.define(`User${Support.rand()}`, {
  370. name: DataTypes.STRING,
  371. bio: DataTypes.TEXT,
  372. email: DataTypes.STRING,
  373. });
  374. await User.sync({ force: true });
  375. const user0 = await User.create({
  376. name: 'snafu',
  377. email: 'email',
  378. });
  379. const user = await user0.update(
  380. {
  381. bio: 'heyo',
  382. email: 'heho',
  383. },
  384. {
  385. fields: ['bio'],
  386. },
  387. );
  388. expect(user.get('name')).to.equal('snafu');
  389. expect(user.get('email')).to.equal('email');
  390. expect(user.get('bio')).to.equal('heyo');
  391. });
  392. it('updates attributes in the database', async function () {
  393. const user = await this.User.create({ username: 'user' });
  394. expect(user.username).to.equal('user');
  395. const user0 = await user.update({ username: 'person' });
  396. expect(user0.username).to.equal('person');
  397. });
  398. it('ignores unknown attributes', async function () {
  399. const user = await this.User.create({ username: 'user' });
  400. const user0 = await user.update({ username: 'person', foo: 'bar' });
  401. expect(user0.username).to.equal('person');
  402. expect(user0.foo).not.to.exist;
  403. });
  404. it('ignores undefined attributes', async function () {
  405. await this.User.sync({ force: true });
  406. const user = await this.User.create({ username: 'user' });
  407. const user0 = await user.update({ username: undefined });
  408. expect(user0.username).to.equal('user');
  409. });
  410. // NOTE: This is a regression test for https://github.com/sequelize/sequelize/issues/12717
  411. it('updates attributes for model with date pk (#12717)', async function () {
  412. const Event = this.sequelize.define('Event', {
  413. date: { type: DataTypes.DATE, allowNull: false, primaryKey: true },
  414. name: { type: DataTypes.STRING },
  415. });
  416. await Event.sync({ force: true });
  417. const event = await Event.create({
  418. date: new Date(),
  419. name: 'event',
  420. });
  421. expect(event.name).to.equal('event');
  422. await event.update({ name: 'event updated' });
  423. const event0 = await Event.findOne({ where: { date: event.date } });
  424. expect(event0.name).to.equal('event updated');
  425. });
  426. it('stores and restores null values', async function () {
  427. const Download = this.sequelize.define('download', {
  428. startedAt: DataTypes.DATE,
  429. canceledAt: DataTypes.DATE,
  430. finishedAt: DataTypes.DATE,
  431. });
  432. await Download.sync();
  433. const download = await Download.create({
  434. startedAt: new Date(),
  435. });
  436. expect(download.startedAt instanceof Date).to.be.true;
  437. expect(download.canceledAt).to.not.be.ok;
  438. expect(download.finishedAt).to.not.be.ok;
  439. const download0 = await download.update({
  440. canceledAt: new Date(),
  441. });
  442. expect(download0.startedAt instanceof Date).to.be.true;
  443. expect(download0.canceledAt instanceof Date).to.be.true;
  444. expect(download0.finishedAt).to.not.be.ok;
  445. const downloads = await Download.findAll({
  446. where: { finishedAt: null },
  447. });
  448. for (const download of downloads) {
  449. expect(download.startedAt instanceof Date).to.be.true;
  450. expect(download.canceledAt instanceof Date).to.be.true;
  451. expect(download.finishedAt).to.not.be.ok;
  452. }
  453. });
  454. it('should support logging', async function () {
  455. const spy = sinon.spy();
  456. const user = await this.User.create({});
  457. await user.update({ username: 'yolo' }, { logging: spy });
  458. expect(spy.called).to.be.ok;
  459. });
  460. it('supports falsy primary keys', async () => {
  461. const Book = current.define('Book', {
  462. id: {
  463. type: DataTypes.INTEGER,
  464. // must have autoIncrement disabled, as mysql treats 0 as "generate next value"
  465. autoIncrement: false,
  466. primaryKey: true,
  467. },
  468. title: { type: DataTypes.STRING },
  469. });
  470. await Book.sync();
  471. const title1 = 'title 1';
  472. const title2 = 'title 2';
  473. const book1 = await Book.create({ id: 0, title: title1 });
  474. expect(book1.id).to.equal(0);
  475. expect(book1.title).to.equal(title1);
  476. const book2 = await Book.findByPk(0, { rejectOnEmpty: true });
  477. expect(book2.id).to.equal(0);
  478. expect(book2.title).to.equal(title1);
  479. await book2.update({ title: title2 });
  480. expect(book2.id).to.equal(0);
  481. expect(book2.title).to.equal(title2);
  482. });
  483. });