PageRenderTime 144ms CodeModel.GetById 14ms app.highlight 124ms RepoModel.GetById 1ms app.codeStats 0ms

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