issues_spec.rb 69.9 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

Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
186
      it 'returns issues reacted by the authenticated user' do
187
        issue2 = create(:issue, project: project, author: user, assignees: [user])
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
188
        create(:award_emoji, awardable: issue2, user: user2, name: 'star')
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
189
        create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup')
190

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

193
        expect_paginated_array_response([issue2.id, issue.id])
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
194 195 196 197 198 199
      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')

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

202
        expect_paginated_array_response([issue.id, closed_issue.id])
203 204
      end

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

208
        expect_paginated_array_response(issue.id)
209 210
      end

211 212 213 214 215 216 217 218 219 220 221 222
      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

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

226
        expect_paginated_array_response(issue.id)
227 228
      end

229 230 231 232 233 234
      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)

235
          expect_paginated_array_response(issue2.id)
236 237 238 239 240
        end

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

241
          expect_paginated_array_response(issue2.id)
242 243 244 245 246 247 248 249 250
        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)

251
          expect_paginated_array_response(issue2.id)
252 253 254 255 256
        end

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

257
          expect_paginated_array_response(issue2.id)
258 259 260
        end
      end

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

264 265
        expect_paginated_array_response(issue.id)
        expect(json_response.first['labels']).to eq([label.title])
266 267
      end

268 269 270 271 272 273 274
      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)

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

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

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

284
        expect_paginated_array_response([])
285 286
      end

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

290
        expect_paginated_array_response(issue.id)
291 292
        expect(json_response.first['labels']).to eq([label.title])
        expect(json_response.first['state']).to eq('opened')
293 294
      end

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

298
        expect_paginated_array_response([])
299 300 301
      end

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

304
        expect_paginated_array_response(issue.id)
305 306
      end

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

310
        expect_paginated_array_response(closed_issue.id)
311 312 313
      end

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

316
        expect_paginated_array_response(closed_issue.id)
317
      end
Sean McGivern's avatar
Sean McGivern committed
318

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

322
        expect_paginated_array_response([])
323 324 325 326 327
      end

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

328
        expect_paginated_array_response([])
329 330 331 332 333
      end

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

334
        expect_paginated_array_response([issue.id, closed_issue.id])
335 336 337 338 339 340
      end

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

341
        expect_paginated_array_response(closed_issue.id)
342 343 344
      end

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

347
        expect_paginated_array_response(confidential_issue.id)
348 349
      end

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

353
        expect_paginated_array_response(closed_issue.id)
354 355 356
      end

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

359
        expect_paginated_array_response([])
360 361
      end

362 363 364
      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
365

366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
          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
394 395 396 397 398
      end

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

399
        expect_paginated_array_response([closed_issue.id, issue.id])
Sean McGivern's avatar
Sean McGivern committed
400 401 402 403 404
      end

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

405
        issue.touch(:updated_at)
406

407
        expect_paginated_array_response([issue.id, closed_issue.id])
Sean McGivern's avatar
Sean McGivern committed
408 409 410 411 412
      end

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

413
        issue.touch(:updated_at)
414

415
        expect_paginated_array_response([closed_issue.id, issue.id])
Sean McGivern's avatar
Sean McGivern committed
416
      end
417 418 419 420

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

421
        expect(response).to have_gitlab_http_status(200)
422 423
        expect(response).to match_response_schema('public_api/v4/issues')
      end
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441

      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
442 443 444
    end
  end

445 446
  describe "GET /groups/:id/issues" do
    let!(:group)            { create(:group) }
447
    let!(:group_project)    { create(:project, :public, creator_id: user.id, namespace: group) }
448 449 450
    let!(:group_closed_issue) do
      create :closed_issue,
             author: user,
451
             assignees: [user],
452 453
             project: group_project,
             state: :closed,
Sean McGivern's avatar
Sean McGivern committed
454
             milestone: group_milestone,
455 456
             updated_at: 3.hours.ago,
             created_at: 1.day.ago
457 458 459 460 461 462
    end
    let!(:group_confidential_issue) do
      create :issue,
             :confidential,
             project: group_project,
             author: author,
463
             assignees: [assignee],
464 465
             updated_at: 2.hours.ago,
             created_at: 2.days.ago
466 467 468 469
    end
    let!(:group_issue) do
      create :issue,
             author: user,
470
             assignees: [user],
