issues_spec.rb 71.1 KB
Newer Older
Nihad Abbasov's avatar
Nihad Abbasov committed
1 2
require 'spec_helper'

3
describe API::Issues do
4 5
  set(:user) { create(:user) }
  set(:project) do
6
    create(:project, :public, creator_id: user.id, namespace: user.namespace)
7 8
  end

9
  let(:user2)       { create(:user) }
10
  let(:non_member)  { create(:user) }
11 12 13
  set(:guest)       { create(:user) }
  set(:author)      { create(:author) }
  set(:assignee)    { create(:assignee) }
14
  let(:admin)       { create(:user, :admin) }
15 16
  let(:issue_title)       { 'foo' }
  let(:issue_description) { 'closed' }
17 18 19
  let!(:closed_issue) do
    create :closed_issue,
           author: user,
20
           assignees: [user],
21 22
           project: project,
           state: :closed,
Sean McGivern's avatar
Sean McGivern committed
23
           milestone: milestone,
24
           created_at: generate(:past_time),
25 26
           updated_at: 3.hours.ago,
           closed_at: 1.hour.ago
27
  end
28 29 30 31 32
  let!(:confidential_issue) do
    create :issue,
           :confidential,
           project: project,
           author: author,
33
           assignees: [assignee],
34
           created_at: generate(:past_time),
Sean McGivern's avatar
Sean McGivern committed
35
           updated_at: 2.hours.ago
36
  end
37 38 39
  let!(:issue) do
    create :issue,
           author: user,
40
           assignees: [user],
41
           project: project,
Sean McGivern's avatar
Sean McGivern committed
42
           milestone: milestone,
43
           created_at: generate(:past_time),
44 45 46
           updated_at: 1.hour.ago,
           title: issue_title,
           description: issue_description
47
  end
48
  set(:label) do
49 50
    create(:label, title: 'label', color: '#FFAABB', project: project)
  end
51
  let!(:label_link) { create(:label_link, label: label, target: issue) }
52 53
  set(:milestone) { create(:milestone, title: '1.0.0', project: project) }
  set(:empty_milestone) do
54 55
    create(:milestone, title: '2.0.0', project: project)
  end
56
  let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
57

58 59
  let(:no_milestone_title) { "None" }
  let(:any_milestone_title) { "Any" }
60

61
  before(:all) do
62 63
    project.add_reporter(user)
    project.add_guest(guest)
64
  end
Nihad Abbasov's avatar
Nihad Abbasov committed
65 66

  describe "GET /issues" do
67
    context "when unauthenticated" do
68
      it "returns an array of all issues" do
69
        get api("/issues"), params: { scope: 'all' }
70 71 72 73 74 75

        expect(response).to have_http_status(200)
        expect(json_response).to be_an Array
      end

      it "returns authentication error without any scope" do
76
        get api("/issues")
77

78 79 80 81
        expect(response).to have_http_status(401)
      end

      it "returns authentication error when scope is assigned-to-me" do
82
        get api("/issues"), params: { scope: 'assigned-to-me' }
83 84 85 86 87

        expect(response).to have_http_status(401)
      end

      it "returns authentication error when scope is created-by-me" do
88
        get api("/issues"), params: { scope: 'created-by-me' }
89 90

        expect(response).to have_http_status(401)
91
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
92
    end
93

94
    context "when authenticated" do
95
      it "returns an array of issues" do
Robert Speicher's avatar
Robert Speicher committed
96
        get api("/issues", user)
97

98
        expect_paginated_array_response([issue.id, closed_issue.id])
99
        expect(json_response.first['title']).to eq(issue.title)
100
        expect(json_response.last).to have_key('web_url')
Nihad Abbasov's avatar
Nihad Abbasov committed
101
      end
102

103
      it 'returns an array of closed issues' do
104
        get api('/issues', user), params: { state: :closed }
105

106
        expect_paginated_array_response(closed_issue.id)
107 108
      end

109
      it 'returns an array of opened issues' do
110
        get api('/issues', user), params: { state: :opened }
111

112
        expect_paginated_array_response(issue.id)
113 114
      end

115
      it 'returns an array of all issues' do
116
        get api('/issues', user), params: { state: :all }
117

118
        expect_paginated_array_response([issue.id, closed_issue.id])
119
      end
120

121 122 123
      it 'returns issues assigned to me' do
        issue2 = create(:issue, assignees: [user2], project: project)

