groupedLimit.test.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. 'use strict';
  2. const groupBy = require('lodash/groupBy');
  3. const invokeMap = require('lodash/invokeMap');
  4. const property = require('lodash/property');
  5. const chai = require('chai');
  6. const sinon = require('sinon');
  7. const expect = chai.expect;
  8. const Support = require('../../support');
  9. const { DataTypes, Sequelize } = require('@sequelize/core');
  10. const current = Support.sequelize;
  11. if (current.dialect.supports['UNION ALL']) {
  12. describe(Support.getTestDialectTeaser('Model'), () => {
  13. describe('findAll', () => {
  14. describe('groupedLimit', () => {
  15. before(function () {
  16. this.clock = sinon.useFakeTimers();
  17. });
  18. afterEach(function () {
  19. this.clock.reset();
  20. });
  21. after(function () {
  22. this.clock.restore();
  23. });
  24. beforeEach(async function () {
  25. this.User = this.sequelize.define('user', {
  26. age: DataTypes.INTEGER,
  27. });
  28. this.Project = this.sequelize.define('project', {
  29. title: DataTypes.STRING,
  30. });
  31. this.Task = this.sequelize.define('task');
  32. this.ProjectUserParanoid = this.sequelize.define(
  33. 'project_user_paranoid',
  34. {},
  35. {
  36. timestamps: true,
  37. paranoid: true,
  38. createdAt: false,
  39. updatedAt: false,
  40. },
  41. );
  42. this.User.Projects = this.User.belongsToMany(this.Project, {
  43. through: 'project_user',
  44. inverse: { as: 'members' },
  45. });
  46. this.User.ParanoidProjects = this.User.belongsToMany(this.Project, {
  47. as: 'paranoidProjects',
  48. through: this.ProjectUserParanoid,
  49. inverse: { as: 'paranoidMembers' },
  50. });
  51. this.User.Tasks = this.User.hasMany(this.Task);
  52. await this.sequelize.sync({ force: true });
  53. await Promise.all([
  54. this.User.bulkCreate([
  55. { age: -5 },
  56. { age: 45 },
  57. { age: 7 },
  58. { age: -9 },
  59. { age: 8 },
  60. { age: 15 },
  61. { age: -9 },
  62. ]),
  63. this.Project.bulkCreate([{}, {}]),
  64. this.Task.bulkCreate([{}, {}]),
  65. ]);
  66. const [users, projects, tasks] = await Promise.all([
  67. this.User.findAll(),
  68. this.Project.findAll(),
  69. this.Task.findAll(),
  70. ]);
  71. this.projects = projects;
  72. await Promise.all([
  73. projects[0].setMembers(users.slice(0, 4)),
  74. projects[1].setMembers(users.slice(2)),
  75. projects[0].setParanoidMembers(users.slice(0, 4)),
  76. projects[1].setParanoidMembers(users.slice(2)),
  77. users[2].setTasks(tasks),
  78. ]);
  79. });
  80. describe('on: belongsToMany', () => {
  81. it('maps attributes from a grouped limit to models', async function () {
  82. const users = await this.User.findAll({
  83. groupedLimit: {
  84. limit: 3,
  85. on: this.User.Projects,
  86. values: this.projects.map(item => item.get('id')),
  87. },
  88. });
  89. expect(users).to.have.length(5);
  90. for (const u of users.filter(u => u.get('id') !== 3)) {
  91. expect(u.get('project_user')).to.have.length(1);
  92. }
  93. for (const u of users.filter(u => u.get('id') === 3)) {
  94. expect(u.get('project_user')).to.have.length(2);
  95. }
  96. });
  97. it('maps attributes from a grouped limit to models with include', async function () {
  98. const users = await this.User.findAll({
  99. groupedLimit: {
  100. limit: 3,
  101. on: this.User.Projects,
  102. values: this.projects.map(item => item.get('id')),
  103. },
  104. order: ['id'],
  105. include: [this.User.Tasks],
  106. });
  107. /*
  108. project1 - 1, 2, 3
  109. project2 - 3, 4, 5
  110. */
  111. expect(users).to.have.length(5);
  112. expect(users.map(u => u.get('id'))).to.deep.equal([1, 2, 3, 4, 5]);
  113. expect(users[2].get('tasks')).to.have.length(2);
  114. for (const u of users.filter(u => u.get('id') !== 3)) {
  115. expect(u.get('project_user')).to.have.length(1);
  116. }
  117. for (const u of users.filter(u => u.get('id') === 3)) {
  118. expect(u.get('project_user')).to.have.length(2);
  119. }
  120. });
  121. it('works with computed orders', async function () {
  122. const users = await this.User.findAll({
  123. attributes: ['id'],
  124. groupedLimit: {
  125. limit: 3,
  126. on: this.User.Projects,
  127. values: this.projects.map(item => item.get('id')),
  128. },
  129. order: [
  130. Sequelize.fn('ABS', Sequelize.col('age')),
  131. // Two users have the same abs(age), so we need to make sure that the order is deterministic
  132. ['id', 'DESC'],
  133. ],
  134. include: [this.User.Tasks],
  135. });
  136. /*
  137. project1 - 1, 3, 4
  138. project2 - 3, 5, 7
  139. */
  140. expect(users).to.have.length(5);
  141. expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]);
  142. });
  143. it('works with paranoid junction models', async function () {
  144. const users0 = await this.User.findAll({
  145. attributes: ['id'],
  146. groupedLimit: {
  147. limit: 3,
  148. on: this.User.ParanoidProjects,
  149. values: this.projects.map(item => item.get('id')),
  150. },
  151. order: [Sequelize.fn('ABS', Sequelize.col('age')), ['id', 'DESC']],
  152. include: [this.User.Tasks],
  153. });
  154. /*
  155. project1 - 1, 3, 4
  156. project2 - 3, 5, 7
  157. */
  158. expect(users0).to.have.length(5);
  159. expect(users0.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]);
  160. await Promise.all([
  161. this.projects[0].setParanoidMembers(users0.slice(0, 2)),
  162. this.projects[1].setParanoidMembers(users0.slice(4)),
  163. ]);
  164. const users = await this.User.findAll({
  165. attributes: ['id'],
  166. groupedLimit: {
  167. limit: 3,
  168. on: this.User.ParanoidProjects,
  169. values: this.projects.map(item => item.get('id')),
  170. },
  171. order: [Sequelize.fn('ABS', Sequelize.col('age')), ['id', 'DESC']],
  172. include: [this.User.Tasks],
  173. });
  174. /*
  175. project1 - 1, 3
  176. project2 - 4
  177. */
  178. expect(users).to.have.length(3);
  179. expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 4]);
  180. });
  181. });
  182. describe('on: hasMany', () => {
  183. beforeEach(async function () {
  184. this.User = this.sequelize.define('user');
  185. this.Task = this.sequelize.define('task');
  186. this.User.Tasks = this.User.hasMany(this.Task);
  187. await this.sequelize.sync({ force: true });
  188. await Promise.all([
  189. this.User.bulkCreate([{}, {}, {}]),
  190. this.Task.bulkCreate([
  191. { id: 1 },
  192. { id: 2 },
  193. { id: 3 },
  194. { id: 4 },
  195. { id: 5 },
  196. { id: 6 },
  197. ]),
  198. ]);
  199. const [users, tasks] = await Promise.all([this.User.findAll(), this.Task.findAll()]);
  200. this.users = users;
  201. await Promise.all([
  202. users[0].setTasks(tasks[0]),
  203. users[1].setTasks(tasks.slice(1, 4)),
  204. users[2].setTasks(tasks.slice(4)),
  205. ]);
  206. });
  207. it('Applies limit and order correctly', async function () {
  208. const tasks = await this.Task.findAll({
  209. order: [['id', 'DESC']],
  210. groupedLimit: {
  211. limit: 3,
  212. on: this.User.Tasks,
  213. values: this.users.map(item => item.get('id')),
  214. },
  215. });
  216. const byUser = groupBy(tasks, property('userId'));
  217. expect(Object.keys(byUser)).to.have.length(3);
  218. expect(byUser[1]).to.have.length(1);
  219. expect(byUser[2]).to.have.length(3);
  220. expect(invokeMap(byUser[2], 'get', 'id')).to.deep.equal([4, 3, 2]);
  221. expect(byUser[3]).to.have.length(2);
  222. });
  223. });
  224. });
  225. });
  226. });
  227. }