123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- 'use strict';
- const chai = require('chai');
- const sinon = require('sinon');
- const expect = chai.expect;
- const Support = require('../support');
- const { DataTypes, Sequelize, sql } = require('@sequelize/core');
- const current = Support.sequelize;
- describe('Model#update', () => {
- beforeEach(async function () {
- this.User = this.sequelize.define('User', {
- username: { type: DataTypes.STRING },
- uuidv1: { type: DataTypes.UUID, defaultValue: sql.uuidV1 },
- uuidv4: { type: DataTypes.UUID, defaultValue: sql.uuidV4 },
- touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
- aNumber: { type: DataTypes.INTEGER },
- bNumber: { type: DataTypes.INTEGER },
- aDate: { type: DataTypes.DATE },
- validateTest: {
- type: DataTypes.INTEGER,
- allowNull: true,
- validate: { isInt: true },
- },
- validateCustom: {
- type: DataTypes.STRING,
- allowNull: true,
- validate: { len: { msg: 'Length failed.', args: [1, 20] } },
- },
- validateSideEffect: {
- type: DataTypes.VIRTUAL,
- allowNull: true,
- validate: { isInt: true },
- set(val) {
- this.setDataValue('validateSideEffect', val);
- this.setDataValue('validateSideAffected', val * 2);
- },
- },
- validateSideAffected: {
- type: DataTypes.INTEGER,
- allowNull: true,
- validate: { isInt: true },
- },
- dateAllowNullTrue: {
- type: DataTypes.DATE,
- allowNull: true,
- },
- });
- await this.User.sync({ force: true });
- });
- context('Fake Timers Suite', () => {
- before(function () {
- this.clock = sinon.useFakeTimers();
- });
- after(function () {
- this.clock.restore();
- });
- it('should update timestamps with milliseconds', async function () {
- const User = this.sequelize.define(
- `User${Support.rand()}`,
- {
- name: DataTypes.STRING,
- bio: DataTypes.TEXT,
- email: DataTypes.STRING,
- },
- {
- timestamps: true,
- },
- );
- this.clock.tick(2100); // move the clock forward 2100 ms.
- await User.sync({ force: true });
- const user0 = await User.create({
- name: 'snafu',
- email: 'email',
- });
- const user = await user0.reload();
- expect(user.get('name')).to.equal('snafu');
- expect(user.get('email')).to.equal('email');
- const testDate = new Date();
- testDate.setTime(2100);
- expect(user.get('createdAt')).to.equalTime(testDate);
- });
- it('does not update timestamps when option "silent=true" is used', async function () {
- const user = await this.User.create({ username: 'user' });
- const updatedAt = user.updatedAt;
- this.clock.tick(1000);
- await user.update(
- {
- username: 'userman',
- },
- {
- silent: true,
- },
- );
- expect(user.updatedAt).to.equalTime(updatedAt);
- });
- it(`doesn't update primary keys or timestamps`, async function () {
- const User = this.sequelize.define(`User${Support.rand()}`, {
- name: DataTypes.STRING,
- bio: DataTypes.TEXT,
- identifier: { type: DataTypes.STRING, primaryKey: true },
- });
- await User.sync({ force: true });
- const user = await User.create({
- name: 'snafu',
- identifier: 'identifier',
- });
- const oldCreatedAt = user.createdAt;
- const oldUpdatedAt = user.updatedAt;
- const oldIdentifier = user.identifier;
- this.clock.tick(1000);
- const user0 = await user.update({
- name: 'foobar',
- createdAt: new Date(2000, 1, 1),
- identifier: 'another identifier',
- });
- expect(new Date(user0.createdAt)).to.equalDate(new Date(oldCreatedAt));
- expect(new Date(user0.updatedAt)).to.not.equalTime(new Date(oldUpdatedAt));
- expect(user0.identifier).to.equal(oldIdentifier);
- });
- });
- if (current.dialect.supports.transactions) {
- it('supports transactions', async function () {
- const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
- this.sequelize,
- );
- const User = sequelize.define('User', { username: DataTypes.STRING });
- await User.sync({ force: true });
- const user = await User.create({ username: 'foo' });
- const t = await sequelize.startUnmanagedTransaction();
- await user.update({ username: 'bar' }, { transaction: t });
- const users1 = await User.findAll();
- const users2 = await User.findAll({ transaction: t });
- expect(users1[0].username).to.equal('foo');
- expect(users2[0].username).to.equal('bar');
- await t.rollback();
- });
- }
- it('should update fields that are not specified on create', async function () {
- const User = this.sequelize.define(`User${Support.rand()}`, {
- name: DataTypes.STRING,
- bio: DataTypes.TEXT,
- email: DataTypes.STRING,
- });
- await User.sync({ force: true });
- const user1 = await User.create(
- {
- name: 'snafu',
- email: 'email',
- },
- {
- fields: ['name', 'email'],
- },
- );
- const user0 = await user1.update({ bio: 'swag' });
- const user = await user0.reload();
- expect(user.get('name')).to.equal('snafu');
- expect(user.get('email')).to.equal('email');
- expect(user.get('bio')).to.equal('swag');
- });
- it('should succeed in updating when values are unchanged (without timestamps)', async function () {
- const User = this.sequelize.define(
- `User${Support.rand()}`,
- {
- name: DataTypes.STRING,
- bio: DataTypes.TEXT,
- email: DataTypes.STRING,
- },
- {
- timestamps: false,
- },
- );
- await User.sync({ force: true });
- const user1 = await User.create(
- {
- name: 'snafu',
- email: 'email',
- },
- {
- fields: ['name', 'email'],
- },
- );
- const user0 = await user1.update({
- name: 'snafu',
- email: 'email',
- });
- const user = await user0.reload();
- expect(user.get('name')).to.equal('snafu');
- expect(user.get('email')).to.equal('email');
- });
- it('should only save passed attributes', async function () {
- const user = this.User.build();
- await user.save();
- user.set('validateTest', 5);
- expect(user.changed('validateTest')).to.be.ok;
- await user.update({
- validateCustom: '1',
- });
- expect(user.changed('validateTest')).to.be.ok;
- expect(user.validateTest).to.equal(5);
- await user.reload();
- expect(user.validateTest).to.not.be.equal(5);
- });
- it('should save attributes affected by setters', async function () {
- const user = await this.User.create();
- await user.update({ validateSideEffect: 5 });
- expect(user.validateSideEffect).to.equal(5);
- await user.reload();
- expect(user.validateSideAffected).to.equal(10);
- expect(user.validateSideEffect).not.to.be.ok;
- });
- it('fails if the update was made to a new record which is not persisted', async function () {
- const Foo = this.sequelize.define(
- 'Foo',
- {
- name: { type: DataTypes.STRING },
- },
- { noPrimaryKey: true },
- );
- await Foo.sync({ force: true });
- const instance = Foo.build({ name: 'FooBar' }, { isNewRecord: true });
- await expect(instance.update()).to.be.rejectedWith(
- 'You attempted to update an instance that is not persisted.',
- );
- });
- describe('hooks', () => {
- it('should update attributes added in hooks when default fields are used', async function () {
- const User = this.sequelize.define(`User${Support.rand()}`, {
- name: DataTypes.STRING,
- bio: DataTypes.TEXT,
- email: DataTypes.STRING,
- });
- User.beforeUpdate(instance => {
- instance.set('email', 'B');
- });
- await User.sync({ force: true });
- const user0 = await User.create({
- name: 'A',
- bio: 'A',
- email: 'A',
- });
- await user0.update({
- name: 'B',
- bio: 'B',
- });
- const user = await User.findOne({});
- expect(user.get('name')).to.equal('B');
- expect(user.get('bio')).to.equal('B');
- expect(user.get('email')).to.equal('B');
- });
- it('should update attributes changed in hooks when default fields are used', async function () {
- const User = this.sequelize.define(`User${Support.rand()}`, {
- name: DataTypes.STRING,
- bio: DataTypes.TEXT,
- email: DataTypes.STRING,
- });
- User.beforeUpdate(instance => {
- instance.set('email', 'C');
- });
- await User.sync({ force: true });
- const user0 = await User.create({
- name: 'A',
- bio: 'A',
- email: 'A',
- });
- await user0.update({
- name: 'B',
- bio: 'B',
- email: 'B',
- });
- const user = await User.findOne({});
- expect(user.get('name')).to.equal('B');
- expect(user.get('bio')).to.equal('B');
- expect(user.get('email')).to.equal('C');
- });
- it('should work on a model with an attribute named length', async function () {
- const Box = this.sequelize.define('box', {
- length: DataTypes.INTEGER,
- width: DataTypes.INTEGER,
- height: DataTypes.INTEGER,
- });
- await Box.sync({ force: true });
- const box0 = await Box.create({
- length: 1,
- width: 2,
- height: 3,
- });
- await box0.update({
- length: 4,
- width: 5,
- height: 6,
- });
- const box = await Box.findOne({});
- expect(box.get('length')).to.equal(4);
- expect(box.get('width')).to.equal(5);
- expect(box.get('height')).to.equal(6);
- });
- it('runs validation', async function () {
- const user = await this.User.create({ aNumber: 0 });
- const error = await expect(user.update({ validateTest: 'hello' })).to.be.rejectedWith(
- Sequelize.ValidationError,
- );
- expect(error).to.exist;
- expect(error).to.be.instanceof(Object);
- expect(error.get('validateTest')).to.exist;
- expect(error.get('validateTest')).to.be.instanceof(Array);
- expect(error.get('validateTest')[1]).to.exist;
- expect(error.get('validateTest')[1].message).to.equal(
- 'Validation isInt on validateTest failed',
- );
- });
- it('should validate attributes added in hooks when default fields are used', async function () {
- const User = this.sequelize.define(`User${Support.rand()}`, {
- name: DataTypes.STRING,
- bio: DataTypes.TEXT,
- email: {
- type: DataTypes.STRING,
- validate: {
- isEmail: true,
- },
- },
- });
- User.beforeUpdate(instance => {
- instance.set('email', 'B');
- });
- await User.sync({ force: true });
- const user0 = await User.create({
- name: 'A',
- bio: 'A',
- email: 'valid.email@gmail.com',
- });
- await expect(
- user0.update({
- name: 'B',
- }),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- const user = await User.findOne({});
- expect(user.get('email')).to.equal('valid.email@gmail.com');
- });
- it('should validate attributes changed in hooks when default fields are used', async function () {
- const User = this.sequelize.define(`User${Support.rand()}`, {
- name: DataTypes.STRING,
- bio: DataTypes.TEXT,
- email: {
- type: DataTypes.STRING,
- validate: {
- isEmail: true,
- },
- },
- });
- User.beforeUpdate(instance => {
- instance.set('email', 'B');
- });
- await User.sync({ force: true });
- const user0 = await User.create({
- name: 'A',
- bio: 'A',
- email: 'valid.email@gmail.com',
- });
- await expect(
- user0.update({
- name: 'B',
- email: 'still.valid.email@gmail.com',
- }),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- const user = await User.findOne({});
- expect(user.get('email')).to.equal('valid.email@gmail.com');
- });
- });
- it('should not set attributes that are not specified by fields', async function () {
- const User = this.sequelize.define(`User${Support.rand()}`, {
- name: DataTypes.STRING,
- bio: DataTypes.TEXT,
- email: DataTypes.STRING,
- });
- await User.sync({ force: true });
- const user0 = await User.create({
- name: 'snafu',
- email: 'email',
- });
- const user = await user0.update(
- {
- bio: 'heyo',
- email: 'heho',
- },
- {
- fields: ['bio'],
- },
- );
- expect(user.get('name')).to.equal('snafu');
- expect(user.get('email')).to.equal('email');
- expect(user.get('bio')).to.equal('heyo');
- });
- it('updates attributes in the database', async function () {
- const user = await this.User.create({ username: 'user' });
- expect(user.username).to.equal('user');
- const user0 = await user.update({ username: 'person' });
- expect(user0.username).to.equal('person');
- });
- it('ignores unknown attributes', async function () {
- const user = await this.User.create({ username: 'user' });
- const user0 = await user.update({ username: 'person', foo: 'bar' });
- expect(user0.username).to.equal('person');
- expect(user0.foo).not.to.exist;
- });
- it('ignores undefined attributes', async function () {
- await this.User.sync({ force: true });
- const user = await this.User.create({ username: 'user' });
- const user0 = await user.update({ username: undefined });
- expect(user0.username).to.equal('user');
- });
- // NOTE: This is a regression test for https://github.com/sequelize/sequelize/issues/12717
- it('updates attributes for model with date pk (#12717)', async function () {
- const Event = this.sequelize.define('Event', {
- date: { type: DataTypes.DATE, allowNull: false, primaryKey: true },
- name: { type: DataTypes.STRING },
- });
- await Event.sync({ force: true });
- const event = await Event.create({
- date: new Date(),
- name: 'event',
- });
- expect(event.name).to.equal('event');
- await event.update({ name: 'event updated' });
- const event0 = await Event.findOne({ where: { date: event.date } });
- expect(event0.name).to.equal('event updated');
- });
- it('stores and restores null values', async function () {
- const Download = this.sequelize.define('download', {
- startedAt: DataTypes.DATE,
- canceledAt: DataTypes.DATE,
- finishedAt: DataTypes.DATE,
- });
- await Download.sync();
- const download = await Download.create({
- startedAt: new Date(),
- });
- expect(download.startedAt instanceof Date).to.be.true;
- expect(download.canceledAt).to.not.be.ok;
- expect(download.finishedAt).to.not.be.ok;
- const download0 = await download.update({
- canceledAt: new Date(),
- });
- expect(download0.startedAt instanceof Date).to.be.true;
- expect(download0.canceledAt instanceof Date).to.be.true;
- expect(download0.finishedAt).to.not.be.ok;
- const downloads = await Download.findAll({
- where: { finishedAt: null },
- });
- for (const download of downloads) {
- expect(download.startedAt instanceof Date).to.be.true;
- expect(download.canceledAt instanceof Date).to.be.true;
- expect(download.finishedAt).to.not.be.ok;
- }
- });
- it('should support logging', async function () {
- const spy = sinon.spy();
- const user = await this.User.create({});
- await user.update({ username: 'yolo' }, { logging: spy });
- expect(spy.called).to.be.ok;
- });
- it('supports falsy primary keys', async () => {
- const Book = current.define('Book', {
- id: {
- type: DataTypes.INTEGER,
- // must have autoIncrement disabled, as mysql treats 0 as "generate next value"
- autoIncrement: false,
- primaryKey: true,
- },
- title: { type: DataTypes.STRING },
- });
- await Book.sync();
- const title1 = 'title 1';
- const title2 = 'title 2';
- const book1 = await Book.create({ id: 0, title: title1 });
- expect(book1.id).to.equal(0);
- expect(book1.title).to.equal(title1);
- const book2 = await Book.findByPk(0, { rejectOnEmpty: true });
- expect(book2.id).to.equal(0);
- expect(book2.title).to.equal(title1);
- await book2.update({ title: title2 });
- expect(book2.id).to.equal(0);
- expect(book2.title).to.equal(title2);
- });
- });
|