124
        get api('/issues', user2), params: { scope: 'assigned_to_me' }
125

126
        expect_paginated_array_response(issue2.id)
127 128 129 130 131
      end

      it 'returns issues assigned to me (kebab-case)' do
        issue2 = create(:issue, assignees: [user2], project: project)

132
        get api('/issues', user2), params: { scope: 'assigned-to-me' }
133

134
        expect_paginated_array_response(issue2.id)
135 136
      end

137 138 139
      it 'returns issues authored by the given author id' do
        issue2 = create(:issue, author: user2, project: project)

140
        get api('/issues', user), params: { author_id: user2.id, scope: 'all' }
141

142
        expect_paginated_array_response(issue2.id)
143 144 145 146 147
      end

      it 'returns issues assigned to the given assignee id' do
        issue2 = create(:issue, assignees: [user2], project: project)

148
        get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' }
149

150
        expect_paginated_array_response(issue2.id)
151 152 153 154 155
      end

      it 'returns issues authored by the given author id and assigned to the given assignee id' do
        issue2 = create(:issue, author: user2, assignees: [user2], project: project)

156
        get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' }
157

158
        expect_paginated_array_response(issue2.id)
159 160
      end

161 162 163
      it 'returns issues with no assignee' do
        issue2 = create(:issue, author: user2, project: project)

164
        get api('/issues', user), params: { assignee_id: 0, scope: 'all' }
165

166
        expect_paginated_array_response(issue2.id)
167 168
      end

169 170 171
      it 'returns issues with no assignee' do
        issue2 = create(:issue, author: user2, project: project)

172
        get api('/issues', user), params: { assignee_id: 'None', scope: 'all' }
173

174
        expect_paginated_array_response(issue2.id)
175 176 177
      end

      it 'returns issues with any assignee' do
178 179 180
        # This issue without assignee should not be returned
        create(:issue, author: user2, project: project)

181
        get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' }
182

183
        expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
184 185
      end

186 187 188 189 190 191 192 193 194 195 196 197
      it 'returns only confidential issues' do
        get api('/issues', user), params: { confidential: true, scope: 'all' }

        expect_paginated_array_response(confidential_issue.id)
      end

      it 'returns only public issues' do
        get api('/issues', user), params: { confidential: false }

        expect_paginated_array_response([issue.id, closed_issue.id])
      end

Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
198
      it 'returns issues reacted by the authenticated user' do
199
        issue2 = create(:issue, project: project, author: user, assignees: [user])
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
200
        create(:award_emoji, awardable: issue2, user: user2, name: 'star')
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
201
        create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup')
202

203
        get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' }
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
204

205
        expect_paginated_array_response([issue2.id, issue.id])
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
206 207 208 209 210 211
      end

      it 'returns issues not reacted by the authenticated user' do
        issue2 = create(:issue, project: project, author: user, assignees: [user])
        create(:award_emoji, awardable: issue2, user: user2, name: 'star')

212
        get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' }
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
213

214
        expect_paginated_array_response([issue.id, closed_issue.id])
215 216
      end

217
      it 'returns issues matching given search string for title' do
218
        get api("/issues", user), params: { search: issue.title }
219

220
        expect_paginated_array_response(issue.id)
221 222
      end

223 224 225 226 227 228 229 230 231 232 233 234
      it 'returns issues matching given search string for title and scoped in title' do
        get api("/issues", user), params: { search: issue.title, in: 'title' }

        expect_paginated_array_response(issue.id)
      end

      it 'returns an empty array if no issue matches given search string for title and scoped in description' do
        get api("/issues", user), params: { search: issue.title, in: 'description' }

        expect_paginated_array_response([])
      end

235
      it 'returns issues matching given search string for description' do
236
        get api("/issues", user), params: { search: issue.description }
237

238
        expect_paginated_array_response(issue.id)
239 240
      end

241 242 243 244 245 246
      context 'filtering before a specific date' do
        let!(:issue2) { create(:issue, project: project, author: user, created_at: Date.new(2000, 1, 1), updated_at: Date.new(2000, 1, 1)) }

        it 'returns issues created before a specific date' do
          get api('/issues?created_before=2000-01-02T00:00:00.060Z', user)

247
          expect_paginated_array_response(issue2.id)
248 249 250 251 252
        end

        it 'returns issues updated before a specific date' do
          get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user)

