From d1332c51cea58de013f8119458b1c28c394095f0 Mon Sep 17 00:00:00 2001 From: Etienne Turc Date: Sun, 20 Feb 2022 20:15:48 +0100 Subject: [PATCH] feat(scopes): use AND merging scope strategy as default (and unique) --- src/model.d.ts | 10 - src/model.js | 32 +-- test/integration/model/scope/destroy.test.js | 17 +- test/integration/model/scope/find.test.js | 7 + .../model/scope/findAndCountAll.test.js | 7 + test/integration/model/scope/update.test.js | 15 +- test/unit/model/include.test.js | 21 +- test/unit/model/scope.test.js | 240 +++++++++++++----- 8 files changed, 235 insertions(+), 114 deletions(-) diff --git a/src/model.d.ts b/src/model.d.ts index 630b7184a0be..ab2addcef75d 100644 --- a/src/model.d.ts +++ b/src/model.d.ts @@ -1579,16 +1579,6 @@ export interface ModelOptions { * @default false */ version?: boolean | string; - - /** - * Enable where scopes merging using Op.and operator. - * When enabled, scopes containing the same attribute in a where clause will be grouped with the Op.and operator - * For instance merging scopes containing `{ where: { myField: 1 }}` and `{ where: { myField: 2 }}` will result in - * `{ where: { [Op.and]: [{ myField: 1 }, { myField: 2 }] } }`. - * - * @default false - */ - mergeWhereScopesWithAndOperator?: boolean; } /** diff --git a/src/model.js b/src/model.js index 173fd787c0ec..d61a9b97a098 100644 --- a/src/model.js +++ b/src/model.js @@ -855,32 +855,26 @@ class Model { return args[0]; } - static _mergeOverlappingAttributeConditions(objValue, srcValue) { - return { - [Op.and]: [ - ...((objValue && objValue[Op.and]) || []), - srcValue, - ], - }; - } - static _mergeFunction(objValue, srcValue, key) { if (Array.isArray(objValue) && Array.isArray(srcValue)) { return _.union(objValue, srcValue); } if (['where', 'having'].includes(key)) { - if (srcValue instanceof Utils.SequelizeMethod) { - srcValue = { [Op.and]: srcValue }; + let objKeysToKeep; + if (objValue) { + const objKeys = Object.getOwnPropertyNames(objValue).concat(Object.getOwnPropertySymbols(objValue)); + objKeysToKeep = _.filter(objKeys, key => key !== Op.and); } - if (this.options?.mergeWhereScopesWithAndOperator) { - return this._mergeOverlappingAttributeConditions(objValue, srcValue); - } + return { + [Op.and]: _.compact([ + ...(objValue?.[Op.and] ?? []), + !_.isEmpty(objKeysToKeep) && _.pick(objValue, objKeysToKeep), + srcValue, + ]), + }; - if (_.isPlainObject(objValue) && _.isPlainObject(srcValue)) { - return Object.assign(objValue, srcValue); - } } else if (key === 'attributes' && _.isPlainObject(objValue) && _.isPlainObject(srcValue)) { return _.assignWith(objValue, srcValue, (objValue, srcValue) => { if (Array.isArray(objValue) && Array.isArray(srcValue)) { @@ -900,7 +894,7 @@ class Model { } static _assignOptions(...args) { - return this._baseMerge(...args, this._mergeFunction.bind(this)); + return this._baseMerge(...args, this._mergeFunction); } static _defaultsOptions(target, opts) { @@ -997,7 +991,6 @@ class Model { * @param {string} [options.initialAutoIncrement] Set the initial AUTO_INCREMENT value for the table in MySQL. * @param {object} [options.hooks] An object of hook function that are called before and after certain lifecycle events. The possible hooks are: beforeValidate, afterValidate, validationFailed, beforeBulkCreate, beforeBulkDestroy, beforeBulkUpdate, beforeCreate, beforeDestroy, beforeUpdate, afterCreate, beforeSave, afterDestroy, afterUpdate, afterBulkCreate, afterSave, afterBulkDestroy and afterBulkUpdate. See Hooks for more information about hook functions and their signatures. Each property can either be a function, or an array of functions. * @param {object} [options.validate] An object of model wide validations. Validations have access to all model values via `this`. If the validator function takes an argument, it is assumed to be async, and is called with a callback that accepts an optional error. - * @param {boolean} [options.mergeWhereScopesWithAndOperator] Merge `where` properties of scopes together by adding `Op.and` at the top-most level. * * @returns {Model} */ @@ -1047,7 +1040,6 @@ class Model { defaultScope: {}, scopes: {}, indexes: [], - mergeWhereScopesWithAndOperator: false, ...options, }; diff --git a/test/integration/model/scope/destroy.test.js b/test/integration/model/scope/destroy.test.js index 524d72d5e3f0..3592744c3b68 100644 --- a/test/integration/model/scope/destroy.test.js +++ b/test/integration/model/scope/destroy.test.js @@ -54,14 +54,6 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(users[1].get('username')).to.equal('fred'); }); - it('should be able to override default scope', async function () { - await this.ScopeMe.destroy({ where: { access_level: { [Op.lt]: 5 } } }); - const users = await this.ScopeMe.unscoped().findAll(); - expect(users).to.have.length(2); - expect(users[0].get('username')).to.equal('tobi'); - expect(users[1].get('username')).to.equal('dan'); - }); - it('should be able to unscope destroy', async function () { await this.ScopeMe.unscoped().destroy({ where: {} }); await expect(this.ScopeMe.unscoped().findAll()).to.eventually.have.length(0); @@ -83,6 +75,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(users[2].get('username')).to.equal('fred'); }); + it('should be able to merge scopes with similar where', async function () { + await this.ScopeMe.scope('defaultScope', 'lowAccess').destroy(); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(3); + expect(users[0].get('username')).to.equal('tony'); + expect(users[1].get('username')).to.equal('tobi'); + expect(users[2].get('username')).to.equal('fred'); + }); + it('should work with empty where', async function () { await this.ScopeMe.scope('lowAccess').destroy(); const users = await this.ScopeMe.unscoped().findAll(); diff --git a/test/integration/model/scope/find.test.js b/test/integration/model/scope/find.test.js index daaa938421f6..feacd07efdc7 100644 --- a/test/integration/model/scope/find.test.js +++ b/test/integration/model/scope/find.test.js @@ -83,6 +83,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should be able to combine scope and findAll where clauses', async function () { const users = await this.ScopeMe.scope({ where: { parent_id: 1 } }).findAll({ where: { access_level: 3 } }); expect(users).to.have.length(2); + expect(['tobi', 'dan'].includes(users[0].username)).to.be.true; + expect(['tobi', 'dan'].includes(users[1].username)).to.be.true; + }); + + it('should be able to combine multiple scopes', async function () { + const users = await this.ScopeMe.scope('defaultScope', 'highValue').findAll(); + expect(users).to.have.length(2); expect(['tony', 'fred'].includes(users[0].username)).to.be.true; expect(['tony', 'fred'].includes(users[1].username)).to.be.true; }); diff --git a/test/integration/model/scope/findAndCountAll.test.js b/test/integration/model/scope/findAndCountAll.test.js index 24aeaa386651..5beaff3ea68b 100644 --- a/test/integration/model/scope/findAndCountAll.test.js +++ b/test/integration/model/scope/findAndCountAll.test.js @@ -82,6 +82,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(result.count).to.equal(1); }); + it('should be able to merge multiple scopes', async function () { + const result = await this.ScopeMe.scope('defaultScope', 'lowAccess') + .findAndCountAll(); + + expect(result.count).to.equal(1); + }); + it('should ignore the order option if it is found within the scope', async function () { const result = await this.ScopeMe.scope('withOrder').findAndCountAll(); expect(result.count).to.equal(4); diff --git a/test/integration/model/scope/update.test.js b/test/integration/model/scope/update.test.js index 89883dd38aa9..48bf20826560 100644 --- a/test/integration/model/scope/update.test.js +++ b/test/integration/model/scope/update.test.js @@ -54,14 +54,6 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(users[1].get('email')).to.equal('dan@sequelizejs.com'); }); - it('should be able to override default scope', async function () { - await this.ScopeMe.update({ username: 'ruben' }, { where: { access_level: { [Op.lt]: 5 } } }); - const users = await this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); - expect(users).to.have.length(2); - expect(users[0].get('email')).to.equal('tony@sequelizejs.com'); - expect(users[1].get('email')).to.equal('fred@foobar.com'); - }); - it('should be able to unscope destroy', async function () { await this.ScopeMe.unscoped().update({ username: 'ruben' }, { where: {} }); const rubens = await this.ScopeMe.unscoped().findAll(); @@ -82,6 +74,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(users[0].get('email')).to.equal('dan@sequelizejs.com'); }); + it('should be able to merge scopes with similar where', async function () { + await this.ScopeMe.scope('defaultScope', 'lowAccess').update({ username: 'fakeName' }); + const users = await this.ScopeMe.unscoped().findAll({ where: { username: 'fakeName' } }); + expect(users).to.have.length(1); + expect(users[0].get('email')).to.equal('dan@sequelizejs.com'); + }); + it('should work with empty where', async function () { await this.ScopeMe.scope('lowAccess').update({ username: 'ruby', diff --git a/test/unit/model/include.test.js b/test/unit/model/include.test.js index 03b14baec868..5746c63ced8e 100644 --- a/test/unit/model/include.test.js +++ b/test/unit/model/include.test.js @@ -1,14 +1,23 @@ 'use strict'; const chai = require('chai'); +const util = require('util'); const expect = chai.expect; const Support = require('../support'); const Sequelize = require('sequelize'); +const Op = Sequelize.Op; + const current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { + // Using util.inspect to correctly assert objects with symbols + // Because expect.deep.equal does not test non iterator keys such as symbols (https://github.com/chaijs/chai/issues/1054) + chai.Assertion.addMethod('deepEquals', function (expected, depth = 5) { + expect(util.inspect(this._obj, { depth })).to.deep.equal(util.inspect(expected, { depth })); + }); + describe('all', () => { const Referral = current.define('referal'); @@ -179,7 +188,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { include: [{ model: this.Project }], }); - expect(options.include[0]).to.have.property('where').which.deep.equals({ active: true }); + expect(options.include[0]).to.have.property('where').which.deepEquals({ [Op.and]: [{ active: true }] }); }); it('adds the where from a scoped model', function () { @@ -188,7 +197,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { include: [{ model: this.Project.scope('that') }], }); - expect(options.include[0]).to.have.property('where').which.deep.equals({ that: false }); + expect(options.include[0]).to.have.property('where').which.deepEquals({ [Op.and]: [{ that: false }] }); expect(options.include[0]).to.have.property('limit').which.equals(12); }); @@ -198,7 +207,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { include: [{ model: this.Project.scope('attr') }], }); - expect(options.include[0]).to.have.property('attributes').which.deep.equals(['baz']); + expect(options.include[0]).to.have.property('attributes').which.deepEquals(['baz']); }); it('merges where with the where from a scoped model', function () { @@ -207,7 +216,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { include: [{ where: { active: false }, model: this.Project.scope('that') }], }); - expect(options.include[0]).to.have.property('where').which.deep.equals({ active: false, that: false }); + expect(options.include[0]).to.have.property('where').which.deepEquals({ [Op.and]: [{ that: false }, { active: false }] }); }); it('add the where from a scoped associated model', function () { @@ -216,7 +225,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { include: [{ model: this.Project, as: 'thisProject' }], }); - expect(options.include[0]).to.have.property('where').which.deep.equals({ this: true }); + expect(options.include[0]).to.have.property('where').which.deepEquals({ [Op.and]: [{ this: true }] }); }); it('handles a scope with an aliased column (.field)', function () { @@ -225,7 +234,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { include: [{ model: this.Project.scope('foobar') }], }); - expect(options.include[0]).to.have.property('where').which.deep.equals({ foo: 42 }); + expect(options.include[0]).to.have.property('where').which.deepEquals({ [Op.and]: [{ foo: 42 }] }); }); }); diff --git a/test/unit/model/scope.test.js b/test/unit/model/scope.test.js index b8d54349c8c6..51c84127b459 100644 --- a/test/unit/model/scope.test.js +++ b/test/unit/model/scope.test.js @@ -13,6 +13,12 @@ const DataTypes = require('sequelize/lib/data-types'); const current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { + // Using util.inspect to correctly assert objects with symbols + // Because expect.deep.equal does not test non iterator keys such as symbols (https://github.com/chaijs/chai/issues/1054) + chai.Assertion.addMethod('deepEqual', function (expected, depth = 5) { + expect(util.inspect(this._obj, { depth })).to.deep.equal(util.inspect(expected, { depth })); + }); + const Project = current.define('project'); const User = current.define('user'); @@ -34,7 +40,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { something: false, }, }, - sequelize_where: { + sequelizeWhere: { where: Sequelize.where(), }, users: { @@ -143,11 +149,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should be able to merge scopes', () => { - expect(Company.scope('somethingTrue', 'somethingFalse', 'sequelize_where')._scope).to.deep.equal({ + expect(Company.scope('somethingTrue', 'somethingFalse', 'sequelizeWhere')._scope).to.deepEqual({ where: { - something: false, - somethingElse: 42, - [Op.and]: Sequelize.where(), + [Op.and]: [ + { something: true, somethingElse: 42 }, + { something: false }, + Sequelize.where(), + ], }, limit: 5, }); @@ -157,35 +165,56 @@ describe(Support.getTestDialectTeaser('Model'), () => { const scoped1 = Company.scope('somethingTrue'); const scoped2 = Company.scope('somethingFalse'); - expect(scoped1._scope).to.deep.equal(scopes.somethingTrue); - expect(scoped2._scope).to.deep.equal(scopes.somethingFalse); + expect(scoped1._scope).to.deepEqual({ + where: { + [Op.and]: [ + { something: true, somethingElse: 42 }, + ], + }, + limit: 5, + }); + expect(scoped2._scope).to.deepEqual({ + where: { + [Op.and]: [ + { something: false }, + ], + }, + }); }); it('should work with function scopes', () => { - expect(Company.scope({ method: ['actualValue', 11] })._scope).to.deep.equal({ + expect(Company.scope({ method: ['actualValue', 11] })._scope).to.deepEqual({ where: { - other_value: 11, + [Op.and]: [ + { other_value: 11 }, + ], }, }); - expect(Company.scope('noArgs')._scope).to.deep.equal({ + expect(Company.scope('noArgs')._scope).to.deepEqual({ where: { - other_value: 7, + [Op.and]: [ + { other_value: 7 }, + ], }, }); }); it('should work with consecutive function scopes', () => { const scope = { method: ['actualValue', 11] }; - expect(Company.scope(scope)._scope).to.deep.equal({ + expect(Company.scope(scope)._scope).to.deepEqual({ where: { - other_value: 11, + [Op.and]: [ + { other_value: 11 }, + ], }, }); - expect(Company.scope(scope)._scope).to.deep.equal({ + expect(Company.scope(scope)._scope).to.deepEqual({ where: { - other_value: 11, + [Op.and]: [ + { other_value: 11 }, + ], }, }); }); @@ -211,21 +240,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should be keep original scope definition clean', () => { - expect(Company.scope('projects', 'users', 'alsoUsers')._scope).to.deep.equal({ + it('should be able to keep original scope definition clean', () => { + expect(Company.scope('projects', 'users', 'alsoUsers')._scope).to.deepEqual({ include: [ { model: Project }, - { model: User, where: { something: 42 } }, + { model: User, where: { [Op.and]: [{ something: 42 }] } }, ], }); - expect(Company.options.scopes.alsoUsers).to.deep.equal({ + expect(Company.options.scopes.alsoUsers).to.deepEqual({ include: [ { model: User, where: { something: 42 } }, ], }); - expect(Company.options.scopes.users).to.deep.equal({ + expect(Company.options.scopes.users).to.deepEqual({ include: [ { model: User }, ], @@ -233,21 +262,30 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should be able to override the default scope', () => { - expect(Company.scope('somethingTrue')._scope).to.deep.equal(scopes.somethingTrue); + expect(Company.scope('somethingTrue')._scope).to.deepEqual({ + where: { + [Op.and]: [ + { something: true, somethingElse: 42 }, + ], + }, + limit: 5, + }); }); it('should be able to combine default with another scope', () => { - expect(Company.scope(['defaultScope', { method: ['actualValue', 11] }])._scope).to.deep.equal({ + expect(Company.scope(['defaultScope', { method: ['actualValue', 11] }])._scope).to.deepEqual({ include: [{ model: Project }], where: { - active: true, - other_value: 11, + [Op.and]: [ + { active: true }, + { other_value: 11 }, + ], }, }); }); - describe('merging scopes using `mergeWhereScopesWithAndOperator`', () => { - const scopes = { + describe('merging where clause', () => { + const testModelScopes = { whereAttributeIs1: { where: { field: 1, @@ -298,11 +336,17 @@ describe(Support.getTestDialectTeaser('Model'), () => { [Op.or]: [{ field: 3 }, { field: 3 }], }, }, + whereSequelizeWhere1: { + where: Sequelize.where('field', Op.is, 1), + }, + whereSequelizeWhere2: { + where: Sequelize.where('field', Op.is, 2), + }, }; const TestModel = current.define('testModel', {}, { mergeWhereScopesWithAndOperator: true, - scopes, + scopes: testModelScopes, }); describe('attributes', () => { @@ -316,7 +360,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ], }, }; - expect(util.inspect(scope, { depth: 3 })).to.deep.equal(util.inspect(expected, { depth: 3 })); + expect(scope).to.deepEqual(expected); }); it('should group multiple similar attributes with an unique Op.and', () => { @@ -330,7 +374,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ], }, }; - expect(util.inspect(scope, { depth: 3 })).to.deep.equal(util.inspect(expected, { depth: 3 })); + expect(scope).to.deepEqual(expected); }); it('should group different attributes with an Op.and', () => { @@ -343,7 +387,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ], }, }; - expect(util.inspect(scope, { depth: 3 })).to.deep.equal(util.inspect(expected, { depth: 3 })); + expect(scope).to.deepEqual(expected); }); }); @@ -358,7 +402,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ], }, }; - expect(util.inspect(scope, { depth: 5 })).to.deep.equal(util.inspect(expected, { depth: 5 })); + expect(scope).to.deepEqual(expected); }); it('should concatenate multiple Op.and into an unique one', () => { @@ -372,7 +416,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ], }, }; - expect(util.inspect(scope, { depth: 5 })).to.deep.equal(util.inspect(expected, { depth: 5 })); + expect(scope).to.deepEqual(expected); }); }); @@ -387,7 +431,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ], }, }; - expect(util.inspect(scope, { depth: 5 })).to.deep.equal(util.inspect(expected, { depth: 5 })); + expect(scope).to.deepEqual(expected); }); it('should group multiple Op.or with an unique Op.and', () => { @@ -401,7 +445,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ], }, }; - expect(util.inspect(scope, { depth: 5 })).to.deep.equal(util.inspect(expected, { depth: 5 })); + expect(scope).to.deepEqual(expected); }); it('should group multiple Op.or and Op.and with an unique Op.and', () => { @@ -416,7 +460,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ], }, }; - expect(util.inspect(scope, { depth: 5 })).to.deep.equal(util.inspect(expected, { depth: 5 })); + expect(scope).to.deepEqual(expected); }); it('should group multiple Op.and and Op.or with an unique Op.and', () => { @@ -431,21 +475,60 @@ describe(Support.getTestDialectTeaser('Model'), () => { ], }, }; - expect(util.inspect(scope, { depth: 5 })).to.deep.equal(util.inspect(expected, { depth: 5 })); + expect(scope).to.deepEqual(expected); + }); + }); + + describe('sequelize where', () => { + it('should group 2 sequelize.where with an Op.and', () => { + const scope = TestModel.scope(['whereSequelizeWhere1', 'whereSequelizeWhere2'])._scope; + const expected = { + where: { + [Op.and]: [ + Sequelize.where('field', Op.is, 1), + Sequelize.where('field', Op.is, 2), + ], + }, + }; + expect(scope).to.deepEqual(expected); + }); + + it('should group 2 sequelize.where and other scopes with an Op.and', () => { + const scope = TestModel.scope(['whereAttributeIs1', 'whereOpAnd1', 'whereOpOr1', 'whereSequelizeWhere1'])._scope; + const expected = { + where: { + [Op.and]: [ + { field: 1 }, + { [Op.and]: [{ field: 1 }, { field: 1 }] }, + { [Op.or]: [{ field: 1 }, { field: 1 }] }, + Sequelize.where('field', Op.is, 1), + ], + }, + }; + expect(scope).to.deepEqual(expected); }); }); }); it('should be able to use raw queries', () => { - expect(Company.scope([{ method: ['complexFunction', 'qux'] }])._scope).to.deep.equal({ - where: ['qux IN (SELECT foobar FROM some_sql_function(foo.bar))'], + expect(Company.scope([{ method: ['complexFunction', 'qux'] }])._scope).to.deepEqual({ + where: { + [Op.and]: [ + ['qux IN (SELECT foobar FROM some_sql_function(foo.bar))'], + ], + }, }); }); it('should override the default scope', () => { - expect(Company.scope(['defaultScope', { method: ['complexFunction', 'qux'] }])._scope).to.deep.equal({ + expect(Company.scope(['defaultScope', { method: ['complexFunction', 'qux'] }])._scope).to.deepEqual({ include: [{ model: Project }], - where: ['qux IN (SELECT foobar FROM some_sql_function(foo.bar))'], + where: { + [Op.and]: [ + { active: true }, + ['qux IN (SELECT foobar FROM some_sql_function(foo.bar))'], + ], + }, }); }); @@ -484,8 +567,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { include: [{ model: Project }], }); - expect(Company.scope('newScope')._scope).to.deep.equal({ - where: { this: 'that' }, + expect(Company.scope('newScope')._scope).to.deepEqual({ + where: { + [Op.and]: [ + { this: 'that' }, + ], + }, include: [{ model: Project }], }); }); @@ -503,8 +590,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, }, { override: true }); - expect(Company.scope('somethingTrue')._scope).to.deep.equal({ - where: { something: false }, + expect(Company.scope('somethingTrue')._scope).to.deepEqual({ + where: { + [Op.and]: [ + { something: false }, + ], + }, }); }); @@ -555,8 +646,17 @@ describe(Support.getTestDialectTeaser('Model'), () => { Company.addScope('alsoProject', { include: [{ model: Project, where: { something: true }, limit: 1 }], }); - expect(Company.scope(['project', 'alsoProject'])._scope).to.deep.equal({ - include: [{ model: Project, where: { something: true, somethingElse: 99 }, limit: 1 }], + expect(Company.scope(['project', 'alsoProject'])._scope).to.deepEqual({ + include: [{ + model: Project, + where: { + [Op.and]: [ + { something: false, somethingElse: 99 }, + { something: true }, + ], + }, + limit: 1, + }], }); }); }); @@ -564,10 +664,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('_injectScope', () => { it('should be able to merge scope and where', () => { Sequelize.Model._scope = { - where: { - something: true, - somethingElse: 42, - }, + where: { something: true, somethingElse: 42 }, limit: 15, offset: 3, }; @@ -581,10 +678,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { Sequelize.Model._injectScope(options); - expect(options).to.deep.equal({ + expect(options).to.deepEqual({ where: { - something: false, - somethingElse: 42, + [Op.and]: [ + { something: true, somethingElse: 42 }, + { something: false }, + ], }, limit: 9, offset: 3, @@ -593,10 +692,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should be able to merge scope and having', () => { Sequelize.Model._scope = { - having: { - something: true, - somethingElse: 42, - }, + having: { something: true, somethingElse: 42 }, limit: 15, offset: 3, }; @@ -610,10 +706,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { Sequelize.Model._injectScope(options); - expect(options).to.deep.equal({ + expect(options).to.deepEqual({ having: { - something: false, - somethingElse: 42, + [Op.and]: [ + { something: true, somethingElse: 42 }, + { something: false }, + ], }, limit: 9, offset: 3, @@ -633,7 +731,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { Sequelize.Model._injectScope(options); expect(options.include).to.have.length(1); - expect(options.include[0]).to.deep.equal({ model: Project, where: { something: true, somethingElse: 99 }, limit: 1 }); + expect(options.include[0]).to.deepEqual({ + model: Project, + where: { + [Op.and]: [ + { something: false, somethingElse: 99 }, + { something: true }, + ], + }, + limit: 1, + }); }); it('should be able to merge scoped include', () => { @@ -648,7 +755,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { Sequelize.Model._injectScope(options); expect(options.include).to.have.length(1); - expect(options.include[0]).to.deep.equal({ model: Project, where: { something: true, somethingElse: 99 }, limit: 1 }); + expect(options.include[0]).to.deepEqual({ + model: Project, + where: { + [Op.and]: [ + { something: false, somethingElse: 99 }, + { something: true }, + ], + }, + limit: 1, + }); }); it('should be able to merge aliased includes with the same model', () => {