471
             project: group_project,
Sean McGivern's avatar
Sean McGivern committed
472
             milestone: group_milestone,
473 474
             updated_at: 1.hour.ago,
             title: issue_title,
475 476
             description: issue_description,
             created_at: 5.days.ago
477 478 479 480 481 482 483 484 485 486 487 488 489
    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" }

490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
    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)

507
        expect_paginated_array_response([issue_2.id, issue_1.id, group_closed_issue.id, group_confidential_issue.id, group_issue.id])
508 509 510
      end
    end

511 512 513
    context 'when user is unauthenticated' do
      it 'lists all issues in public projects' do
        get api(base_url)
514

515
        expect_paginated_array_response([group_closed_issue.id, group_issue.id])
516
      end
517 518
    end

519 520 521 522
    context 'when user is a group member' do
      before do
        group_project.add_reporter(user)
      end
523

524 525
      it 'returns all group issues (including opened and closed)' do
        get api(base_url, admin)
526

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

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

533
        expect_paginated_array_response(group_issue.id)
534
      end
535

536
      it 'returns group confidential issues for author' do
537
        get api(base_url, author), params: { state: :opened }
538

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

542
      it 'returns group confidential issues for assignee' do
543
        get api(base_url, assignee), params: { state: :opened }
544

545
        expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
546
      end
547

548
      it 'returns group issues with confidential issues for project members' do
549
        get api(base_url, user), 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 admin' do
555
        get api(base_url, admin), params: { state: :opened }
556

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

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

563
        expect_paginated_array_response(group_issue.id)
564 565
        expect(json_response.first['labels']).to eq([group_label.title])
      end
566

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

570
        expect_paginated_array_response([])
571
      end
572

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

576
        expect_paginated_array_response(group_issue.id)
577
      end
578

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

582
        expect_paginated_array_response(group_issue.id)
583
      end
584

585 586 587
      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)
588

589 590
        create(:label_link, label: label_b, target: group_issue)
        create(:label_link, label: label_c, target: group_issue)
591

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

594
        expect_paginated_array_response(group_issue.id)
595 596
        expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
      end
597

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

601
        expect_paginated_array_response(group_issue.id)
602 603
        expect(json_response.first['id']).to eq(group_issue.id)
      end
604

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

608
        expect_paginated_array_response([])
609
      end
610

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

614
        expect_paginated_array_response([])
615
      end
616

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

620
        expect_paginated_array_response(group_issue.id)
621 622 623 624
        expect(json_response.first['id']).to eq(group_issue.id)
      end

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

627
        expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id])
628 629
      end

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

633
        expect_paginated_array_response([])
634
      end
635

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

639
        expect_paginated_array_response([])
640
      end
641

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

645
        expect_paginated_array_response(group_issue.id)
646
      end
647

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

651
        expect_paginated_array_response(group_closed_issue.id)
652
      end
653

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

657
        expect(response).to have_gitlab_http_status(200)
658

659
        expect_paginated_array_response(group_confidential_issue.id)
660
      end
Sean McGivern's avatar
Sean McGivern committed
661

662 663 664
      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
665

666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
          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
694
      end
Sean McGivern's avatar
Sean McGivern committed
695

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

699
        expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
700
      end
Sean McGivern's avatar
Sean McGivern committed
701

702 703
      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
704

705
        group_issue.touch(:updated_at)
706

707
        expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
708 709 710
      end

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

713
        expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
714
      end
Sean McGivern's avatar
Sean McGivern committed
715
    end
716 717
  end

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

721 722 723 724
    context 'when unauthenticated' do
      it 'returns public project issues' do
        get api("/projects/#{project.id}/issues")

725
        expect_paginated_array_response([issue.id, closed_issue.id])
726 727 728
      end
    end

729
    it 'avoids N+1 queries' do
730 731 732
      get api("/projects/#{project.id}/issues", user)

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

736
      create_list(:issue, 3, project: project)
737 738 739

      expect do
        get api("/projects/#{project.id}/issues", user)
740
      end.not_to exceed_all_query_limit(control_count)
741 742
    end

743 744 745
    it 'returns 404 when project does not exist' do
      get api('/projects/1000/issues', non_member)

746
      expect(response).to have_gitlab_http_status(404)
747 748
    end