253
          expect_paginated_array_response(issue2.id)
254 255 256 257 258 259 260 261 262
        end
      end

      context 'filtering after a specific date' do
        let!(:issue2) { create(:issue, project: project, author: user, created_at: 1.week.from_now, updated_at: 1.week.from_now) }

        it 'returns issues created after a specific date' do
          get api("/issues?created_after=#{issue2.created_at}", user)

263
          expect_paginated_array_response(issue2.id)
264 265 266 267 268
        end

        it 'returns issues updated after a specific date' do
          get api("/issues?updated_after=#{issue2.updated_at}", user)

269
          expect_paginated_array_response(issue2.id)
270 271 272
        end
      end

273
      it 'returns an array of labeled issues' do
274
        get api("/issues", user), params: { labels: label.title }
275

276 277
        expect_paginated_array_response(issue.id)
        expect(json_response.first['labels']).to eq([label.title])
278 279
      end

280 281 282 283 284 285 286
      it 'returns an array of labeled issues when all labels matches' do
        label_b = create(:label, title: 'foo', project: project)
        label_c = create(:label, title: 'bar', project: project)

        create(:label_link, label: label_b, target: issue)
        create(:label_link, label: label_c, target: issue)

287
        get api("/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" }
288

289
        expect_paginated_array_response(issue.id)
290
        expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
291 292
      end

293
      it 'returns an empty array if no issue matches labels' do
294
        get api('/issues', user), params: { labels: 'foo,bar' }
295

296
        expect_paginated_array_response([])
297 298
      end

299
      it 'returns an array of labeled issues matching given state' do
300
        get api("/issues", user), params: { labels: label.title, state: :opened }
301

302
        expect_paginated_array_response(issue.id)
303 304
        expect(json_response.first['labels']).to eq([label.title])
        expect(json_response.first['state']).to eq('opened')
305 306
      end

307
      it 'returns an empty array if no issue matches labels and state filters' do
308
        get api("/issues", user), params: { labels: label.title, state: :closed }
309

310
        expect_paginated_array_response([])
311 312 313
      end

      it 'returns an array of issues with any label' do
