/spec/finders/issues_finder_spec.rb

https://gitlab.com/tnir/gitlab-ce · Ruby · 1000 lines · 763 code · 233 blank · 4 comment · 4 complexity · 8edf1bc2076764233c33faa047068bf5 MD5 · raw file

  1. # frozen_string_literal: true
  2. require 'spec_helper'
  3. describe IssuesFinder do
  4. include_context 'IssuesFinder context'
  5. describe '#execute' do
  6. include_context 'IssuesFinder#execute context'
  7. context 'scope: all' do
  8. let(:scope) { 'all' }
  9. it 'returns all issues' do
  10. expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
  11. end
  12. context 'assignee filtering' do
  13. let(:issuables) { issues }
  14. it_behaves_like 'assignee ID filter' do
  15. let(:params) { { assignee_id: user.id } }
  16. let(:expected_issuables) { [issue1, issue2] }
  17. end
  18. it_behaves_like 'assignee NOT ID filter' do
  19. let(:params) { { not: { assignee_id: user.id } } }
  20. let(:expected_issuables) { [issue3, issue4] }
  21. end
  22. context 'filter by username' do
  23. let_it_be(:user3) { create(:user) }
  24. before do
  25. project2.add_developer(user3)
  26. issue2.assignees = [user2]
  27. issue3.assignees = [user3]
  28. end
  29. it_behaves_like 'assignee username filter' do
  30. let(:params) { { assignee_username: [user2.username] } }
  31. let(:expected_issuables) { [issue2] }
  32. end
  33. it_behaves_like 'assignee NOT username filter' do
  34. before do
  35. issue2.assignees = [user2]
  36. end
  37. let(:params) { { not: { assignee_username: [user.username, user2.username] } } }
  38. let(:expected_issuables) { [issue3, issue4] }
  39. end
  40. end
  41. it_behaves_like 'no assignee filter' do
  42. let_it_be(:user3) { create(:user) }
  43. let(:expected_issuables) { [issue4] }
  44. end
  45. it_behaves_like 'any assignee filter' do
  46. let(:expected_issuables) { [issue1, issue2, issue3] }
  47. end
  48. end
  49. context 'filtering by projects' do
  50. context 'when projects are passed in a list of ids' do
  51. let(:params) { { projects: [project1.id] } }
  52. it 'returns the issue belonging to the projects' do
  53. expect(issues).to contain_exactly(issue1)
  54. end
  55. end
  56. context 'when projects are passed in a subquery' do
  57. let(:params) { { projects: Project.id_in(project1.id) } }
  58. it 'returns the issue belonging to the projects' do
  59. expect(issues).to contain_exactly(issue1)
  60. end
  61. end
  62. end
  63. context 'filtering by group_id' do
  64. let(:params) { { group_id: group.id } }
  65. context 'when include_subgroup param not set' do
  66. it 'returns all group issues' do
  67. expect(issues).to contain_exactly(issue1)
  68. end
  69. context 'when projects outside the group are passed' do
  70. let(:params) { { group_id: group.id, projects: [project2.id] } }
  71. it 'returns no issues' do
  72. expect(issues).to be_empty
  73. end
  74. end
  75. context 'when projects of the group are passed' do
  76. let(:params) { { group_id: group.id, projects: [project1.id] } }
  77. it 'returns the issue within the group and projects' do
  78. expect(issues).to contain_exactly(issue1)
  79. end
  80. end
  81. context 'when projects of the group are passed as a subquery' do
  82. let(:params) { { group_id: group.id, projects: Project.id_in(project1.id) } }
  83. it 'returns the issue within the group and projects' do
  84. expect(issues).to contain_exactly(issue1)
  85. end
  86. end
  87. end
  88. context 'when include_subgroup param is true' do
  89. before do
  90. params[:include_subgroups] = true
  91. end
  92. it 'returns all group and subgroup issues' do
  93. expect(issues).to contain_exactly(issue1, issue4)
  94. end
  95. context 'when mixed projects are passed' do
  96. let(:params) { { group_id: group.id, projects: [project2.id, project3.id] } }
  97. it 'returns the issue within the group and projects' do
  98. expect(issues).to contain_exactly(issue4)
  99. end
  100. end
  101. end
  102. end
  103. context 'filtering by NOT group_id' do
  104. let(:params) { { not: { group_id: group.id } } }
  105. context 'when include_subgroup param not set' do
  106. it 'returns all other group issues' do
  107. expect(issues).to contain_exactly(issue2, issue3, issue4)
  108. end
  109. end
  110. context 'when include_subgroup param is true', :nested_groups do
  111. before do
  112. params[:include_subgroups] = true
  113. end
  114. it 'returns all other group and subgroup issues' do
  115. expect(issues).to contain_exactly(issue2, issue3)
  116. end
  117. end
  118. end
  119. context 'filtering by author ID' do
  120. let(:params) { { author_id: user2.id } }
  121. it 'returns issues created by that user' do
  122. expect(issues).to contain_exactly(issue3)
  123. end
  124. end
  125. context 'filtering by not author ID' do
  126. let(:params) { { not: { author_id: user2.id } } }
  127. it 'returns issues not created by that user' do
  128. expect(issues).to contain_exactly(issue1, issue2, issue4)
  129. end
  130. end
  131. context 'filtering by nonexistent author ID and issue term using CTE for search' do
  132. let(:params) do
  133. {
  134. author_id: 'does-not-exist',
  135. search: 'git',
  136. attempt_group_search_optimizations: true
  137. }
  138. end
  139. it 'returns no results' do
  140. expect(issues).to be_empty
  141. end
  142. end
  143. context 'filtering by milestone' do
  144. let(:params) { { milestone_title: milestone.title } }
  145. it 'returns issues assigned to that milestone' do
  146. expect(issues).to contain_exactly(issue1)
  147. end
  148. end
  149. context 'filtering by not milestone' do
  150. let(:params) { { not: { milestone_title: milestone.title } } }
  151. it 'returns issues not assigned to that milestone' do
  152. expect(issues).to contain_exactly(issue2, issue3, issue4)
  153. end
  154. end
  155. context 'filtering by group milestone' do
  156. let!(:group) { create(:group, :public) }
  157. let(:group_milestone) { create(:milestone, group: group) }
  158. let!(:group_member) { create(:group_member, group: group, user: user) }
  159. let(:params) { { milestone_title: group_milestone.title } }
  160. before do
  161. project2.update(namespace: group)
  162. issue2.update(milestone: group_milestone)
  163. issue3.update(milestone: group_milestone)
  164. end
  165. it 'returns issues assigned to that group milestone' do
  166. expect(issues).to contain_exactly(issue2, issue3)
  167. end
  168. context 'using NOT' do
  169. let(:params) { { not: { milestone_title: group_milestone.title } } }
  170. it 'returns issues not assigned to that group milestone' do
  171. expect(issues).to contain_exactly(issue1, issue4)
  172. end
  173. end
  174. end
  175. context 'filtering by no milestone' do
  176. let(:params) { { milestone_title: 'None' } }
  177. it 'returns issues with no milestone' do
  178. expect(issues).to contain_exactly(issue2, issue3, issue4)
  179. end
  180. it 'returns issues with no milestone (deprecated)' do
  181. params[:milestone_title] = Milestone::None.title
  182. expect(issues).to contain_exactly(issue2, issue3, issue4)
  183. end
  184. end
  185. context 'filtering by any milestone' do
  186. let(:params) { { milestone_title: 'Any' } }
  187. it 'returns issues with any assigned milestone' do
  188. expect(issues).to contain_exactly(issue1)
  189. end
  190. it 'returns issues with any assigned milestone (deprecated)' do
  191. params[:milestone_title] = Milestone::Any.title
  192. expect(issues).to contain_exactly(issue1)
  193. end
  194. end
  195. context 'filtering by upcoming milestone' do
  196. let(:params) { { milestone_title: Milestone::Upcoming.name } }
  197. let!(:group) { create(:group, :public) }
  198. let!(:group_member) { create(:group_member, group: group, user: user) }
  199. let(:project_no_upcoming_milestones) { create(:project, :public) }
  200. let(:project_next_1_1) { create(:project, :public) }
  201. let(:project_next_8_8) { create(:project, :public) }
  202. let(:project_in_group) { create(:project, :public, namespace: group) }
  203. let(:yesterday) { Date.current - 1.day }
  204. let(:tomorrow) { Date.current + 1.day }
  205. let(:two_days_from_now) { Date.current + 2.days }
  206. let(:ten_days_from_now) { Date.current + 10.days }
  207. let(:milestones) do
  208. [
  209. create(:milestone, :closed, project: project_no_upcoming_milestones),
  210. create(:milestone, project: project_next_1_1, title: '1.1', due_date: two_days_from_now),
  211. create(:milestone, project: project_next_1_1, title: '8.9', due_date: ten_days_from_now),
  212. create(:milestone, project: project_next_8_8, title: '1.2', due_date: yesterday),
  213. create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow),
  214. create(:milestone, group: group, title: '9.9', due_date: tomorrow)
  215. ]
  216. end
  217. before do
  218. @created_issues = milestones.map do |milestone|
  219. create(:issue, project: milestone.project || project_in_group, milestone: milestone, author: user, assignees: [user])
  220. end
  221. end
  222. it 'returns issues in the upcoming milestone for each project or group' do
  223. expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8', '9.9')
  224. expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now, tomorrow)
  225. end
  226. context 'using NOT' do
  227. let(:params) { { not: { milestone_title: Milestone::Upcoming.name } } }
  228. it 'returns issues not in upcoming milestones for each project or group' do
  229. target_issues = @created_issues.reject do |issue|
  230. issue.milestone&.due_date && issue.milestone.due_date > Date.current
  231. end + @created_issues.select { |issue| issue.milestone&.title == '8.9' }
  232. expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, *target_issues)
  233. end
  234. end
  235. end
  236. context 'filtering by started milestone' do
  237. let(:params) { { milestone_title: Milestone::Started.name } }
  238. let(:project_no_started_milestones) { create(:project, :public) }
  239. let(:project_started_1_and_2) { create(:project, :public) }
  240. let(:project_started_8) { create(:project, :public) }
  241. let(:yesterday) { Date.current - 1.day }
  242. let(:tomorrow) { Date.current + 1.day }
  243. let(:two_days_ago) { Date.current - 2.days }
  244. let(:three_days_ago) { Date.current - 3.days }
  245. let(:milestones) do
  246. [
  247. create(:milestone, project: project_no_started_milestones, start_date: tomorrow),
  248. create(:milestone, project: project_started_1_and_2, title: '1.0', start_date: two_days_ago),
  249. create(:milestone, project: project_started_1_and_2, title: '2.0', start_date: yesterday),
  250. create(:milestone, project: project_started_1_and_2, title: '3.0', start_date: tomorrow),
  251. create(:milestone, :closed, project: project_started_1_and_2, title: '4.0', start_date: three_days_ago),
  252. create(:milestone, :closed, project: project_started_8, title: '6.0', start_date: three_days_ago),
  253. create(:milestone, project: project_started_8, title: '7.0'),
  254. create(:milestone, project: project_started_8, title: '8.0', start_date: yesterday),
  255. create(:milestone, project: project_started_8, title: '9.0', start_date: tomorrow)
  256. ]
  257. end
  258. before do
  259. milestones.each do |milestone|
  260. create(:issue, project: milestone.project, milestone: milestone, author: user, assignees: [user])
  261. end
  262. end
  263. it 'returns issues in the started milestones for each project' do
  264. expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.0', '2.0', '8.0')
  265. expect(issues.map { |issue| issue.milestone.start_date }).to contain_exactly(two_days_ago, yesterday, yesterday)
  266. end
  267. context 'using NOT' do
  268. let(:params) { { not: { milestone_title: Milestone::Started.name } } }
  269. it 'returns issues not in the started milestones for each project' do
  270. target_issues = Issue.where.not(milestone: Milestone.started)
  271. expect(issues).to contain_exactly(issue2, issue3, issue4, *target_issues)
  272. end
  273. end
  274. end
  275. context 'filtering by label' do
  276. let(:params) { { label_name: label.title } }
  277. it 'returns issues with that label' do
  278. expect(issues).to contain_exactly(issue2)
  279. end
  280. context 'using NOT' do
  281. let(:params) { { not: { label_name: label.title } } }
  282. it 'returns issues that do not have that label' do
  283. expect(issues).to contain_exactly(issue1, issue3, issue4)
  284. end
  285. # IssuableFinder first filters using the outer params (the ones not inside the `not` key.)
  286. # Afterwards, it applies the `not` params to that resultset. This means that things inside the `not` param
  287. # do not take precedence over the outer params with the same name.
  288. context 'shadowing the same outside param' do
  289. let(:params) { { label_name: label2.title, not: { label_name: label.title } } }
  290. it 'does not take precedence over labels outside NOT' do
  291. expect(issues).to contain_exactly(issue3)
  292. end
  293. end
  294. context 'further filtering outside params' do
  295. let(:params) { { label_name: label2.title, not: { assignee_username: user2.username } } }
  296. it 'further filters on the returned resultset' do
  297. expect(issues).to be_empty
  298. end
  299. end
  300. end
  301. end
  302. context 'filtering by multiple labels' do
  303. let(:params) { { label_name: [label.title, label2.title].join(',') } }
  304. let(:label2) { create(:label, project: project2) }
  305. before do
  306. create(:label_link, label: label2, target: issue2)
  307. end
  308. it 'returns the unique issues with all those labels' do
  309. expect(issues).to contain_exactly(issue2)
  310. end
  311. context 'using NOT' do
  312. let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } }
  313. it 'returns issues that do not have any of the labels provided' do
  314. expect(issues).to contain_exactly(issue1, issue4)
  315. end
  316. end
  317. end
  318. context 'filtering by a label that includes any or none in the title' do
  319. let(:params) { { label_name: [label.title, label2.title].join(',') } }
  320. let(:label) { create(:label, title: 'any foo', project: project2) }
  321. let(:label2) { create(:label, title: 'bar none', project: project2) }
  322. before do
  323. create(:label_link, label: label2, target: issue2)
  324. end
  325. it 'returns the unique issues with all those labels' do
  326. expect(issues).to contain_exactly(issue2)
  327. end
  328. context 'using NOT' do
  329. let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } }
  330. it 'returns issues that do not have ANY ONE of the labels provided' do
  331. expect(issues).to contain_exactly(issue1, issue4)
  332. end
  333. end
  334. end
  335. context 'filtering by no label' do
  336. let(:params) { { label_name: described_class::Params::FILTER_NONE } }
  337. it 'returns issues with no labels' do
  338. expect(issues).to contain_exactly(issue1, issue4)
  339. end
  340. end
  341. context 'filtering by any label' do
  342. let(:params) { { label_name: described_class::Params::FILTER_ANY } }
  343. it 'returns issues that have one or more label' do
  344. create_list(:label_link, 2, label: create(:label, project: project2), target: issue3)
  345. expect(issues).to contain_exactly(issue2, issue3)
  346. end
  347. end
  348. context 'filtering by issue term' do
  349. let(:params) { { search: 'git' } }
  350. it 'returns issues with title and description match for search term' do
  351. expect(issues).to contain_exactly(issue1, issue2)
  352. end
  353. context 'using NOT' do
  354. let(:params) { { not: { search: 'git' } } }
  355. it 'returns issues with no title and description match for search term' do
  356. expect(issues).to contain_exactly(issue3, issue4)
  357. end
  358. end
  359. end
  360. context 'filtering by issue term in title' do
  361. let(:params) { { search: 'git', in: 'title' } }
  362. it 'returns issues with title match for search term' do
  363. expect(issues).to contain_exactly(issue1)
  364. end
  365. context 'using NOT' do
  366. let(:params) { { not: { search: 'git', in: 'title' } } }
  367. it 'returns issues with no title match for search term' do
  368. expect(issues).to contain_exactly(issue2, issue3, issue4)
  369. end
  370. end
  371. end
  372. context 'filtering by issues iids' do
  373. let(:params) { { iids: issue3.iid } }
  374. it 'returns issues with iids match' do
  375. expect(issues).to contain_exactly(issue3)
  376. end
  377. context 'using NOT' do
  378. let(:params) { { not: { iids: issue3.iid } } }
  379. it 'returns issues with no iids match' do
  380. expect(issues).to contain_exactly(issue1, issue2, issue4)
  381. end
  382. end
  383. end
  384. context 'filtering by state' do
  385. context 'with opened' do
  386. let(:params) { { state: 'opened' } }
  387. it 'returns only opened issues' do
  388. expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
  389. end
  390. end
  391. context 'with closed' do
  392. let(:params) { { state: 'closed' } }
  393. it 'returns only closed issues' do
  394. expect(issues).to contain_exactly(closed_issue)
  395. end
  396. end
  397. context 'with all' do
  398. let(:params) { { state: 'all' } }
  399. it 'returns all issues' do
  400. expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4)
  401. end
  402. end
  403. context 'with invalid state' do
  404. let(:params) { { state: 'invalid_state' } }
  405. it 'returns all issues' do
  406. expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4)
  407. end
  408. end
  409. end
  410. context 'filtering by created_at' do
  411. context 'through created_after' do
  412. let(:params) { { created_after: issue3.created_at } }
  413. it 'returns issues created on or after the given date' do
  414. expect(issues).to contain_exactly(issue3)
  415. end
  416. end
  417. context 'through created_before' do
  418. let(:params) { { created_before: issue1.created_at } }
  419. it 'returns issues created on or before the given date' do
  420. expect(issues).to contain_exactly(issue1)
  421. end
  422. end
  423. context 'through created_after and created_before' do
  424. let(:params) { { created_after: issue2.created_at, created_before: issue3.created_at } }
  425. it 'returns issues created between the given dates' do
  426. expect(issues).to contain_exactly(issue2, issue3)
  427. end
  428. end
  429. end
  430. context 'filtering by updated_at' do
  431. context 'through updated_after' do
  432. let(:params) { { updated_after: issue3.updated_at } }
  433. it 'returns issues updated on or after the given date' do
  434. expect(issues).to contain_exactly(issue3)
  435. end
  436. end
  437. context 'through updated_before' do
  438. let(:params) { { updated_before: issue1.updated_at } }
  439. it 'returns issues updated on or before the given date' do
  440. expect(issues).to contain_exactly(issue1)
  441. end
  442. end
  443. context 'through updated_after and updated_before' do
  444. let(:params) { { updated_after: issue2.updated_at, updated_before: issue3.updated_at } }
  445. it 'returns issues updated between the given dates' do
  446. expect(issues).to contain_exactly(issue2, issue3)
  447. end
  448. end
  449. end
  450. context 'filtering by closed_at' do
  451. let!(:closed_issue1) { create(:issue, project: project1, state: :closed, closed_at: 1.week.ago) }
  452. let!(:closed_issue2) { create(:issue, project: project2, state: :closed, closed_at: 1.week.from_now) }
  453. let!(:closed_issue3) { create(:issue, project: project2, state: :closed, closed_at: 2.weeks.from_now) }
  454. context 'through closed_after' do
  455. let(:params) { { state: :closed, closed_after: closed_issue3.closed_at } }
  456. it 'returns issues closed on or after the given date' do
  457. expect(issues).to contain_exactly(closed_issue3)
  458. end
  459. end
  460. context 'through closed_before' do
  461. let(:params) { { state: :closed, closed_before: closed_issue1.closed_at } }
  462. it 'returns issues closed on or before the given date' do
  463. expect(issues).to contain_exactly(closed_issue1)
  464. end
  465. end
  466. context 'through closed_after and closed_before' do
  467. let(:params) { { state: :closed, closed_after: closed_issue2.closed_at, closed_before: closed_issue3.closed_at } }
  468. it 'returns issues closed between the given dates' do
  469. expect(issues).to contain_exactly(closed_issue2, closed_issue3)
  470. end
  471. end
  472. end
  473. context 'filtering by reaction name' do
  474. context 'user searches by no reaction' do
  475. let(:params) { { my_reaction_emoji: 'None' } }
  476. it 'returns issues that the user did not react to' do
  477. expect(issues).to contain_exactly(issue2, issue4)
  478. end
  479. end
  480. context 'user searches by any reaction' do
  481. let(:params) { { my_reaction_emoji: 'Any' } }
  482. it 'returns issues that the user reacted to' do
  483. expect(issues).to contain_exactly(issue1, issue3)
  484. end
  485. end
  486. context 'user searches by "thumbsup" reaction' do
  487. let(:params) { { my_reaction_emoji: 'thumbsup' } }
  488. it 'returns issues that the user thumbsup to' do
  489. expect(issues).to contain_exactly(issue1)
  490. end
  491. context 'using NOT' do
  492. let(:params) { { not: { my_reaction_emoji: 'thumbsup' } } }
  493. it 'returns issues that the user did not thumbsup to' do
  494. expect(issues).to contain_exactly(issue2, issue3, issue4)
  495. end
  496. end
  497. end
  498. context 'user2 searches by "thumbsup" reaction' do
  499. let(:search_user) { user2 }
  500. let(:params) { { my_reaction_emoji: 'thumbsup' } }
  501. it 'returns issues that the user2 thumbsup to' do
  502. expect(issues).to contain_exactly(issue2)
  503. end
  504. context 'using NOT' do
  505. let(:params) { { not: { my_reaction_emoji: 'thumbsup' } } }
  506. it 'returns issues that the user2 thumbsup to' do
  507. expect(issues).to contain_exactly(issue3)
  508. end
  509. end
  510. end
  511. context 'user searches by "thumbsdown" reaction' do
  512. let(:params) { { my_reaction_emoji: 'thumbsdown' } }
  513. it 'returns issues that the user thumbsdown to' do
  514. expect(issues).to contain_exactly(issue3)
  515. end
  516. context 'using NOT' do
  517. let(:params) { { not: { my_reaction_emoji: 'thumbsdown' } } }
  518. it 'returns issues that the user thumbsdown to' do
  519. expect(issues).to contain_exactly(issue1, issue2, issue4)
  520. end
  521. end
  522. end
  523. end
  524. context 'filtering by confidential' do
  525. let_it_be(:confidential_issue) { create(:issue, project: project1, confidential: true) }
  526. context 'no filtering' do
  527. it 'returns all issues' do
  528. expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, confidential_issue)
  529. end
  530. end
  531. context 'user filters confidential issues' do
  532. let(:params) { { confidential: true } }
  533. it 'returns only confdential issues' do
  534. expect(issues).to contain_exactly(confidential_issue)
  535. end
  536. end
  537. context 'user filters only public issues' do
  538. let(:params) { { confidential: false } }
  539. it 'returns only confdential issues' do
  540. expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
  541. end
  542. end
  543. end
  544. context 'when the user is unauthorized' do
  545. let(:search_user) { nil }
  546. it 'returns no results' do
  547. expect(issues).to be_empty
  548. end
  549. end
  550. context 'when the user can see some, but not all, issues' do
  551. let(:search_user) { user2 }
  552. it 'returns only issues they can see' do
  553. expect(issues).to contain_exactly(issue2, issue3)
  554. end
  555. end
  556. it 'finds issues user can access due to group' do
  557. group = create(:group)
  558. project = create(:project, group: group)
  559. issue = create(:issue, project: project)
  560. group.add_user(user, :owner)
  561. expect(issues).to include(issue)
  562. end
  563. end
  564. context 'personal scope' do
  565. let(:scope) { 'assigned_to_me' }
  566. it 'returns issue assigned to the user' do
  567. expect(issues).to contain_exactly(issue1, issue2)
  568. end
  569. context 'filtering by project' do
  570. let(:params) { { project_id: project1.id } }
  571. it 'returns issues assigned to the user in that project' do
  572. expect(issues).to contain_exactly(issue1)
  573. end
  574. end
  575. end
  576. context 'when project restricts issues' do
  577. let(:scope) { nil }
  578. it "doesn't return team-only issues to non team members" do
  579. project = create(:project, :public, :issues_private)
  580. issue = create(:issue, project: project)
  581. expect(issues).not_to include(issue)
  582. end
  583. it "doesn't return issues if feature disabled" do
  584. [project1, project2, project3].each do |project|
  585. project.project_feature.update!(issues_access_level: ProjectFeature::DISABLED)
  586. end
  587. expect(issues.count).to eq 0
  588. end
  589. end
  590. context 'external authorization' do
  591. it_behaves_like 'a finder with external authorization service' do
  592. let!(:subject) { create(:issue, project: project) }
  593. let(:project_params) { { project_id: project.id } }
  594. end
  595. end
  596. end
  597. describe '#row_count', :request_store do
  598. let_it_be(:admin) { create(:admin) }
  599. it 'returns the number of rows for the default state' do
  600. finder = described_class.new(admin)
  601. expect(finder.row_count).to eq(4)
  602. end
  603. it 'returns the number of rows for a given state' do
  604. finder = described_class.new(admin, state: 'closed')
  605. expect(finder.row_count).to be_zero
  606. end
  607. end
  608. describe '#with_confidentiality_access_check' do
  609. let(:guest) { create(:user) }
  610. let_it_be(:authorized_user) { create(:user) }
  611. let_it_be(:project) { create(:project, namespace: authorized_user.namespace) }
  612. let_it_be(:public_issue) { create(:issue, project: project) }
  613. let_it_be(:confidential_issue) { create(:issue, project: project, confidential: true) }
  614. context 'when no project filter is given' do
  615. let(:params) { {} }
  616. context 'for an anonymous user' do
  617. subject { described_class.new(nil, params).with_confidentiality_access_check }
  618. it 'returns only public issues' do
  619. expect(subject).to include(public_issue)
  620. expect(subject).not_to include(confidential_issue)
  621. end
  622. end
  623. context 'for a user without project membership' do
  624. subject { described_class.new(user, params).with_confidentiality_access_check }
  625. it 'returns only public issues' do
  626. expect(subject).to include(public_issue)
  627. expect(subject).not_to include(confidential_issue)
  628. end
  629. end
  630. context 'for a guest user' do
  631. subject { described_class.new(guest, params).with_confidentiality_access_check }
  632. before do
  633. project.add_guest(guest)
  634. end
  635. it 'returns only public issues' do
  636. expect(subject).to include(public_issue)
  637. expect(subject).not_to include(confidential_issue)
  638. end
  639. end
  640. context 'for a project member with access to view confidential issues' do
  641. subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
  642. it 'returns all issues' do
  643. expect(subject).to include(public_issue, confidential_issue)
  644. end
  645. end
  646. context 'for an admin' do
  647. let(:admin_user) { create(:user, :admin) }
  648. subject { described_class.new(admin_user, params).with_confidentiality_access_check }
  649. it 'returns all issues' do
  650. expect(subject).to include(public_issue, confidential_issue)
  651. end
  652. end
  653. end
  654. context 'when searching within a specific project' do
  655. let(:params) { { project_id: project.id } }
  656. context 'for an anonymous user' do
  657. subject { described_class.new(nil, params).with_confidentiality_access_check }
  658. it 'returns only public issues' do
  659. expect(subject).to include(public_issue)
  660. expect(subject).not_to include(confidential_issue)
  661. end
  662. it 'does not filter by confidentiality' do
  663. expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
  664. subject
  665. end
  666. end
  667. context 'for a user without project membership' do
  668. subject { described_class.new(user, params).with_confidentiality_access_check }
  669. it 'returns only public issues' do
  670. expect(subject).to include(public_issue)
  671. expect(subject).not_to include(confidential_issue)
  672. end
  673. it 'filters by confidentiality' do
  674. expect(subject.to_sql).to match("issues.confidential")
  675. end
  676. end
  677. context 'for a guest user' do
  678. subject { described_class.new(guest, params).with_confidentiality_access_check }
  679. before do
  680. project.add_guest(guest)
  681. end
  682. it 'returns only public issues' do
  683. expect(subject).to include(public_issue)
  684. expect(subject).not_to include(confidential_issue)
  685. end
  686. it 'filters by confidentiality' do
  687. expect(subject.to_sql).to match("issues.confidential")
  688. end
  689. end
  690. context 'for a project member with access to view confidential issues' do
  691. subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
  692. it 'returns all issues' do
  693. expect(subject).to include(public_issue, confidential_issue)
  694. end
  695. it 'does not filter by confidentiality' do
  696. expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
  697. subject
  698. end
  699. end
  700. context 'for an admin' do
  701. let(:admin_user) { create(:user, :admin) }
  702. subject { described_class.new(admin_user, params).with_confidentiality_access_check }
  703. it 'returns all issues' do
  704. expect(subject).to include(public_issue, confidential_issue)
  705. end
  706. it 'does not filter by confidentiality' do
  707. expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
  708. subject
  709. end
  710. end
  711. end
  712. end
  713. describe '#use_cte_for_search?' do
  714. let(:finder) { described_class.new(nil, params) }
  715. before do
  716. stub_feature_flags(attempt_group_search_optimizations: true)
  717. end
  718. context 'when there is no search param' do
  719. let(:params) { { attempt_group_search_optimizations: true } }
  720. it 'returns false' do
  721. expect(finder.use_cte_for_search?).to be_falsey
  722. end
  723. end
  724. context 'when the force_cte param is falsey' do
  725. let(:params) { { search: 'foo' } }
  726. it 'returns false' do
  727. expect(finder.use_cte_for_search?).to be_falsey
  728. end
  729. end
  730. context 'when the attempt_group_search_optimizations flag is disabled' do
  731. let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
  732. before do
  733. stub_feature_flags(attempt_group_search_optimizations: false)
  734. end
  735. it 'returns false' do
  736. expect(finder.use_cte_for_search?).to be_falsey
  737. end
  738. end
  739. context 'when attempt_group_search_optimizations is unset and attempt_project_search_optimizations is set' do
  740. let(:params) { { search: 'foo', attempt_project_search_optimizations: true } }
  741. context 'and the corresponding feature flag is disabled' do
  742. before do
  743. stub_feature_flags(attempt_project_search_optimizations: false)
  744. end
  745. it 'returns false' do
  746. expect(finder.use_cte_for_search?).to be_falsey
  747. end
  748. end
  749. context 'and the corresponding feature flag is enabled' do
  750. before do
  751. stub_feature_flags(attempt_project_search_optimizations: true)
  752. end
  753. it 'returns true' do
  754. expect(finder.use_cte_for_search?).to be_truthy
  755. end
  756. end
  757. end
  758. context 'when all conditions are met' do
  759. let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
  760. it 'returns true' do
  761. expect(finder.use_cte_for_search?).to be_truthy
  762. end
  763. end
  764. end
  765. end