scope.test.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  1. import type { FindOptions } from '@sequelize/core';
  2. import { DataTypes, Op, Sequelize, col, literal, where } from '@sequelize/core';
  3. import { expect } from 'chai';
  4. import assert from 'node:assert';
  5. import { beforeEach2, getTestDialectTeaser, resetSequelizeInstance } from '../../support';
  6. const sequelize = require('../../support').sequelize;
  7. describe(getTestDialectTeaser('Model'), () => {
  8. beforeEach(() => {
  9. resetSequelizeInstance();
  10. });
  11. function getModels() {
  12. const Project = sequelize.define('project', {});
  13. const User = sequelize.define(
  14. 'user',
  15. {
  16. password: DataTypes.STRING,
  17. value: DataTypes.INTEGER,
  18. name: DataTypes.STRING,
  19. },
  20. {
  21. defaultScope: {
  22. attributes: {
  23. exclude: ['password'],
  24. },
  25. },
  26. scopes: {
  27. aScope: {
  28. attributes: {
  29. exclude: ['value'],
  30. },
  31. },
  32. },
  33. },
  34. );
  35. const Company = sequelize.define(
  36. 'company',
  37. {},
  38. {
  39. defaultScope: {
  40. include: [Project],
  41. where: { active: true },
  42. },
  43. scopes: {
  44. complexFunction(value: any): FindOptions {
  45. return {
  46. where: literal(`${value} IN (SELECT foobar FROM some_sql_function(foo.bar))`),
  47. };
  48. },
  49. somethingTrue: {
  50. where: {
  51. something: true,
  52. somethingElse: 42,
  53. },
  54. limit: 5,
  55. },
  56. somethingFalse: {
  57. where: {
  58. something: false,
  59. },
  60. },
  61. sequelizeWhere: {
  62. where: where(col('a'), 1),
  63. },
  64. users: {
  65. include: [{ model: User }],
  66. },
  67. alsoUsers: {
  68. include: [{ model: User, where: { something: 42 } }],
  69. },
  70. projects: {
  71. include: [Project],
  72. },
  73. groupByCompanyId: {
  74. group: ['company.id'],
  75. },
  76. groupByProjectId: {
  77. group: ['project.id'],
  78. include: [Project],
  79. },
  80. noArgs() {
  81. // This does not make much sense, since it does not actually need to be in a function,
  82. // In reality it could be used to do for example new Date or random in the scope - but we want it deterministic
  83. return {
  84. where: {
  85. other_value: 7,
  86. },
  87. };
  88. },
  89. actualValue(value: any) {
  90. return {
  91. where: {
  92. other_value: value,
  93. },
  94. };
  95. },
  96. },
  97. },
  98. );
  99. Company.hasMany(User);
  100. Company.hasMany(Project);
  101. return { Project, User, Company };
  102. }
  103. describe('withScope', () => {
  104. describe('attribute exclude / include', () => {
  105. const vars = beforeEach2(() => {
  106. const User = sequelize.define(
  107. 'User',
  108. {
  109. password: DataTypes.STRING,
  110. value: DataTypes.INTEGER,
  111. name: DataTypes.STRING,
  112. },
  113. {
  114. defaultScope: {
  115. attributes: {
  116. exclude: ['password'],
  117. },
  118. },
  119. scopes: {
  120. aScope: {
  121. attributes: {
  122. exclude: ['value'],
  123. },
  124. },
  125. },
  126. },
  127. );
  128. return { User };
  129. });
  130. it('should not expand attributes', () => {
  131. const { User } = vars;
  132. expect(User._scope.attributes).to.deep.equal({ exclude: ['password'] });
  133. });
  134. it('should not expand attributes', () => {
  135. const { User } = vars;
  136. expect(User.withScope('aScope')._scope.attributes).to.deep.equal({ exclude: ['value'] });
  137. });
  138. it('should unite attributes with array', () => {
  139. const { User } = vars;
  140. expect(User.withScope('aScope', 'defaultScope')._scope.attributes).to.deep.equal({
  141. exclude: ['value', 'password'],
  142. });
  143. });
  144. it('should not modify the original scopes when merging them', () => {
  145. const { User } = vars;
  146. expect(
  147. User.withScope('defaultScope', 'aScope').options.defaultScope!.attributes,
  148. ).to.deep.equal({ exclude: ['password'] });
  149. });
  150. });
  151. it('defaultScope should be an empty object if not overridden', () => {
  152. const Foo = sequelize.define('foo', {}, {});
  153. expect(Foo.withScope('defaultScope')._scope).to.deep.equal({});
  154. });
  155. it('should apply default scope', () => {
  156. const { Company, Project } = getModels();
  157. expect(Company._scope).to.deep.equal({
  158. include: [Project],
  159. where: { active: true },
  160. });
  161. });
  162. it('should be able to merge scopes', () => {
  163. const { Company } = getModels();
  164. expect(
  165. Company.withScope('somethingTrue', 'somethingFalse', 'sequelizeWhere')._scope,
  166. ).to.deep.equal({
  167. where: {
  168. [Op.and]: [
  169. { something: true, somethingElse: 42 },
  170. { something: false },
  171. where(col('a'), 1),
  172. ],
  173. },
  174. limit: 5,
  175. });
  176. });
  177. it('should support multiple, coexistent scoped models', () => {
  178. const { Company } = getModels();
  179. const scoped1 = Company.withScope('somethingTrue');
  180. const scoped2 = Company.withScope('somethingFalse');
  181. expect(scoped1._scope).to.deep.equal({
  182. where: {
  183. something: true,
  184. somethingElse: 42,
  185. },
  186. limit: 5,
  187. });
  188. expect(scoped2._scope).to.deep.equal({
  189. where: {
  190. something: false,
  191. },
  192. });
  193. });
  194. it('should work with function scopes', () => {
  195. const { Company } = getModels();
  196. expect(Company.withScope({ method: ['actualValue', 11] })._scope).to.deep.equal({
  197. where: {
  198. other_value: 11,
  199. },
  200. });
  201. expect(Company.withScope('noArgs')._scope).to.deep.equal({
  202. where: {
  203. other_value: 7,
  204. },
  205. });
  206. });
  207. it('should work with consecutive function scopes', () => {
  208. const { Company } = getModels();
  209. const scope = { method: ['actualValue', 11] };
  210. expect(Company.withScope(scope)._scope).to.deep.equal({
  211. where: {
  212. other_value: 11,
  213. },
  214. });
  215. expect(Company.withScope(scope)._scope).to.deep.equal({
  216. where: {
  217. other_value: 11,
  218. },
  219. });
  220. });
  221. it('should be able to check default scope name', () => {
  222. const { Company } = getModels();
  223. expect(Company._scopeNames).to.include('defaultScope');
  224. });
  225. it('should be able to check custom scope name', () => {
  226. const { Company } = getModels();
  227. expect(Company.withScope('users')._scopeNames).to.include('users');
  228. });
  229. it('should be able to check multiple custom scope names', () => {
  230. const { Company } = getModels();
  231. expect(Company.withScope('users', 'projects')._scopeNames).to.include.members([
  232. 'users',
  233. 'projects',
  234. ]);
  235. });
  236. it('should be able to merge two scoped includes', () => {
  237. const { Company, Project, User } = getModels();
  238. expect(Company.withScope('users', 'projects')._scope).to.deep.equal({
  239. include: [
  240. { model: User, association: Company.associations.users, as: 'users' },
  241. { model: Project, association: Company.associations.projects, as: 'projects' },
  242. ],
  243. });
  244. });
  245. it('should be able to keep original scope definition clean', () => {
  246. const { Company, Project, User } = getModels();
  247. expect(Company.withScope('projects', 'users', 'alsoUsers')._scope).to.deep.equal({
  248. include: [
  249. { model: Project, association: Company.associations.projects, as: 'projects' },
  250. {
  251. model: User,
  252. association: Company.associations.users,
  253. as: 'users',
  254. where: { something: 42 },
  255. },
  256. ],
  257. });
  258. expect(Company.options.scopes!.alsoUsers).to.deep.equal({
  259. include: [
  260. {
  261. model: User,
  262. association: Company.associations.users,
  263. as: 'users',
  264. where: { something: 42 },
  265. },
  266. ],
  267. });
  268. expect(Company.options.scopes!.users).to.deep.equal({
  269. include: [{ model: User, association: Company.associations.users, as: 'users' }],
  270. });
  271. });
  272. it('should be able to override the default scope', () => {
  273. const { Company } = getModels();
  274. expect(Company.withScope('somethingTrue')._scope).to.deep.equal({
  275. where: {
  276. something: true,
  277. somethingElse: 42,
  278. },
  279. limit: 5,
  280. });
  281. });
  282. it('should be able to combine default with another scope', () => {
  283. const { Company, Project } = getModels();
  284. expect(
  285. Company.withScope(['defaultScope', { method: ['actualValue', 11] }])._scope,
  286. ).to.deep.equal({
  287. include: [{ model: Project, association: Company.associations.projects, as: 'projects' }],
  288. where: {
  289. [Op.and]: [{ active: true }, { other_value: 11 }],
  290. },
  291. });
  292. });
  293. describe('merging where clause', () => {
  294. const testModelScopes = {
  295. whereAttributeIs1: {
  296. where: {
  297. field: 1,
  298. },
  299. },
  300. whereAttributeIs2: {
  301. where: {
  302. field: 2,
  303. },
  304. },
  305. whereAttributeIs3: {
  306. where: {
  307. field: 3,
  308. },
  309. },
  310. whereOtherAttributeIs4: {
  311. where: {
  312. otherField: 4,
  313. },
  314. },
  315. whereOpAnd1: {
  316. where: {
  317. [Op.and]: [{ field: 1 }, { field: 1 }],
  318. },
  319. },
  320. whereOpAnd2: {
  321. where: {
  322. [Op.and]: [{ field: 2 }, { field: 2 }],
  323. },
  324. },
  325. whereOpAnd3: {
  326. where: {
  327. [Op.and]: [{ field: 3 }, { field: 3 }],
  328. },
  329. },
  330. whereOpOr1: {
  331. where: {
  332. [Op.or]: [{ field: 1 }, { field: 1 }],
  333. },
  334. },
  335. whereOpOr2: {
  336. where: {
  337. [Op.or]: [{ field: 2 }, { field: 2 }],
  338. },
  339. },
  340. whereOpOr3: {
  341. where: {
  342. [Op.or]: [{ field: 3 }, { field: 3 }],
  343. },
  344. },
  345. whereSequelizeWhere1: {
  346. where: where(col('field'), Op.is, 1),
  347. },
  348. whereSequelizeWhere2: {
  349. where: where(col('field'), Op.is, 2),
  350. },
  351. };
  352. const vars = beforeEach2(() => {
  353. const TestModel = sequelize.define(
  354. 'TestModel',
  355. {},
  356. {
  357. scopes: testModelScopes,
  358. },
  359. );
  360. return { TestModel };
  361. });
  362. describe('attributes', () => {
  363. it('should group 2 similar attributes with an Op.and', () => {
  364. const { TestModel } = vars;
  365. const scope = TestModel.withScope(['whereAttributeIs1', 'whereAttributeIs2'])._scope;
  366. const expected = {
  367. where: {
  368. [Op.and]: [{ field: 1 }, { field: 2 }],
  369. },
  370. };
  371. expect(scope).to.deep.equal(expected);
  372. });
  373. it('should group multiple similar attributes with an unique Op.and', () => {
  374. const { TestModel } = vars;
  375. const scope = TestModel.withScope([
  376. 'whereAttributeIs1',
  377. 'whereAttributeIs2',
  378. 'whereAttributeIs3',
  379. ])._scope;
  380. const expected = {
  381. where: {
  382. [Op.and]: [{ field: 1 }, { field: 2 }, { field: 3 }],
  383. },
  384. };
  385. expect(scope).to.deep.equal(expected);
  386. });
  387. it('should group different attributes with an Op.and', () => {
  388. const { TestModel } = vars;
  389. const scope = TestModel.withScope(['whereAttributeIs1', 'whereOtherAttributeIs4'])._scope;
  390. const expected = {
  391. where: {
  392. [Op.and]: [{ field: 1 }, { otherField: 4 }],
  393. },
  394. };
  395. expect(scope).to.deep.equal(expected);
  396. });
  397. });
  398. describe('and operators', () => {
  399. it('should concatenate 2 Op.and into an unique one', () => {
  400. const { TestModel } = vars;
  401. const scope = TestModel.withScope(['whereOpAnd1', 'whereOpAnd2'])._scope;
  402. const expected = {
  403. where: {
  404. [Op.and]: [{ field: 1 }, { field: 1 }, { field: 2 }, { field: 2 }],
  405. },
  406. };
  407. expect(scope).to.deep.equal(expected);
  408. });
  409. it('should concatenate multiple Op.and into an unique one', () => {
  410. const { TestModel } = vars;
  411. const scope = TestModel.withScope(['whereOpAnd1', 'whereOpAnd2', 'whereOpAnd3'])._scope;
  412. const expected = {
  413. where: {
  414. [Op.and]: [
  415. { field: 1 },
  416. { field: 1 },
  417. { field: 2 },
  418. { field: 2 },
  419. { field: 3 },
  420. { field: 3 },
  421. ],
  422. },
  423. };
  424. expect(scope).to.deep.equal(expected);
  425. });
  426. });
  427. describe('or operators', () => {
  428. it('should group 2 Op.or with an Op.and', () => {
  429. const { TestModel } = vars;
  430. const scope = TestModel.withScope(['whereOpOr1', 'whereOpOr2'])._scope;
  431. const expected = {
  432. where: {
  433. [Op.and]: [
  434. { [Op.or]: [{ field: 1 }, { field: 1 }] },
  435. { [Op.or]: [{ field: 2 }, { field: 2 }] },
  436. ],
  437. },
  438. };
  439. expect(scope).to.deep.equal(expected);
  440. });
  441. it('should group multiple Op.or with an unique Op.and', () => {
  442. const { TestModel } = vars;
  443. const scope = TestModel.withScope(['whereOpOr1', 'whereOpOr2', 'whereOpOr3'])._scope;
  444. const expected = {
  445. where: {
  446. [Op.and]: [
  447. { [Op.or]: [{ field: 1 }, { field: 1 }] },
  448. { [Op.or]: [{ field: 2 }, { field: 2 }] },
  449. { [Op.or]: [{ field: 3 }, { field: 3 }] },
  450. ],
  451. },
  452. };
  453. expect(scope).to.deep.equal(expected);
  454. });
  455. it('should group multiple Op.or and Op.and with an unique Op.and', () => {
  456. const { TestModel } = vars;
  457. const scope = TestModel.withScope([
  458. 'whereOpOr1',
  459. 'whereOpOr2',
  460. 'whereOpAnd1',
  461. 'whereOpAnd2',
  462. ])._scope;
  463. const expected = {
  464. where: {
  465. [Op.and]: [
  466. { [Op.or]: [{ field: 1 }, { field: 1 }] },
  467. { [Op.or]: [{ field: 2 }, { field: 2 }] },
  468. { field: 1 },
  469. { field: 1 },
  470. { field: 2 },
  471. { field: 2 },
  472. ],
  473. },
  474. };
  475. expect(scope).to.deep.equal(expected);
  476. });
  477. it('should group multiple Op.and and Op.or with an unique Op.and', () => {
  478. const { TestModel } = vars;
  479. const scope = TestModel.withScope([
  480. 'whereOpAnd1',
  481. 'whereOpAnd2',
  482. 'whereOpOr1',
  483. 'whereOpOr2',
  484. ])._scope;
  485. const expected = {
  486. where: {
  487. [Op.and]: [
  488. { field: 1 },
  489. { field: 1 },
  490. { field: 2 },
  491. { field: 2 },
  492. { [Op.or]: [{ field: 1 }, { field: 1 }] },
  493. { [Op.or]: [{ field: 2 }, { field: 2 }] },
  494. ],
  495. },
  496. };
  497. expect(scope).to.deep.equal(expected);
  498. });
  499. });
  500. describe('sequelize where', () => {
  501. it('should group 2 sequelize.where with an Op.and', () => {
  502. const { TestModel } = vars;
  503. const scope = TestModel.withScope([
  504. 'whereSequelizeWhere1',
  505. 'whereSequelizeWhere2',
  506. ])._scope;
  507. const expected = {
  508. where: {
  509. [Op.and]: [where(col('field'), Op.is, 1), where(col('field'), Op.is, 2)],
  510. },
  511. };
  512. expect(scope).to.deep.equal(expected);
  513. });
  514. it('should group 2 sequelize.where and other scopes with an Op.and', () => {
  515. const { TestModel } = vars;
  516. const scope = TestModel.withScope([
  517. 'whereAttributeIs1',
  518. 'whereOpAnd1',
  519. 'whereOpOr1',
  520. 'whereSequelizeWhere1',
  521. ])._scope;
  522. const expected = {
  523. where: {
  524. [Op.and]: [
  525. { field: 1 },
  526. { field: 1 },
  527. { field: 1 },
  528. { [Op.or]: [{ field: 1 }, { field: 1 }] },
  529. Sequelize.where(col('field'), Op.is, 1),
  530. ],
  531. },
  532. };
  533. expect(scope).to.deep.equal(expected);
  534. });
  535. });
  536. });
  537. it('should be able to use raw queries', () => {
  538. const { Company } = getModels();
  539. expect(Company.withScope([{ method: ['complexFunction', 'qux'] }])._scope).to.deep.equal({
  540. where: literal('qux IN (SELECT foobar FROM some_sql_function(foo.bar))'),
  541. });
  542. });
  543. it('should override the default scope', () => {
  544. const { Company, Project } = getModels();
  545. expect(
  546. Company.withScope(['defaultScope', { method: ['complexFunction', 'qux'] }])._scope,
  547. ).to.deep.equal({
  548. include: [{ model: Project, association: Company.associations.projects, as: 'projects' }],
  549. where: {
  550. [Op.and]: [
  551. { active: true },
  552. literal('qux IN (SELECT foobar FROM some_sql_function(foo.bar))'),
  553. ],
  554. },
  555. });
  556. });
  557. it("should emit an error for scopes that don't exist", () => {
  558. const { Company } = getModels();
  559. expect(() => {
  560. Company.withScope('doesntexist');
  561. }).to.throw(
  562. '"company.withScope()" has been called with an invalid scope: "doesntexist" does not exist.',
  563. );
  564. });
  565. it('should concatenate scope groups', () => {
  566. const { Company, Project } = getModels();
  567. expect(Company.withScope('groupByCompanyId', 'groupByProjectId')._scope).to.deep.equal({
  568. group: ['company.id', 'project.id'],
  569. include: [{ model: Project, association: Company.associations.projects, as: 'projects' }],
  570. });
  571. });
  572. });
  573. describe('withoutScope', () => {
  574. it('returns a model with no scope (including the default scope)', () => {
  575. const { Company } = getModels();
  576. expect(Company.withScope(null)._scope).to.be.empty;
  577. expect(Company.withoutScope()._scope).to.be.empty;
  578. // Yes, being unscoped is also a scope - this prevents inject defaultScope, when including a scoped model, see #4663
  579. expect(Company.withoutScope().scoped).to.be.true;
  580. });
  581. it('returns the same model no matter which variant it was called on', () => {
  582. const { Company } = getModels();
  583. assert(Company.withoutScope() === Company.withScope('somethingTrue').withoutScope());
  584. });
  585. it('returns the same model if used with schema', () => {
  586. const { Company } = getModels();
  587. assert(
  588. Company.withSchema('schema1').withoutScope() ===
  589. Company.withoutScope().withSchema('schema1'),
  590. );
  591. });
  592. });
  593. describe('withInitialScope', () => {
  594. it('returns the initial model if no schema is defined', () => {
  595. const { Company } = getModels();
  596. assert(Company.withScope('somethingTrue').withInitialScope() === Company);
  597. });
  598. it('returns the a model with just the schema if one was defined is defined', () => {
  599. const { Company } = getModels();
  600. assert(
  601. Company.withSchema('schema1').withInitialScope() ===
  602. Company.withInitialScope().withSchema('schema1'),
  603. );
  604. });
  605. });
  606. describe('addScope', () => {
  607. it('works if the model does not have any initial scopes', () => {
  608. const MyModel = sequelize.define('model');
  609. expect(() => {
  610. MyModel.addScope('anything', {});
  611. }).not.to.throw();
  612. });
  613. it('allows me to add a new scope', () => {
  614. const { Company, Project } = getModels();
  615. expect(() => {
  616. Company.withScope('newScope');
  617. }).to.throw(
  618. '"company.withScope()" has been called with an invalid scope: "newScope" does not exist.',
  619. );
  620. Company.addScope('newScope', {
  621. where: {
  622. this: 'that',
  623. },
  624. include: [{ model: Project }],
  625. });
  626. expect(Company.withScope('newScope')._scope).to.deep.equal({
  627. where: {
  628. this: 'that',
  629. },
  630. include: [{ model: Project, association: Company.associations.projects, as: 'projects' }],
  631. });
  632. });
  633. it('warns me when overriding an existing scope', () => {
  634. const { Company } = getModels();
  635. expect(() => {
  636. Company.addScope('somethingTrue', {});
  637. }).to.throw(
  638. 'The scope somethingTrue already exists. Pass { override: true } as options to silence this error',
  639. );
  640. });
  641. it('allows me to override an existing scope', () => {
  642. const { Company } = getModels();
  643. Company.addScope(
  644. 'somethingTrue',
  645. {
  646. where: {
  647. something: false,
  648. },
  649. },
  650. { override: true },
  651. );
  652. expect(Company.withScope('somethingTrue')._scope).to.deep.equal({
  653. where: {
  654. something: false,
  655. },
  656. });
  657. });
  658. it('warns me when overriding an existing default scope', () => {
  659. const { Company } = getModels();
  660. expect(() => {
  661. Company.addScope('defaultScope', {});
  662. }).to.throw(
  663. 'The scope defaultScope already exists. Pass { override: true } as options to silence this error',
  664. );
  665. });
  666. it('should not warn if default scope is not defined', () => {
  667. const MyModel = sequelize.define('model');
  668. expect(() => {
  669. MyModel.addScope('defaultScope', {});
  670. }).not.to.throw();
  671. });
  672. it('allows me to override a default scope', () => {
  673. const { Company, Project } = getModels();
  674. Company.addScope(
  675. 'defaultScope',
  676. {
  677. include: [{ model: Project }],
  678. },
  679. { override: true },
  680. );
  681. expect(Company._scope).to.deep.equal({
  682. include: [{ model: Project }],
  683. });
  684. });
  685. it('should keep exclude and include attributes', () => {
  686. const { Company } = getModels();
  687. Company.addScope('newIncludeScope', {
  688. attributes: {
  689. include: ['foobar'],
  690. exclude: ['createdAt'],
  691. },
  692. });
  693. expect(Company.withScope('newIncludeScope')._scope).to.deep.equal({
  694. attributes: {
  695. include: ['foobar'],
  696. exclude: ['createdAt'],
  697. },
  698. });
  699. });
  700. it('should be able to merge scopes with the same include', () => {
  701. const { Company, Project } = getModels();
  702. Company.addScope('project', {
  703. include: [{ model: Project, where: { something: false, somethingElse: 99 } }],
  704. });
  705. Company.addScope('alsoProject', {
  706. include: [{ model: Project, where: { something: true }, limit: 1 }],
  707. });
  708. expect(Company.withScope(['project', 'alsoProject'])._scope).to.deep.equal({
  709. include: [
  710. {
  711. model: Project,
  712. where: {
  713. [Op.and]: [{ something: false, somethingElse: 99 }, { something: true }],
  714. },
  715. association: Company.associations.projects,
  716. as: 'projects',
  717. limit: 1,
  718. },
  719. ],
  720. });
  721. });
  722. });
  723. describe('_injectScope', () => {
  724. it('should be able to merge scope and where', () => {
  725. const MyModel = sequelize.define('model');
  726. MyModel.addScope('defaultScope', {
  727. where: { something: true, somethingElse: 42 },
  728. limit: 15,
  729. offset: 3,
  730. });
  731. const options = {
  732. where: {
  733. something: false,
  734. },
  735. limit: 9,
  736. };
  737. MyModel._normalizeIncludes(options, MyModel);
  738. MyModel._injectScope(options);
  739. expect(options).to.deep.equal({
  740. where: {
  741. [Op.and]: [{ something: true, somethingElse: 42 }, { something: false }],
  742. },
  743. limit: 9,
  744. offset: 3,
  745. });
  746. });
  747. it('should be able to merge scope and having', () => {
  748. const MyModel = sequelize.define('model');
  749. MyModel.addScope('defaultScope', {
  750. having: { something: true, somethingElse: 42 },
  751. limit: 15,
  752. offset: 3,
  753. });
  754. const options = {
  755. having: {
  756. something: false,
  757. },
  758. limit: 9,
  759. };
  760. MyModel._normalizeIncludes(options, MyModel);
  761. MyModel._injectScope(options);
  762. expect(options).to.deep.equal({
  763. having: {
  764. [Op.and]: [{ something: true, somethingElse: 42 }, { something: false }],
  765. },
  766. limit: 9,
  767. offset: 3,
  768. });
  769. });
  770. it('should be able to merge scoped include', () => {
  771. const { Project } = getModels();
  772. const MyModel = sequelize.define('model');
  773. MyModel.hasMany(Project);
  774. MyModel.addScope('defaultScope', {
  775. include: [{ model: Project, where: { something: false, somethingElse: 99 } }],
  776. });
  777. const options = {
  778. include: [{ model: Project, where: { something: true }, limit: 1 }],
  779. };
  780. MyModel._conformIncludes(options, MyModel);
  781. MyModel._injectScope(options);
  782. expect(options.include).to.deep.equal([
  783. {
  784. model: Project,
  785. where: {
  786. [Op.and]: [{ something: false, somethingElse: 99 }, { something: true }],
  787. },
  788. association: MyModel.associations.projects,
  789. as: 'projects',
  790. limit: 1,
  791. },
  792. ]);
  793. });
  794. it('should be able to merge aliased includes with the same model', () => {
  795. const { User } = getModels();
  796. const MyModel = sequelize.define('model');
  797. MyModel.hasMany(User, { as: 'someUser' });
  798. MyModel.hasMany(User, { as: 'otherUser' });
  799. MyModel.addScope('defaultScope', {
  800. include: [{ model: User, as: 'someUser' }],
  801. });
  802. const options = {
  803. include: [{ model: User, as: 'otherUser' }],
  804. };
  805. MyModel._normalizeIncludes(options, MyModel);
  806. MyModel._injectScope(options);
  807. expect(options.include).to.have.length(2);
  808. expect(options.include[0]).to.deep.equal({
  809. model: User,
  810. as: 'someUser',
  811. association: MyModel.associations.someUser,
  812. });
  813. expect(options.include[1]).to.deep.equal({
  814. model: User,
  815. as: 'otherUser',
  816. association: MyModel.associations.otherUser,
  817. });
  818. });
  819. it('should be able to merge scoped include with include in find', () => {
  820. const { Project, User } = getModels();
  821. const MyModel = sequelize.define('model');
  822. MyModel.hasMany(Project);
  823. MyModel.hasMany(User);
  824. MyModel.addScope('defaultScope', {
  825. include: [{ model: Project, where: { something: false } }],
  826. });
  827. const options = {
  828. include: [{ model: User, where: { something: true } }],
  829. };
  830. MyModel._normalizeIncludes(options, MyModel);
  831. MyModel._injectScope(options);
  832. expect(options.include).to.have.length(2);
  833. expect(options.include[0]).to.deep.equal({
  834. model: Project,
  835. as: 'projects',
  836. association: MyModel.associations.projects,
  837. where: { something: false },
  838. });
  839. expect(options.include[1]).to.deep.equal({
  840. model: User,
  841. as: 'users',
  842. association: MyModel.associations.users,
  843. where: { something: true },
  844. });
  845. });
  846. describe('include all', () => {
  847. it('scope with all', () => {
  848. const { User, Project } = getModels();
  849. const MyModel = sequelize.define('model');
  850. MyModel.hasMany(User);
  851. MyModel.hasMany(Project);
  852. MyModel.addScope('defaultScope', {
  853. include: [{ all: true }],
  854. });
  855. const options = {
  856. include: [{ model: User, where: { something: true } }],
  857. };
  858. MyModel._normalizeIncludes(options, MyModel);
  859. MyModel._injectScope(options);
  860. expect(options.include).to.have.length(2);
  861. expect(options.include[0]).to.deep.equal({
  862. model: User,
  863. as: 'users',
  864. association: MyModel.associations.users,
  865. where: { something: true },
  866. });
  867. expect(options.include[1]).to.deep.equal({
  868. model: Project,
  869. as: 'projects',
  870. association: MyModel.associations.projects,
  871. });
  872. });
  873. it('options with all', () => {
  874. const { Project, User } = getModels();
  875. const MyModel = sequelize.define('model');
  876. MyModel.hasMany(User);
  877. MyModel.hasMany(Project);
  878. MyModel.addScope('defaultScope', {
  879. include: [{ model: User, where: { something: true } }],
  880. });
  881. const options = {
  882. include: [{ all: true }],
  883. };
  884. MyModel._normalizeIncludes(options, MyModel);
  885. MyModel._injectScope(options);
  886. expect(options.include).to.have.length(2);
  887. expect(options.include[0]).to.deep.equal({
  888. model: User,
  889. as: 'users',
  890. association: MyModel.associations.users,
  891. where: { something: true },
  892. });
  893. expect(options.include[1]).to.deep.equal({
  894. model: Project,
  895. as: 'projects',
  896. association: MyModel.associations.projects,
  897. });
  898. });
  899. });
  900. });
  901. });