314
        get api("/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
315

316
        expect_paginated_array_response(issue.id)
317 318
      end

319
      it 'returns an array of issues with no label' do
320
        get api("/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
321

322
        expect_paginated_array_response(closed_issue.id)
323 324 325
      end

      it 'returns an array of issues with no label when using the legacy No+Label filter' do
326
        get api("/issues", user), params: { labels: "No Label" }
327

328
        expect_paginated_array_response(closed_issue.id)
329
      end
Sean McGivern's avatar
Sean McGivern committed
330

331 332 333
      it 'returns an empty array if no issue matches milestone' do
        get api("/issues?milestone=#{empty_milestone.title}", user)

334
        expect_paginated_array_response([])
335 336 337 338 339
      end

      it 'returns an empty array if milestone does not exist' do
        get api("/issues?milestone=foo", user)

340
        expect_paginated_array_response([])
341 342 343 344 345
      end

      it 'returns an array of issues in given milestone' do
        get api("/issues?milestone=#{milestone.title}", user)

346
        expect_paginated_array_response([issue.id, closed_issue.id])
347 348 349 350 351 352
      end

      it 'returns an array of issues matching state in milestone' do
        get api("/issues?milestone=#{milestone.title}"\
                '&state=closed', user)

353
        expect_paginated_array_response(closed_issue.id)
354 355 356
      end

      it 'returns an array of issues with no milestone' do
357
        get api("/issues?milestone=#{no_milestone_title}", author)
358

359
        expect_paginated_array_response(confidential_issue.id)
360 361
      end

362
      it 'returns an array of issues found by iids' do
363
        get api('/issues', user), params: { iids: [closed_issue.iid] }
364

365
        expect_paginated_array_response(closed_issue.id)
366 367 368
      end

      it 'returns an empty array if iid does not exist' do
369
        get api("/issues", user), params: { iids: [99999] }
370

371
        expect_paginated_array_response([])
372 373
      end

374 375 376
      context 'without sort params' do
        it 'sorts by created_at descending by default' do
          get api('/issues', user)
Sean McGivern's avatar
Sean McGivern committed
377

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
          expect_paginated_array_response([issue.id, closed_issue.id])
        end

        context 'with 2 issues with same created_at' do
          let!(:closed_issue2) do
            create :closed_issue,
                   author: user,
                   assignees: [user],
                   project: project,
                   milestone: milestone,
                   created_at: closed_issue.created_at,
                   updated_at: 1.hour.ago,
                   title: issue_title,
                   description: issue_description
          end

          it 'page breaks first page correctly' do
            get api('/issues?per_page=2', user)

            expect_paginated_array_response([issue.id, closed_issue2.id])
          end

          it 'page breaks second page correctly' do
            get api('/issues?per_page=2&page=2', user)

            expect_paginated_array_response([closed_issue.id])
          end
        end
Sean McGivern's avatar
Sean McGivern committed
406 407 408 409 410
      end

      it 'sorts ascending when requested' do
        get api('/issues?sort=asc', user)

411
        expect_paginated_array_response([closed_issue.id, issue.id])
Sean McGivern's avatar
Sean McGivern committed
412 413 414 415 416
      end

      it 'sorts by updated_at descending when requested' do
        get api('/issues?order_by=updated_at', user)

417
        issue.touch(:updated_at)
418

419
        expect_paginated_array_response([issue.id, closed_issue.id])
Sean McGivern's avatar
Sean McGivern committed
420 421 422 423 424
      end

      it 'sorts by updated_at ascending when requested' do
        get api('/issues?order_by=updated_at&sort=asc', user)

425
        issue.touch(:updated_at)
426

427
        expect_paginated_array_response([closed_issue.id, issue.id])
Sean McGivern's avatar
Sean McGivern committed
428
      end
429 430 431 432

      it 'matches V4 response schema' do
        get api('/issues', user)

433
        expect(response).to have_gitlab_http_status(200)
434 435
        expect(response).to match_response_schema('public_api/v4/issues')
      end
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453

      it 'returns a related merge request count of 0 if there are no related merge requests' do
        get api('/issues', user)

        expect(response).to have_gitlab_http_status(200)
        expect(response).to match_response_schema('public_api/v4/issues')
        expect(json_response.first).to include('merge_requests_count' => 0)
      end

      it 'returns a related merge request count > 0 if there are related merge requests' do
        create(:merge_requests_closing_issues, issue: issue)

        get api('/issues', user)

        expect(response).to have_gitlab_http_status(200)
        expect(response).to match_response_schema('public_api/v4/issues')
        expect(json_response.first).to include('merge_requests_count' => 1)
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
454 455 456
    end
  end

457 458
  describe "GET /groups/:id/issues" do
    let!(:group)            { create(:group) }
459
    let!(:group_project)    { create(:project, :public, creator_id: user.id, namespace: group) }
460 461 462
    let!(:group_closed_issue) do
      create :closed_issue,
             author: user,
463
             assignees: [user],
464 465
             project: group_project,
             state: :closed,
Sean McGivern's avatar
Sean McGivern committed
466
             milestone: group_milestone,
467 468
             updated_at: 3.hours.ago,
             created_at: 1.day.ago
469 470 471 472 473 474
    end
    let!(:group_confidential_issue) do
      create :issue,
             :confidential,
             project: group_project,
             author: author,
475
             assignees: [assignee],
476 477
             updated_at: 2.hours.ago,
             created_at: 2.days.ago
478 479 480 481
    end
    let!(:group_issue) do
      create :issue,
             author: user,
482
             assignees: [user],
483
             project: group_project,
Sean McGivern's avatar
Sean McGivern committed
484
             milestone: group_milestone,
485 486
             updated_at: 1.hour.ago,
             title: issue_title,
487 488
             description: issue_description,
             created_at: 5.days.ago
489 490 491 492 493 494 495 496 497 498 499 500 501
    end
    let!(:group_label) do
      create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
    end
    let!(:group_label_link) { create(:label_link, label: group_label, target: group_issue) }
    let!(:group_milestone) { create(:milestone, title: '3.0.0', project: group_project) }
    let!(:group_empty_milestone) do
      create(:milestone, title: '4.0.0', project: group_project)
    end
    let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) }

    let(:base_url) { "/groups/#{group.id}/issues" }