749
    it "returns 404 on private projects for other users" do
750
      private_project = create(:project, :private)
751 752 753 754
      create(:issue, project: private_project)

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

755
      expect(response).to have_gitlab_http_status(404)
756 757 758
    end

    it 'returns no issues when user has access to project but not issues' do
759
      restricted_project = create(:project, :public, :issues_private)
760 761 762 763
      create(:issue, project: restricted_project)

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

764
      expect_paginated_array_response([])
765 766
    end

767
    it 'returns project issues without confidential issues for non project members' do
768
      get api("#{base_url}/issues", non_member)
769

770
      expect_paginated_array_response([issue.id, closed_issue.id])
771 772
    end

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

776
      expect_paginated_array_response([issue.id, closed_issue.id])
777 778
    end

779
    it 'returns project confidential issues for author' do
780
      get api("#{base_url}/issues", author)
781

782
      expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
783 784
    end

785
    it 'returns project confidential issues for assignee' do
786
      get api("#{base_url}/issues", assignee)
787

788
      expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
789 790
    end

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

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

797
    it 'returns project confidential issues for admin' do
798
      get api("#{base_url}/issues", admin)
799

800
      expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
Nihad Abbasov's avatar
Nihad Abbasov committed
801
    end
802

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

806
      expect_paginated_array_response(issue.id)
807 808
    end

809 810 811 812 813 814 815
    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)

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

818
      expect_paginated_array_response(issue.id)
819 820
    end

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

824
      expect_paginated_array_response(issue.id)
825 826 827 828 829
    end

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

830
      expect_paginated_array_response(issue.id)
831 832
    end

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

836
      expect_paginated_array_response(issue.id)
837 838 839
    end

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

842
      expect_paginated_array_response([])
843 844
    end

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

848
      expect_paginated_array_response([])
849 850
    end

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

854
      expect_paginated_array_response(issue.id)
855 856 857
    end

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

860
      expect_paginated_array_response([confidential_issue.id, closed_issue.id])
861 862
    end

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

866
      expect_paginated_array_response([])
867 868
    end

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

872
      expect_paginated_array_response([])
873
    end
874

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

878
      expect_paginated_array_response([])
879 880
    end

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

884
      expect_paginated_array_response([issue.id, closed_issue.id])
885 886
    end

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

890
      expect_paginated_array_response(closed_issue.id)
891
    end
Sean McGivern's avatar
Sean McGivern committed
892

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

896
      expect_paginated_array_response(confidential_issue.id)
897 898
    end

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

902
      expect_paginated_array_response([issue.id, closed_issue.id])
903 904
    end

905 906 907
    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
908

909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936
        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
937 938 939
    end

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

942
      expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
Sean McGivern's avatar
Sean McGivern committed
943 944 945
    end

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

948
      issue.touch(:updated_at)
949

950
      expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
Sean McGivern's avatar
Sean McGivern committed
951 952 953
    end

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

956
      expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
Sean McGivern's avatar
Sean McGivern committed
957
    end
958 959
  end

960
  describe "GET /projects/:id/issues/:issue_iid" do
961 962 963 964 965 966 967 968
    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

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

972
      expect(response).to have_gitlab_http_status(200)
973 974 975 976 977 978
      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)
979
      expect(json_response['closed_at']).to be_falsy
980 981 982 983
      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
984
      expect(json_response['assignees']).to be_a Array
985 986
      expect(json_response['assignee']).to be_a Hash
      expect(json_response['author']).to be_a Hash
987
      expect(json_response['confidential']).to be_falsy
988
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
989

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

993
      expect(response).to have_gitlab_http_status(200)
994 995 996
      expect(json_response['closed_at']).to be_present
    end

997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
    context 'links exposure' do
      it 'exposes related resources full URIs' do
        get api("/projects/#{project.id}/issues/#{issue.iid}", user)

        links = json_response['_links']

        expect(links['self']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}")
        expect(links['notes']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/notes")
        expect(links['award_emoji']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/award_emoji")
        expect(links['project']).to end_with("/api/v4/projects/#{project.id}")
      end
    end

1010 1011
    it "returns a project issue by internal id" do
      get api("/projects/#{project.id}/issues/#{issue.iid}", user)
1012

1013
      expect(response).to have_gitlab_http_status(200)