502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
    context 'when group has subgroups', :nested_groups do
      let(:subgroup_1) { create(:group, parent: group) }
      let(:subgroup_2) { create(:group, parent: subgroup_1) }

      let(:subgroup_1_project) { create(:project, namespace: subgroup_1) }
      let(:subgroup_2_project) { create(:project, namespace: subgroup_2) }

      let!(:issue_1) { create(:issue, project: subgroup_1_project) }
      let!(:issue_2) { create(:issue, project: subgroup_2_project) }

      before do
        group.add_developer(user)
      end

      it 'also returns subgroups projects issues' do
        get api(base_url, user)

519
        expect_paginated_array_response([issue_2.id, issue_1.id, group_closed_issue.id, group_confidential_issue.id, group_issue.id])
520 521 522
      end
    end

523 524 525
    context 'when user is unauthenticated' do
      it 'lists all issues in public projects' do
        get api(base_url)
526

527
        expect_paginated_array_response([group_closed_issue.id, group_issue.id])
528
      end
529 530
    end

531 532 533 534
    context 'when user is a group member' do
      before do
        group_project.add_reporter(user)
      end
535

536 537
      it 'returns all group issues (including opened and closed)' do
        get api(base_url, admin)
538

539
        expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
540
      end
541

542
      it 'returns group issues without confidential issues for non project members' do
543
        get api(base_url, non_member), params: { state: :opened }
544

545
        expect_paginated_array_response(group_issue.id)
546
      end
547

548
      it 'returns group confidential issues for author' do
549
        get api(base_url, author), params: { state: :opened }
550

551
        expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
552
      end
553

554
      it 'returns group confidential issues for assignee' do
555
        get api(base_url, assignee), params: { state: :opened }
556

557
        expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
558
      end
559

560
      it 'returns group issues with confidential issues for project members' do
561
        get api(base_url, user), params: { state: :opened }
562

563
        expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
564
      end
565

566
      it 'returns group confidential issues for admin' do
567
        get api(base_url, admin), params: { state: :opened }
568

569
        expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
570
      end
571

572 573 574 575 576 577 578 579 580 581 582 583
      it 'returns only confidential issues' do
        get api(base_url, user), params: { confidential: true }

        expect_paginated_array_response(group_confidential_issue.id)
      end

      it 'returns only public issues' do
        get api(base_url, user), params: { confidential: false }

        expect_paginated_array_response([group_closed_issue.id, group_issue.id])
      end

584
      it 'returns an array of labeled group issues' do
585
        get api(base_url, user), params: { labels: group_label.title }
586

587
        expect_paginated_array_response(group_issue.id)
588 589
        expect(json_response.first['labels']).to eq([group_label.title])
      end
590

591
      it 'returns an array of labeled group issues where all labels match' do
592
        get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" }
593

594
        expect_paginated_array_response([])
595
      end
596

597
      it 'returns issues matching given search string for title' do
598
        get api(base_url, user), params: { search: group_issue.title }
599

600
        expect_paginated_array_response(group_issue.id)
601
      end
602

603
      it 'returns issues matching given search string for description' do
604
        get api(base_url, user), params: { search: group_issue.description }
605

606
        expect_paginated_array_response(group_issue.id)
607
      end
608

609 610 611
      it 'returns an array of labeled issues when all labels matches' do
        label_b = create(:label, title: 'foo', project: group_project)
        label_c = create(:label, title: 'bar', project: group_project)
612

613 614
        create(:label_link, label: label_b, target: group_issue)
        create(:label_link, label: label_c, target: group_issue)
615

616
        get api(base_url, user), params: { labels: "#{group_label.title},#{label_b.title},#{label_c.title}" }
617

618
        expect_paginated_array_response(group_issue.id)
619 620
        expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
      end
621

622
      it 'returns an array of issues found by iids' do
623
        get api(base_url, user), params: { iids: [group_issue.iid] }
624

625
        expect_paginated_array_response(group_issue.id)
626 627
        expect(json_response.first['id']).to eq(group_issue.id)
      end
628

629
      it 'returns an empty array if iid does not exist' do
630
        get api(base_url, user), params: { iids: [99999] }
631

632
        expect_paginated_array_response([])
633
      end
634

635
      it 'returns an empty array if no group issue matches labels' do
636
        get api(base_url, user), params: { labels: 'foo,bar' }
637

638
        expect_paginated_array_response([])
639
      end
640

641
      it 'returns an array of group issues with any label' do
642
        get api(base_url, user), params: { labels: IssuesFinder::FILTER_ANY }
643

644
        expect_paginated_array_response(group_issue.id)
645 646 647 648
        expect(json_response.first['id']).to eq(group_issue.id)
      end

      it 'returns an array of group issues with no label' do
649
        get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE }
650

651
        expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id])
652 653
      end

654
      it 'returns an empty array if no issue matches milestone' do
655
        get api(base_url, user), params: { milestone: group_empty_milestone.title }
656

657
        expect_paginated_array_response([])
658
      end
659

660
      it 'returns an empty array if milestone does not exist' do
661
        get api(base_url, user), params: { milestone: 'foo' }
662

663
        expect_paginated_array_response([])
664
      end
665

666
      it 'returns an array of issues in given milestone' do
667
        get api(base_url, user), params: { state: :opened, milestone: group_milestone.title }
Sean McGivern's avatar
Sean McGivern committed
668

669
        expect_paginated_array_response(group_issue.id)
670
      end
671

672
      it 'returns an array of issues matching state in milestone' do
673
        get api(base_url, user), params: { milestone: group_milestone.title, state: :closed }
674

675
        expect_paginated_array_response(group_closed_issue.id)
676
      end
677

678
      it 'returns an array of issues with no milestone' do
679
        get api(base_url, user), params: { milestone: no_milestone_title }
Sean McGivern's avatar
Sean McGivern committed
680

681
        expect(response).to have_gitlab_http_status(200)
682

683
        expect_paginated_array_response(group_confidential_issue.id)
684
      end
Sean McGivern's avatar
Sean McGivern committed
685

686 687 688
      context 'without sort params' do
        it 'sorts by created_at descending by default' do
          get api(base_url, user)
Sean McGivern's avatar
Sean McGivern committed
689

690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
          expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
        end

        context 'with 2 issues with same created_at' do
          let!(:group_issue2) do
            create :issue,
                   author: user,
                   assignees: [user],
                   project: group_project,
                   milestone: group_milestone,
                   updated_at: 1.hour.ago,
                   title: issue_title,
                   description: issue_description,
                   created_at: group_issue.created_at
          end

          it 'page breaks first page correctly' do
            get api("#{base_url}?per_page=3", user)

            expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue2.id])
          end

          it 'page breaks second page correctly' do
            get api("#{base_url}?per_page=3&page=2", user)

            expect_paginated_array_response([group_issue.id])
          end
        end
718
      end
Sean McGivern's avatar
Sean McGivern committed
719

720 721
      it 'sorts ascending when requested' do
        get api("#{base_url}?sort=asc", user)
Sean McGivern's avatar
Sean McGivern committed
722

723
        expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
724
      end
Sean McGivern's avatar
Sean McGivern committed
725

726 727
      it 'sorts by updated_at descending when requested' do
        get api("#{base_url}?order_by=updated_at", user)
Sean McGivern's avatar
Sean McGivern committed
728

729
        group_issue.touch(:updated_at)
730

731
        expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
732 733 734
      end

      it 'sorts by updated_at ascending when requested' do
735
        get api(base_url, user), params: { order_by: :updated_at, sort: :asc }
736

737
        expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
738
      end
Sean McGivern's avatar
Sean McGivern committed
739
    end
740 741
  end

Nihad Abbasov's avatar
Nihad Abbasov committed
742
  describe "GET /projects/:id/issues" do
743 744
    let(:base_url) { "/projects/#{project.id}" }

745 746 747 748
    context 'when unauthenticated' do
      it 'returns public project issues' do
        get api("/projects/#{project.id}/issues")

749
        expect_paginated_array_response([issue.id, closed_issue.id])
750 751 752
      end
    end

753
    it 'avoids N+1 queries' do
754 755 756
      get api("/projects/#{project.id}/issues", user)

      control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
757 758 759
        get api("/projects/#{project.id}/issues", user)
      end.count

760
      create_list(:issue, 3, project: project)
761 762 763

      expect do
        get api("/projects/#{project.id}/issues", user)
764
      end.not_to exceed_all_query_limit(control_count)
765 766
    end

767 768 769
    it 'returns 404 when project does not exist' do
      get api('/projects/1000/issues', non_member)

770
      expect(response).to have_gitlab_http_status(404)
771 772
    end

773
    it "returns 404 on private projects for other users" do
774
      private_project = create(:project, :private)
775 776 777 778
      create(:issue, project: private_project)

      get api("/projects/#{private_project.id}/issues", non_member)

779
      expect(response).to have_gitlab_http_status(404)
780 781 782
    end

    it 'returns no issues when user has access to project but not issues' do
783
      restricted_project = create(:project, :public, :issues_private)
784 785 786 787
      create(:issue, project: restricted_project)

      get api("/projects/#{restricted_project.id}/issues", non_member)

788
      expect_paginated_array_response([])
789 790
    end

791
    it 'returns project issues without confidential issues for non project members' do
792
      get api("#{base_url}/issues", non_member)
793

794
      expect_paginated_array_response([issue.id, closed_issue.id])
795 796
    end

797
    it 'returns project issues without confidential issues for project members with guest role' do
798
      get api("#{base_url}/issues", guest)
799

800
      expect_paginated_array_response([issue.id, closed_issue.id])
801 802
    end

803
    it 'returns project confidential issues for author' do
804
      get api("#{base_url}/issues", author)
805

806
      expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
807 808
    end

809 810 811 812 813 814 815 816 817 818 819 820
    it 'returns only confidential issues' do
      get api("#{base_url}/issues", author), params: { confidential: true }

      expect_paginated_array_response(confidential_issue.id)
    end

    it 'returns only public issues' do
      get api("#{base_url}/issues", author), params: { confidential: false }

      expect_paginated_array_response([issue.id, closed_issue.id])
    end

821
    it 'returns project confidential issues for assignee' do
822
      get api("#{base_url}/issues", assignee)
823

824
      expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
825 826
    end

827
    it 'returns project issues with confidential issues for project members' do
828
      get api("#{base_url}/issues", user)
829

830
      expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
831 832
    end

833
    it 'returns project confidential issues for admin' do
834
      get api("#{base_url}/issues", admin)
835

836
      expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
Nihad Abbasov's avatar
Nihad Abbasov committed
837
    end
838

839
    it 'returns an array of labeled project issues' do
840
      get api("#{base_url}/issues", user), params: { labels: label.title }
841

842
      expect_paginated_array_response(issue.id)
843 844
    end

845 846 847 848 849 850 851
    it 'returns an array of labeled issues when all labels matches' do
      label_b = create(:label, title: 'foo', project: project)
      label_c = create(:label, title: 'bar', project: project)

      create(:label_link, label: label_b, target: issue)
      create(:label_link, label: label_c, target: issue)

852
      get api("#{base_url}/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" }
853

854
      expect_paginated_array_response(issue.id)
855 856
    end

857 858 859
    it 'returns issues matching given search string for title' do
      get api("#{base_url}/issues?search=#{issue.title}", user)

860
      expect_paginated_array_response(issue.id)
861 862 863 864 865
    end

    it 'returns issues matching given search string for description' do
      get api("#{base_url}/issues?search=#{issue.description}", user)

866
      expect_paginated_array_response(issue.id)
867 868
    end

869
    it 'returns an array of issues found by iids' do
870
      get api("#{base_url}/issues", user), params: { iids: [issue.iid] }
871

872
      expect_paginated_array_response(issue.id)
873 874 875
    end

    it 'returns an empty array if iid does not exist' do
876
      get api("#{base_url}/issues", user), params: { iids: [99999] }
877

878
      expect_paginated_array_response([])
879 880
    end

881 882 883
    it 'returns an empty array if not all labels matches' do
      get api("#{base_url}/issues?labels=#{label.title},foo", user)

884
      expect_paginated_array_response([])
885 886
    end

887
    it 'returns an array of project issues with any label' do
888
      get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
889

890
      expect_paginated_array_response(issue.id)
891 892 893
    end

    it 'returns an array of project issues with no label' do
894
      get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
895

896
      expect_paginated_array_response([confidential_issue.id, closed_issue.id])
897 898
    end

899
    it 'returns an empty array if no project issue matches labels' do
900
      get api("#{base_url}/issues", user), params: { labels: 'foo,bar' }
901

902
      expect_paginated_array_response([])
903 904
    end

905
    it 'returns an empty array if no issue matches milestone' do
906
      get api("#{base_url}/issues", user), params: { milestone: empty_milestone.title }
907

908
      expect_paginated_array_response([])
909
    end
910

911
    it 'returns an empty array if milestone does not exist' do
912
      get api("#{base_url}/issues", user), params: { milestone: :foo }
913

914
      expect_paginated_array_response([])
915 916
    end

917
    it 'returns an array of issues in given milestone' do
918
      get api("#{base_url}/issues", user), params: { milestone: milestone.title }
919

920
      expect_paginated_array_response([issue.id, closed_issue.id])
921 922
    end

923
    it 'returns an array of issues matching state in milestone' do
924
      get api("#{base_url}/issues", user), params: { milestone: milestone.title, state: :closed }
925

926
      expect_paginated_array_response(closed_issue.id)
927
    end
Sean McGivern's avatar
Sean McGivern committed
928

929
    it 'returns an array of issues with no milestone' do
930
      get api("#{base_url}/issues", user), params: { milestone: no_milestone_title }
931

932
      expect_paginated_array_response(confidential_issue.id)
933 934
    end

935
    it 'returns an array of issues with any milestone' do
936
      get api("#{base_url}/issues", user), params: { milestone: any_milestone_title }
937

938
      expect_paginated_array_response([issue.id, closed_issue.id])
939 940
    end

941 942 943
    context 'without sort params' do
      it 'sorts by created_at descending by default' do
        get api("#{base_url}/issues", user)
Sean McGivern's avatar
Sean McGivern committed
944

945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972
        expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
      end

      context 'with 2 issues with same created_at' do
        let!(:closed_issue2) do
          create :closed_issue,
                 author: user,
                 assignees: [user],
                 project: project,
                 milestone: milestone,
                 created_at: closed_issue.created_at,
                 updated_at: 1.hour.ago,
                 title: issue_title,
                 description: issue_description
        end

        it 'page breaks first page correctly' do
          get api("#{base_url}/issues?per_page=3", user)

          expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue2.id])
        end

        it 'page breaks second page correctly' do
          get api("#{base_url}/issues?per_page=3&page=2", user)

          expect_paginated_array_response([closed_issue.id])
        end
      end
Sean McGivern's avatar
Sean McGivern committed
973 974 975
    end

    it 'sorts ascending when requested' do
976
      get api("#{base_url}/issues", user), params: { sort: :asc }
Sean McGivern's avatar
Sean McGivern committed
977

978
      expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
Sean McGivern's avatar
Sean McGivern committed
979 980 981
    end

    it 'sorts by updated_at descending when requested' do
982
      get api("#{base_url}/issues", user), params: { order_by: :updated_at }
Sean McGivern's avatar
Sean McGivern committed
983

984
      issue.touch(:updated_at)
985

986
      expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
Sean McGivern's avatar
Sean McGivern committed
987 988 989
    end

    it 'sorts by updated_at ascending when requested' do
990
      get api("#{base_url}/issues", user), params: { order_by: :updated_at, sort: :asc }
Sean McGivern's avatar
Sean McGivern committed
991

992
      expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
Sean McGivern's avatar
Sean McGivern committed
993
    end
994 995
  end

996
  describe "GET /projects/:id/issues/:issue_iid" do
997 998 999 1000 1001 1002 1003 1004
    context 'when unauthenticated' do
      it 'returns public issues' do
        get api("/projects/#{project.id}/issues/#{issue.iid}")

        expect(response).to have_gitlab_http_status(200)
      end
    end

1005
    it 'exposes known attributes' do
1006
      get api("/projects/#{project.id}/issues/#{issue.iid}", user)
1007

1008
      expect(response).to have_gitlab_http_status(200)
1009 1010 1011 1012 1013 1014
      expect(json_response['id']).to eq(issue.id)
      expect(json_response['iid']).to eq(issue.iid)
      expect(json_response['project_id']).to eq(issue.project.id)
      expect(json_response['title']).to eq(issue.title)
      expect(json_response['description']).to eq(issue.description)
      expect(json_response['state']).to eq(issue.state)
1015
      expect(json_response['closed_at']).to be_falsy
1016 1017 1018 1019
      expect(json_response['created_at']).to be_present
      expect(json_response['updated_at']).to be_present
      expect(json_response['labels']).to eq(issue.label_names)
      expect(json_response['milestone']).to be_a Hash
1020
      expect(json_response['assignees']).to be_a Array
1021 1022
      expect(json_response['assignee']).to be_a Hash
      expect(json_response['author']).to be_a Hash
1023
      expect(json_response['confidential']).to be_falsy
1024
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
1025

1026 1027 1028
    it "exposes the 'closed_at' attribute" do
      get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user)

1029
      expect(r