issues_spec.rb 56.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
jubianchi's avatar
jubianchi committed
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) { URI.escape(Milestone::None.title) }

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

  describe "GET /issues" do
66
    context "when unauthenticated" do
67
      it "returns authentication error" do
68
        get api("/issues")
69

70
        expect(response).to have_gitlab_http_status(401)
71
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
72
    end
73
    context "when authenticated" do
74 75
      let(:first_issue) { json_response.first }

76
      it "returns an array of issues" do
Robert Speicher's avatar
Robert Speicher committed
77
        get api("/issues", user)
78

79
        expect_paginated_array_response(size: 2)
80
        expect(json_response.first['title']).to eq(issue.title)
81
        expect(json_response.last).to have_key('web_url')
Nihad Abbasov's avatar
Nihad Abbasov committed
82
      end
83

84
      it 'returns an array of closed issues' do
85
        get api('/issues', user), state: :closed
86

87
        expect_paginated_array_response(size: 1)
88
        expect(first_issue['id']).to eq(closed_issue.id)
jubianchi's avatar
jubianchi committed
89 90
      end

91
      it 'returns an array of opened issues' do
92
        get api('/issues', user), state: :opened
93

94
        expect_paginated_array_response(size: 1)
95
        expect(first_issue['id']).to eq(issue.id)
jubianchi's avatar
jubianchi committed
96 97
      end

98
      it 'returns an array of all issues' do
99
        get api('/issues', user), state: :all
100

101
        expect_paginated_array_response(size: 2)
102
        expect(first_issue['id']).to eq(issue.id)
103
        expect(json_response.second['id']).to eq(closed_issue.id)
jubianchi's avatar
jubianchi committed
104
      end
jubianchi's avatar
jubianchi committed
105

106 107 108 109 110 111 112 113 114
      it 'returns issues assigned to me' do
        issue2 = create(:issue, assignees: [user2], project: project)

        get api('/issues', user2), scope: 'assigned-to-me'

        expect_paginated_array_response(size: 1)
        expect(first_issue['id']).to eq(issue2.id)
      end

115 116 117
      it 'returns issues authored by the given author id' do
        issue2 = create(:issue, author: user2, project: project)

118
        get api('/issues', user), author_id: user2.id, scope: 'all'
119 120 121 122 123 124 125 126

        expect_paginated_array_response(size: 1)
        expect(first_issue['id']).to eq(issue2.id)
      end

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

127
        get api('/issues', user), assignee_id: user2.id, scope: 'all'
128 129 130 131 132 133 134 135

        expect_paginated_array_response(size: 1)
        expect(first_issue['id']).to eq(issue2.id)
      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)

136
        get api('/issues', user), author_id: user2.id, assignee_id: user2.id, scope: 'all'
137 138 139 140 141

        expect_paginated_array_response(size: 1)
        expect(first_issue['id']).to eq(issue2.id)
      end

142 143 144 145 146 147 148 149 150 151
      it 'returns issues reacted by the authenticated user by the given emoji' do
        issue2 = create(:issue, project: project, author: user, assignees: [user])
        award_emoji = create(:award_emoji, awardable: issue2, user: user2, name: 'star')

        get api('/issues', user2), my_reaction_emoji: award_emoji.name, scope: 'all'

        expect_paginated_array_response(size: 1)
        expect(first_issue['id']).to eq(issue2.id)
      end

152
      it 'returns issues matching given search string for title' do
153
        get api("/issues", user), search: issue.title
154 155 156 157 158 159

        expect_paginated_array_response(size: 1)
        expect(json_response.first['id']).to eq(issue.id)
      end

      it 'returns issues matching given search string for description' do
160
        get api("/issues", user), search: issue.description
161 162

        expect_paginated_array_response(size: 1)
163
        expect(first_issue['id']).to eq(issue.id)
164 165
      end

166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
      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)

          expect(json_response.size).to eq(1)
          expect(first_issue['id']).to eq(issue2.id)
        end

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

          expect(json_response.size).to eq(1)
          expect(first_issue['id']).to eq(issue2.id)
        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)

          expect(json_response.size).to eq(1)
          expect(first_issue['id']).to eq(issue2.id)
        end

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

          expect(json_response.size).to eq(1)
          expect(first_issue['id']).to eq(issue2.id)
        end
      end

202
      it 'returns an array of labeled issues' do
203
        get api("/issues", user), labels: label.title
204

205
        expect_paginated_array_response(size: 1)
206
        expect(first_issue['labels']).to eq([label.title])
jubianchi's avatar
jubianchi committed
207 208
      end

209 210 211 212 213 214 215 216
      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)

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

218
        expect_paginated_array_response(size: 1)
219
        expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
jubianchi's avatar
jubianchi committed
220 221
      end

222
      it 'returns an empty array if no issue matches labels' do
223
        get api('/issues', user), labels: 'foo,bar'
224

225
        expect_paginated_array_response(size: 0)
jubianchi's avatar
jubianchi committed
226 227
      end

228
      it 'returns an array of labeled issues matching given state' do
229
        get api("/issues", user), labels: label.title, state: :opened
230

231
        expect_paginated_array_response(size: 1)
232 233
        expect(json_response.first['labels']).to eq([label.title])
        expect(json_response.first['state']).to eq('opened')
jubianchi's avatar
jubianchi committed
234 235
      end

236 237 238
      it 'returns unlabeled issues for "No Label" label' do
        get api("/issues", user), labels: 'No Label'

239
        expect_paginated_array_response(size: 1)
240 241 242
        expect(json_response.first['labels']).to be_empty
      end

243
      it 'returns an empty array if no issue matches labels and state filters' do
jubianchi's avatar
jubianchi committed
244
        get api("/issues?labels=#{label.title}&state=closed", user)
245

246
        expect_paginated_array_response(size: 0)
jubianchi's avatar
jubianchi committed
247
      end
Sean McGivern's avatar
Sean McGivern committed
248

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

252
        expect_paginated_array_response(size: 0)
253 254 255 256 257
      end

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

258
        expect_paginated_array_response(size: 0)
259 260 261 262 263
      end

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

264
        expect_paginated_array_response(size: 2)
265 266 267 268 269 270 271 272
        expect(json_response.first['id']).to eq(issue.id)
        expect(json_response.second['id']).to eq(closed_issue.id)
      end

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

273
        expect_paginated_array_response(size: 1)
274 275 276 277
        expect(json_response.first['id']).to eq(closed_issue.id)
      end

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

280
        expect_paginated_array_response(size: 1)
281 282 283
        expect(json_response.first['id']).to eq(confidential_issue.id)
      end

284 285 286
      it 'returns an array of issues found by iids' do
        get api('/issues', user), iids: [closed_issue.iid]

287
        expect_paginated_array_response(size: 1)
288 289 290 291 292 293
        expect(json_response.first['id']).to eq(closed_issue.id)
      end

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

294
        expect_paginated_array_response(size: 0)
295 296
      end

Sean McGivern's avatar
Sean McGivern committed
297 298 299
      it 'sorts by created_at descending by default' do
        get api('/issues', user)

300
        response_dates = json_response.map { |issue| issue['created_at'] }
301 302

        expect_paginated_array_response(size: 2)
Sean McGivern's avatar
Sean McGivern committed
303 304 305 306 307 308
        expect(response_dates).to eq(response_dates.sort.reverse)
      end

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

309
        response_dates = json_response.map { |issue| issue['created_at'] }
310 311

        expect_paginated_array_response(size: 2)
Sean McGivern's avatar
Sean McGivern committed
312 313 314 315 316 317
        expect(response_dates).to eq(response_dates.sort)
      end

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

318
        response_dates = json_response.map { |issue| issue['updated_at'] }
319 320

        expect_paginated_array_response(size: 2)
Sean McGivern's avatar
Sean McGivern committed
321 322 323 324 325 326
        expect(response_dates).to eq(response_dates.sort.reverse)
      end

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

327
        response_dates = json_response.map { |issue| issue['updated_at'] }
328 329

        expect_paginated_array_response(size: 2)
Sean McGivern's avatar
Sean McGivern committed
330 331
        expect(response_dates).to eq(response_dates.sort)
      end
332 333 334 335

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

336
        expect(response).to have_gitlab_http_status(200)
337 338
        expect(response).to match_response_schema('public_api/v4/issues')
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
339 340 341
    end
  end

342 343
  describe "GET /groups/:id/issues" do
    let!(:group)            { create(:group) }
344
    let!(:group_project)    { create(:project, :public, creator_id: user.id, namespace: group) }
345 346 347
    let!(:group_closed_issue) do
      create :closed_issue,
             author: user,
348
             assignees: [user],
349 350
             project: group_project,
             state: :closed,
Sean McGivern's avatar
Sean McGivern committed
351 352
             milestone: group_milestone,
             updated_at: 3.hours.ago
353 354 355 356 357 358
    end
    let!(:group_confidential_issue) do
      create :issue,
             :confidential,
             project: group_project,
             author: author,
359
             assignees: [assignee],
Sean McGivern's avatar
Sean McGivern committed
360
             updated_at: 2.hours.ago
361 362 363 364
    end
    let!(:group_issue) do
      create :issue,
             author: user,
365
             assignees: [user],
366
             project: group_project,
Sean McGivern's avatar
Sean McGivern committed
367
             milestone: group_milestone,
368 369 370
             updated_at: 1.hour.ago,
             title: issue_title,
             description: issue_description
371 372 373 374 375 376 377 378 379 380 381 382
    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) }

    before do
383
      group_project.add_reporter(user)
384 385 386
    end
    let(:base_url) { "/groups/#{group.id}/issues" }

387 388 389
    it 'returns all group issues (including opened and closed)' do
      get api(base_url, admin)

390
      expect_paginated_array_response(size: 3)
391 392
    end

393
    it 'returns group issues without confidential issues for non project members' do
394
      get api("#{base_url}?state=opened", non_member)
395

396
      expect_paginated_array_response(size: 1)
397 398 399 400
      expect(json_response.first['title']).to eq(group_issue.title)
    end

    it 'returns group confidential issues for author' do
401
      get api("#{base_url}?state=opened", author)
402

403
      expect_paginated_array_response(size: 2)
404 405 406
    end

    it 'returns group confidential issues for assignee' do
407
      get api("#{base_url}?state=opened", assignee)
408

409
      expect_paginated_array_response(size: 2)
410 411 412
    end

    it 'returns group issues with confidential issues for project members' do
413
      get api("#{base_url}?state=opened", user)
414

415
      expect_paginated_array_response(size: 2)
416 417 418
    end

    it 'returns group confidential issues for admin' do
419
      get api("#{base_url}?state=opened", admin)
420

421
      expect_paginated_array_response(size: 2)
422 423 424 425 426
    end

    it 'returns an array of labeled group issues' do
      get api("#{base_url}?labels=#{group_label.title}", user)

427
      expect_paginated_array_response(size: 1)
428 429 430 431 432 433
      expect(json_response.first['labels']).to eq([group_label.title])
    end

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

434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
      expect_paginated_array_response(size: 0)
    end

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

      expect_paginated_array_response(size: 1)
      expect(json_response.first['id']).to eq(group_issue.id)
    end

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

      expect_paginated_array_response(size: 1)
      expect(json_response.first['id']).to eq(group_issue.id)
449 450
    end

451 452 453 454 455 456 457 458 459
    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)

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

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

460
      expect_paginated_array_response(size: 1)
461 462 463
      expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
    end

464 465 466
    it 'returns an array of issues found by iids' do
      get api(base_url, user), iids: [group_issue.iid]

467
      expect_paginated_array_response(size: 1)
468 469 470 471 472 473
      expect(json_response.first['id']).to eq(group_issue.id)
    end

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

474
      expect_paginated_array_response(size: 0)
475 476
    end

477 478 479
    it 'returns an empty array if no group issue matches labels' do
      get api("#{base_url}?labels=foo,bar", user)

480
      expect_paginated_array_response(size: 0)
481 482 483 484 485
    end

    it 'returns an empty array if no issue matches milestone' do
      get api("#{base_url}?milestone=#{group_empty_milestone.title}", user)

486
      expect_paginated_array_response(size: 0)
487 488 489 490 491
    end

    it 'returns an empty array if milestone does not exist' do
      get api("#{base_url}?milestone=foo", user)

492
      expect_paginated_array_response(size: 0)
493 494 495
    end

    it 'returns an array of issues in given milestone' do
496
      get api("#{base_url}?state=opened&milestone=#{group_milestone.title}", user)
497

498
      expect_paginated_array_response(size: 1)
499 500 501 502 503 504 505
      expect(json_response.first['id']).to eq(group_issue.id)
    end

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

506
      expect_paginated_array_response(size: 1)
507 508
      expect(json_response.first['id']).to eq(group_closed_issue.id)
    end
Sean McGivern's avatar
Sean McGivern committed
509

510
    it 'returns an array of issues with no milestone' do
511
      get api("#{base_url}?milestone=#{no_milestone_title}", user)
512

513
      expect(response).to have_gitlab_http_status(200)
514 515

      expect_paginated_array_response(size: 1)
516 517 518
      expect(json_response.first['id']).to eq(group_confidential_issue.id)
    end

Sean McGivern's avatar
Sean McGivern committed
519 520 521
    it 'sorts by created_at descending by default' do
      get api(base_url, user)

522
      response_dates = json_response.map { |issue| issue['created_at'] }
523 524

      expect_paginated_array_response(size: 3)
Sean McGivern's avatar
Sean McGivern committed
525 526 527 528 529 530
      expect(response_dates).to eq(response_dates.sort.reverse)
    end

    it 'sorts ascending when requested' do
      get api("#{base_url}?sort=asc", user)

531
      response_dates = json_response.map { |issue| issue['created_at'] }
532 533

      expect_paginated_array_response(size: 3)
Sean McGivern's avatar
Sean McGivern committed
534 535 536 537 538 539
      expect(response_dates).to eq(response_dates.sort)
    end

    it 'sorts by updated_at descending when requested' do
      get api("#{base_url}?order_by=updated_at", user)

540
      response_dates = json_response.map { |issue| issue['updated_at'] }
541 542

      expect_paginated_array_response(size: 3)
Sean McGivern's avatar
Sean McGivern committed
543 544 545 546 547 548
      expect(response_dates).to eq(response_dates.sort.reverse)
    end

    it 'sorts by updated_at ascending when requested' do
      get api("#{base_url}?order_by=updated_at&sort=asc", user)

549
      response_dates = json_response.map { |issue| issue['updated_at'] }
550 551

      expect_paginated_array_response(size: 3)
Sean McGivern's avatar
Sean McGivern committed
552 553
      expect(response_dates).to eq(response_dates.sort)
    end
554 555
  end

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

559 560 561 562 563 564 565 566 567 568 569 570
    it 'avoids N+1 queries' do
      control_count = ActiveRecord::QueryRecorder.new do
        get api("/projects/#{project.id}/issues", user)
      end.count

      create(:issue, author: user, project: project)

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

571 572 573
    it 'returns 404 when project does not exist' do
      get api('/projects/1000/issues', non_member)

574
      expect(response).to have_gitlab_http_status(404)
575 576
    end

577
    it "returns 404 on private projects for other users" do
578
      private_project = create(:project, :private)
579 580 581 582
      create(:issue, project: private_project)

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

583
      expect(response).to have_gitlab_http_status(404)
584 585 586
    end

    it 'returns no issues when user has access to project but not issues' do
587
      restricted_project = create(:project, :public, :issues_private)
588 589 590 591
      create(:issue, project: restricted_project)

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

592
      expect_paginated_array_response(size: 0)
593 594
    end

595
    it 'returns project issues without confidential issues for non project members' do
596
      get api("#{base_url}/issues", non_member)
597

598
      expect_paginated_array_response(size: 2)
599 600 601
      expect(json_response.first['title']).to eq(issue.title)
    end

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

605
      expect_paginated_array_response(size: 2)
606 607 608
      expect(json_response.first['title']).to eq(issue.title)
    end

609
    it 'returns project confidential issues for author' do
610
      get api("#{base_url}/issues", author)
611

612
      expect_paginated_array_response(size: 3)
613 614 615
      expect(json_response.first['title']).to eq(issue.title)
    end

616
    it 'returns project confidential issues for assignee' do
617
      get api("#{base_url}/issues", assignee)
618

619
      expect_paginated_array_response(size: 3)
620 621 622
      expect(json_response.first['title']).to eq(issue.title)
    end

623
    it 'returns project issues with confidential issues for project members' do
624
      get api("#{base_url}/issues", user)
625

626
      expect_paginated_array_response(size: 3)
627 628 629
      expect(json_response.first['title']).to eq(issue.title)
    end

630
    it 'returns project confidential issues for admin' do
631
      get api("#{base_url}/issues", admin)
632

633
      expect_paginated_array_response(size: 3)
634
      expect(json_response.first['title']).to eq(issue.title)
Nihad Abbasov's avatar
Nihad Abbasov committed
635
    end
jubianchi's avatar
jubianchi committed
636

637
    it 'returns an array of labeled project issues' do
638
      get api("#{base_url}/issues?labels=#{label.title}", user)
639

640
      expect_paginated_array_response(size: 1)
641
      expect(json_response.first['labels']).to eq([label.title])
jubianchi's avatar
jubianchi committed
642 643
    end

644 645 646 647 648 649 650 651
    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)

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

653
      expect_paginated_array_response(size: 1)
654 655 656
      expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
    end

657 658 659 660 661 662 663 664 665 666 667 668 669 670
    it 'returns issues matching given search string for title' do
      get api("#{base_url}/issues?search=#{issue.title}", user)

      expect_paginated_array_response(size: 1)
      expect(json_response.first['id']).to eq(issue.id)
    end

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

      expect_paginated_array_response(size: 1)
      expect(json_response.first['id']).to eq(issue.id)
    end

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

674
      expect_paginated_array_response(size: 1)
675 676 677 678 679 680
      expect(json_response.first['id']).to eq(issue.id)
    end

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

681
      expect_paginated_array_response(size: 0)
682 683
    end

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

687
      expect_paginated_array_response(size: 0)
jubianchi's avatar
jubianchi committed
688 689
    end

690
    it 'returns an empty array if no project issue matches labels' do
691
      get api("#{base_url}/issues?labels=foo,bar", user)
692

693
      expect_paginated_array_response(size: 0)
694 695
    end

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

699
      expect_paginated_array_response(size: 0)
jubianchi's avatar
jubianchi committed
700
    end
701

702
    it 'returns an empty array if milestone does not exist' do
703
      get api("#{base_url}/issues?milestone=foo", user)
704

705
      expect_paginated_array_response(size: 0)
706 707
    end

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

711
      expect_paginated_array_response(size: 2)
712 713
      expect(json_response.first['id']).to eq(issue.id)
      expect(json_response.second['id']).to eq(closed_issue.id)
714 715
    end

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

719
      expect_paginated_array_response(size: 1)
720
      expect(json_response.first['id']).to eq(closed_issue.id)
721
    end
Sean McGivern's avatar
Sean McGivern committed
722

723
    it 'returns an array of issues with no milestone' do
724
      get api("#{base_url}/issues?milestone=#{no_milestone_title}", user)
725

726
      expect_paginated_array_response(size: 1)
727 728 729
      expect(json_response.first['id']).to eq(confidential_issue.id)
    end

Sean McGivern's avatar
Sean McGivern committed
730 731 732
    it 'sorts by created_at descending by default' do
      get api("#{base_url}/issues", user)

733
      response_dates = json_response.map { |issue| issue['created_at'] }
734 735

      expect_paginated_array_response(size: 3)
Sean McGivern's avatar
Sean McGivern committed
736 737 738 739 740 741
      expect(response_dates).to eq(response_dates.sort.reverse)
    end

    it 'sorts ascending when requested' do
      get api("#{base_url}/issues?sort=asc", user)

742
      response_dates = json_response.map { |issue| issue['created_at'] }
743 744

      expect_paginated_array_response(size: 3)
Sean McGivern's avatar
Sean McGivern committed
745 746 747 748 749 750
      expect(response_dates).to eq(response_dates.sort)
    end

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

751
      response_dates = json_response.map { |issue| issue['updated_at'] }
752 753

      expect_paginated_array_response(size: 3)
Sean McGivern's avatar
Sean McGivern committed
754 755 756 757 758 759
      expect(response_dates).to eq(response_dates.sort.reverse)
    end

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

760
      response_dates = json_response.map { |issue| issue['updated_at'] }
761 762

      expect_paginated_array_response(size: 3)
Sean McGivern's avatar
Sean McGivern committed
763 764
      expect(response_dates).to eq(response_dates.sort)
    end
765 766
  end

767
  describe "GET /projects/:id/issues/:issue_iid" do
768
    it 'exposes known attributes' do
769
      get api("/projects/#{project.id}/issues/#{issue.iid}", user)
770

771
      expect(response).to have_gitlab_http_status(200)
772 773 774 775 776 777
      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)
778
      expect(json_response['closed_at']).to be_falsy
779 780 781 782
      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
783
      expect(json_response['assignees']).to be_a Array
784 785
      expect(json_response['assignee']).to be_a Hash
      expect(json_response['author']).to be_a Hash
786
      expect(json_response['confidential']).to be_falsy
787
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
788

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

792
      expect(response).to have_gitlab_http_status(200)
793 794 795
      expect(json_response['closed_at']).to be_present
    end

796 797 798 799 800 801 802 803 804 805 806 807 808
    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

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

812
      expect(response).to have_gitlab_http_status(200)
813 814
      expect(json_response['title']).to eq(issue.title)
      expect(json_response['iid']).to eq(issue.iid)
Nihad Abbasov's avatar
Nihad Abbasov committed
815
    end
816

817
    it "returns 404 if issue id not found" do
818
      get api("/projects/#{project.id}/issues/54321", user)
819
      expect(response).to have_gitlab_http_status(404)
820
    end
821

822 823 824
    it "returns 404 if the issue ID is used" do
      get api("/projects/#{project.id}/issues/#{issue.id}", user)

825
      expect(response).to have_gitlab_http_status(404)
826 827
    end

828
    context 'confidential issues' do
829
      it "returns 404 for non project members" do
830
        get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member)
831

832
        expect(response).to have_gitlab_http_status(404)
833 834
      end

835
      it "returns 404 for project members with guest role" do
836
        get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest)
837

838
        expect(response).to have_gitlab_http_status(404)
839 840
      end

841
      it "returns confidential issue for project members" do
842
        get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user)
843

844
        expect(response).to have_gitlab_http_status(200)
845 846 847 848
        expect(json_response['title']).to eq(confidential_issue.title)
        expect(json_response['iid']).to eq(confidential_issue.iid)
      end

849
      it "returns confidential issue for author" do
850
        get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author)
851

852
        expect(response).to have_gitlab_http_status(200)
853 854 855 856
        expect(json_response['title']).to eq(confidential_issue.title)
        expect(json_response['iid']).to eq(confidential_issue.iid)
      end

857
      it "returns confidential issue for assignee" do
858
        get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee)
859

860
        expect(response).to have_gitlab_http_status(200)
861 862 863 864
        expect(json_response['title']).to eq(confidential_issue.title)
        expect(json_response['iid']).to eq(confidential_issue.iid)
      end

865
      it "returns confidential issue for admin" do
866
        get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin)
867

868
        expect(response).to have_gitlab_http_status(200)
869 870 871 872
        expect(json_response['title']).to eq(confidential_issue.title)
        expect(json_response['iid']).to eq(confidential_issue.iid)
      end
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
873 874 875
  end

  describe "POST /projects/:id/issues" do
876 877 878 879 880
    context 'support for deprecated assignee_id' do
      it 'creates a new project issue' do
        post api("/projects/#{project.id}/issues", user),
          title: 'new issue', assignee_id: user2.id

881
        expect(response).to have_gitlab_http_status(201)
882 883 884 885
        expect(json_response['title']).to eq('new issue')
        expect(json_response['assignee']['name']).to eq(user2.name)
        expect(json_response['assignees'].first['name']).to eq(user2.name)
      end
886 887 888 889 890 891 892 893 894

      it 'creates a new project issue when assignee_id is empty' do
        post api("/projects/#{project.id}/issues", user),
          title: 'new issue', assignee_id: ''

        expect(response).to have_gitlab_http_status(201)
        expect(json_response['title']).to eq('new issue')
        expect(json_response['assignee']).to be_nil
      end
895 896
    end

897
    context 'single assignee restrictions' do
898 899 900 901
      it 'creates a new project issue with no more than one assignee' do
        post api("/projects/#{project.id}/issues", user),
          title: 'new issue', assignee_ids: [user2.id, guest.id]

902
        expect(response).to have_gitlab_http_status(201)
903 904 905 906 907
        expect(json_response['title']).to eq('new issue')
        expect(json_response['assignees'].count).to eq(1)
      end
    end

908 909 910 911 912 913 914 915 916 917 918 919 920 921
    context 'user does not have permissions to create issue' do
      let(:not_member)  { create(:user) }

      before do
        project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE)
      end

      it 'renders 403' do
        post api("/projects/#{project.id}/issues", not_member), title: 'new issue'

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

922
    it 'creates a new project issue' do
923
      post api("/projects/#{project.id}/issues", user),
924 925
        title: 'new issue', labels: 'label, label2', weight: 3,
        assignee_ids: [user2.id]
926

927
      expect(response).to have_gitlab_http_status(201)
928 929
      expect(json_response['title']).to eq('new issue')
      expect(json_response['description']).to be_nil
Douwe Maan's avatar
Douwe Maan committed
930
      expect(json_response['labels']).to eq(%w(label label2))
931
      expect(json_response['confidential']).to be_falsy
932 933
      expect(json_response['assignee']['name']).to eq(user2.name)
      expect(json_response['assignees'].first['name']).to eq(user2.name)
934 935 936 937 938 939
    end

    it 'creates a new confidential project issue' do
      post api("/projects/#{project.id}/issues", user),
        title: 'new issue', confidential: true

940
      expect(response).to have_gitlab_http_status(201)
941 942 943 944 945 946 947 948
      expect(json_response['title']).to eq('new issue')
      expect(json_response['confidential']).to be_truthy
    end

    it 'creates a new confidential project issue with a different param' do
      post api("/projects/#{project.id}/issues", user),
        title: 'new issue', confidential: 'y'

949
      expect(response).to have_gitlab_http_status(201)
950 951 952 953 954 955 956 957
      expect(json_response['title']).to eq('new issue')
      expect(json_response['confidential']).to be_truthy
    end

    it 'creates a public issue when confidential param is false' do
      post api("/projects/#{project.id}/issues", user),
        title: 'new issue', confidential: false

958
      expect(response).to have_gitlab_http_status(201)
959 960 961 962 963 964 965 966
      expect(json_response['title']).to eq('new issue')
      expect(json_response['confidential']).to be_falsy
    end

    it 'creates a public issue when confidential param is invalid' do
      post api("/projects/#{project.id}/issues", user),
        title: 'new issue', confidential: 'foo'

967
      expect(response).to have_gitlab_http_status(400)
Robert Schilling's avatar
Robert Schilling committed
968
      expect(json_response['error']).to eq('confidential is invalid')
Nihad Abbasov's avatar
Nihad Abbasov committed
969
    end
970

971
    it "returns a 400 bad request if title not given" do
972
      post api("/projects/#{project.id}/issues", user), labels: 'label, label2'
973
      expect(response).to have_gitlab_http_status(400)
974
    end
975

976
    it 'allows special label names' do
977 978
      post api("/projects/#{project.id}/issues", user),
           title: 'new issue',
979 980 981 982 983 984 985
           labels: 'label, label?, label&foo, ?, &'
      expect(response.status).to eq(201)
      expect(json_response['labels']).to include 'label'
      expect(json_response['labels']).to include 'label?'
      expect(json_response['labels']).to include 'label&foo'
      expect(json_response['labels']).to include '?'
      expect(json_response['labels']).to include '&'
986
    end
987

988
    it 'returns 400 if title is too long' do
989 990
      post api("/projects/#{project.id}/issues", user),
           title: 'g' * 256
991
      expect(response).to have_gitlab_http_status(400)
992
      expect(json_response['message']['title']).to eq([
993
        'is too long (maximum is 255 characters)'
994
      ])
995
    end
996

997
    context 'resolving discussions' do
Douwe Maan's avatar
Douwe Maan committed
998
      let(:discussion) { create(:diff_note_on_merge_request).to_discussion }
999 1000
      let(:merge_request) { discussion.noteable }
      let(:project) { merge_request.source_project }
1001

1002
      before do
1003
        project.add_master(user)
1004 1005
      end

1006 1007 1008 1009
      context 'resolving all discussions in a merge request' do
        before do
          post api("/projects/#{project.id}/issues", user),
               title: 'New Issue',
Bob Van Landuyt's avatar
Bob Van Landuyt committed
1010
               merge_request_to_resolve_discussions_of: merge_request.iid
1011
        end
1012

1013
        it_behaves_like 'creating an issue resolving discussions through the API'
1014 1015
      end

1016 1017 1018 1019
      context 'resolving a single discussion' do
        before do
          post api("/projects/#{project.id}/issues", user),
               title: 'New Issue',
Bob Van Landuyt's avatar
Bob Van Landuyt committed
1020
               merge_request_to_resolve_discussions_of: merge_request.iid,
1021 1022 1023 1024
               discussion_to_resolve: discussion.id
        end

        it_behaves_like 'creating an issue resolving discussions through the API'
1025 1026 1027
      end
    end

1028 1029 1030 1031 1032 1033 1034
    context 'with due date' do
      it 'creates a new project issue' do
        due_date = 2.weeks.from_now.strftime('%Y-%m-%d')

        post api("/projects/#{project.id}/issues", user),
          title: 'new issue', due_date: due_date

1035
        expect(response).to have_gitlab_http_status(201)
1036 1037 1038 1039 1040 1041
        expect(json_response['title']).to eq('new issue')
        expect(json_response['description']).to be_nil
        expect(json_response['due_date']).to eq(due_date)
      end
    end

1042
    context 'when an admin or owner makes the request' do
1043 1044
      it 'accepts the creation date to be set' do
        creation_time = 2.weeks.ago
1045
        post api("/projects/#{project.id}/issues", user),
1046
          title: 'new issue', labels: 'label, label2', created_at: creation_time
1047

1048
        expect(response).to have_gitlab_http_status(201)
1049
        expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
1050 1051
      end
    end
1052 1053 1054 1055 1056 1057 1058 1059

    context 'the user can only read the issue' do
      it 'cannot create new labels' do
        expect do
          post api("/projects/#{project.id}/issues", non_member), title: 'new issue', labels: 'label, label2'
        end.not_to change { project.labels.count }
      end
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
1060 1061
  end

1062 1063
  describe 'POST /projects/:id/issues with spam filtering' do
    before do
1064
      allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
1065
      allow_any_instance_of(AkismetService).to receive_messages(spam?: true)
1066 1067
    end

1068 1069 1070 1071 1072 1073 1074
    let(:params) do
      {
        title: 'new issue',
        description: 'content here',
        labels: 'label, label2'
      }
    end
1075

1076
    it "does not create a new project issue" do
1077
      expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count)
1078
      expect(response).to have_gitlab_http_status(400)
1079
      expect(json_response['message']).to eq({ "error" => "Spam detected" })
1080

1081 1082 1083
      spam_logs = SpamLog.all
      expect(spam_logs.count).to eq(1)
      expect(spam_logs[0].title).to eq('new issue')
1084
      expect(spam_logs[0].description).to eq('content here')
1085 1086 1087 1088 1089
      expect(spam_logs[0].user).to eq(user)
      expect(spam_logs[0].noteable_type).to eq('Issue')
    end
  end

1090
  describe "PUT /projects/:id/issues/:issue_iid to update only title" do
1091
    it "updates a project issue" do
1092
      put api("/projects/#{project.id}/issues/#{issue.iid}", user),
Andrew8xx8's avatar
Andrew8xx8 committed
1093
        title: 'updated title'
1094
      expect(response).to have_gitlab_http_status(200)
Andrew8xx8's avatar
Andrew8xx8 committed
1095

1096
      expect(json_response['title']).to eq('updated title')
Andrew8xx8's avatar
Andrew8xx8 committed
1097
    end
1098

1099
    it "returns 404 error if issue iid not found" do
1100 1101
      put api("/projects/#{project.id}/issues/44444", user),
        title: 'updated title'
1102
      expect(response).to have_gitlab_http_status(404)
1103
    end
1104

1105
    it "returns 404 error if issue id is used instead of the iid" do
1106
      put api("/projects/#{project.id}/issues/#{issue.id}", user),
1107
          title: 'updated title'
1108
      expect(response).to have_gitlab_http_status(404)
1109 1110 1111 1112
    end

    it 'allows special label names' do
      put api("/projects/#{project.id}/issues/#{issue.iid}", user),
1113
          title: 'updated title',
1114 1115 1116 1117 1118 1119 1120 1121
          labels: 'label, label?, label&foo, ?, &'

      expect(response.status).to eq(200)
      expect(json_response['labels']).to include 'label'
      expect(json_response['labels']).to include 'label?'
      expect(json_response['labels']).to include 'label&foo'
      expect(json_response['labels']).to include '?'
      expect(json_response['labels']).to include '&'
1122
    end
1123 1124

    context 'confidential issues' do
1125
      it "returns 403 for non project members" do
1126
        put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member),
1127
          title: 'updated title'
1128
        expect(response).to have_gitlab_http_status(403)
1129 1130
      end

1131
      it "returns 403 for project members with guest role" do
1132
        put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest),
1133
          title: 'updated title'
1134
        expect(response).to have_gitlab_http_status(403)
1135 1136
      end

1137
      it "updates a confidential issue for project members" do
1138
        put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
1139
          title: 'updated title'
1140
        expect(response).to have_gitlab_http_status(200)
1141 1142 1143
        expect(json_response['title']).to eq('updated title')
      end

1144
      it "updates a confidential issue for author" do
1145
        put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author),
1146
          title: 'updated title'
1147
        expect(response).to have_gitlab_http_status(200)
1148 1149 1150
        expect(json_response['title']).to eq('updated title')
      end

1151
      it "updates a confidential issue for admin" do
1152
        put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin),
1153
          title: 'updated title'
1154
        expect(response).to have_gitlab_http_status(200)
1155 1156
        expect(json_response['title']).to eq('updated title')
      end
1157 1158

      it 'sets an issue to confidential' do
1159
        put api("/projects/#{project.id}/issues/#{issue.iid}", user),
1160 1161
          confidential: true

1162
        expect(response).to have_gitlab_http_status(200)
1163 1164 1165 1166
        expect(json_response['confidential']).to be_truthy
      end

      it 'makes a confidential issue public' do
1167
        put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
1168 1169
          confidential: false

1170
        expect(response).to have_gitlab_http_status(200)
1171 1172 1173 1174
        expect(json_response['confidential']).to be_falsy
      end

      it 'does not update a confidential issue with wrong confidential flag' do
1175
        put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
1176 1177
          confidential: 'foo'

1178
        expect(response).to have_gitlab_http_status(400)
Robert Schilling's avatar
Robert Schilling committed
1179
        expect(json_response['error']).to eq('confidential is invalid')
1180
      end
1181
    end
1182 1183
  end

1184
  describe 'PUT /projects/:id/issues/:issue_iid with spam filtering' do
1185 1186 1187 1188 1189 1190 1191 1192 1193 1194
    let(:params) do
      {
        title: 'updated title',
        description: 'content here',
        labels: 'label, label2'
      }
    end

    it "does not create a new project issue" do
      allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true)
1195
      allow_any_instance_of(AkismetService).to receive_messages(spam?: true)
1196

1197
      put api("/projects/#{project.id}/issues/#{issue.iid}", user), params
1198

1199
      expect(response).to have_gitlab_http_status(400)
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210
      expect(json_response['message']).to eq({ "error" => "Spam detected" })

      spam_logs = SpamLog.all
      expect(spam_logs.count).to eq(1)
      expect(spam_logs[0].title).to eq('updated title')
      expect(spam_logs[0].description).to eq('content here')
      expect(spam_logs[0].user).to eq(user)
      expect(spam_logs[0].noteable_type).to eq('Issue')
    end
  end

1211 1212 1213 1214 1215 1216
  describe 'PUT /projects/:id/issues/:issue_iid to update assignee' do
    context 'support for deprecated assignee_id' do
      it 'removes assignee' do
        put api("/projects/#{project.id}/issues/#{issue.iid}", user),
          assignee_id: 0

1217
        expect(response).to have_gitlab_http_status(200)
1218 1219 1220 1221 1222 1223 1224 1225

        expect(json_response['assignee']).to be_nil
      end

      it 'updates an issue with new assignee' do
        put api("/projects/#{project.id}/issues/#{issue.iid}", user),
          assignee_id: user2.id

1226
        expect(response).to have_gitlab_http_status(200)
1227 1228 1229 1230 1231 1232 1233 1234 1235

        expect(json_response['assignee']['name']).to eq(user2.name)
      end
    end

    it 'removes assignee' do
      put api("/projects/#{project.id}/issues/#{issue.iid}", user),
        assignee_ids: [0]

1236
      expect(response).to have_gitlab_http_status(200)
1237 1238 1239 1240 1241 1242 1243 1244

      expect(json_response['assignees']).to be_empty
    end

    it 'updates an issue with new assignee' do
      put api("/projects/#{project.id}/issues/#{issue.iid}", user),
        assignee_ids: [user2.id]

1245
      expect(response).to have_gitlab_http_status(200)
1246 1247 1248

      expect(json_response['assignees'].first['name']).to eq(user2.name)
    end
1249

1250
    context 'single assignee restrictions' do
1251
      it 'updates an issue with several assignees but only one has been applied' do
1252 1253 1254
        put api("/projects/#{project.id}/issues/#{issue.iid}", user),
          assignee_ids: [user2.id, guest.id]

1255
        expect(response).to have_gitlab_http_status(200)
1256 1257 1258 1259

        expect(json_response['assignees'].size).to eq(1)
      end
    end
1260 1261
  end

1262
  describe 'PUT /projects/:id/issues/:issue_iid to update labels' do
1263 1264 1265
    let!(:label) { create(:label, title: 'dummy', project: project) }
    let!(:label_link) { create(:label_link, label: label, target: issue) }

1266
    it 'does not update labels if not present' do
1267
      put api("/projects/#{project.id}/issues/#{issue.iid}", user),
1268
          title: 'updated title'
1269
      expect(response).to have_gitlab_http_status(200)
1270
      expect(json_response['labels']).to eq([label.title])
1271 1272
    end

1273
    it 'removes all labels' do
1274
      put api("/projects/#{project.id}/issues/#{issue.iid}", user), labels: ''
1275

1276
      expect(response).to have_gitlab_http_status(200)
1277
      expect(json_response['labels']).to eq([])
1278 1279
    end

1280
    it 'updates labels' do
1281
      put api("/projects/#{project.id}/issues/#{issue.iid}", user),
1282
          labels: 'foo,bar'
1283
      expect(response).to have_gitlab_http_status(200)
1284 1285
      expect(json_response['labels']).to include 'foo'
      expect(json_response['labels']).to include 'bar'
1286 1287
    end

1288
    it 'allows special label names' do
1289
      put api("/projects/#{project.id}/issues/#{issue.iid}", user),
1290 1291
          labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&'
      expect(response.status).to eq(200)
1292 1293 1294 1295
      expect(json_response['labels']).to include 'label:foo'
      expect(json_response['labels']).to include 'label-bar'
      expect(json_response['labels']).to include 'label_bar'
      expect(json_response['labels']).to include 'label/bar'
1296 1297 1298 1299
      expect(json_response['labels']).to include 'label?bar'
      expect(json_response['labels']).to include 'label&bar'
      expect(json_response['labels']).to include '?'
      expect(json_response['labels']).to include '&'
1300
    end
1301

1302
    it 'returns 400 if title is too long' do
1303
      put api("/projects/#{project.id}/issues/#{issue.iid}", user),
1304
          title: 'g' * 256
1305
      expect(response).to have_gitlab_http_status(400)
1306
      expect(json_response['message']['title']).to eq([
1307
        'is too long (maximum is 255 characters)'
1308
      ])
1309
    end
Andrew8xx8's avatar
Andrew8xx8 committed
1310 1311
  end

1312
  describe "PUT /projects/:id/issues/:issue_iid to update state and label" do
1313
    it "updates a project issue" do
1314
      put api("/projects/#{project.id}/issues/#{issue.iid}", user),
Andrew8xx8's avatar
Andrew8xx8 committed
1315
        labels: 'label2', state_event: "close"
1316
      expect(response).to have_gitlab_http_status(200)
Andrew8xx8's avatar
Andrew8xx8 committed
1317

1318 1319
      expect(json_response['labels']).to include 'label2'
      expect(json_response['state']).to eq "closed"
Nihad Abbasov's avatar
Nihad Abbasov committed
1320
    end
1321

1322
    it 'reopens a project isssue' do
1323
      put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), state_event: 'reopen'
1324

1325
      expect(response).to have_gitlab_http_status(200)
1326
      expect(json_response['state']).to eq 'opened'
1327 1328
    end

1329 1330 1331
    context 'when an admin or owner makes the request' do
      it 'accepts the update date to be set' do
        update_time = 2.weeks.ago
1332
        put api("/projects/#{project.id}/issues/#{issue.iid}", user),
1333 1334
          labels: 'label3', state_event: 'close', updated_at: update_time

1335
        expect(response).to have_gitlab_http_status(200)
1336
        expect(json_response['labels']).to include 'label3'
1337
        expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time)
1338 1339
      end
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
1340 1341
  end

1342
  describe 'PUT /projects/:id/issues/:issue_iid to update due date' do
1343 1344 1345
    it 'creates a new project issue' do
      due_date = 2.weeks.from_now.strftime('%Y-%m-%d')

1346
      put api("/projects/#{project.id}/issues/#{issue.iid}", user), due_date: due_date
1347

1348
      expect(response).to have_gitlab_http_status(200)
1349 1350 1351 1352
      expect(json_response['due_date']).to eq(due_date)
    end
  end

1353
  describe "DELETE /projects/:id/issues/:issue_iid" do
1354
    it "rejects a non member from deleting an issue" do
1355
      delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member)
1356
      expect(response).to have_gitlab_http_status(403)
1357 1358
    end

1359
    it "rejects a developer from deleting an issue" do
1360
      delete api("/projects/#{project.id}/issues/#{issue.iid}", author)
1361
      expect(response).to have_gitlab_http_status(403)
1362
    end
1363

1364 1365
    context "when the user is project owner" do
      let(:owner)     { create(:user) }
1366
      let(:project)   { create(:project, namespace: owner.namespace) }
1367 1368

      it "deletes the issue if an admin requests it" do
1369
        delete api("/projects/#{project.id}/issues/#{issue.iid}", owner)
1370

1371
        expect(response).to have_gitlab_http_status(204)
1372
      end
1373 1374 1375 1376

      it_behaves_like '412 response' do
        let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}", owner) }
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
1377
    end
Robert Schilling's avatar
Robert Schilling committed
1378 1379 1380 1381 1382

    context 'when issue does not exist' do
      it 'returns 404 when trying to move an issue' do
        delete api("/projects/#{project.id}/issues/123", user)

1383
        expect(response).to have_gitlab_http_status(404)
Robert Schilling's avatar
Robert Schilling committed
1384 1385
      end
    end
1386 1387 1388 1389

    it 'returns 404 when using the issue ID instead of IID' do
      delete api("/projects/#{project.id}/issues/#{issue.id}", user)

1390
      expect(response).to have_gitlab_http_status(404)
1391
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
1392
  end
1393

1394
  describe '/projects/:id/issues/:issue_iid/move' do
1395
    let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace ) }
1396
    let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) }
1397 1398

    it 'moves an issue' do
1399
      post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
1400
               to_project_id: target_project.id
1401

1402
      expect(response).to have_gitlab_http_status(201)
1403 1404 1405
      expect(json_response['project_id']).to eq(target_project.id)
    end

1406 1407
    context 'when source and target projects are the same' do
      it 'returns 400 when trying to move an issue' do
1408
        post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
1409
                 to_project_id: project.id
1410

1411
        expect(response).to have_gitlab_http_status(400)
1412 1413
        expect(json_response['message']).to eq('Cannot move issue to project it originates from!')
      end
1414 1415
    end

1416 1417
    context 'when the user does not have the permission to move issues' do
      it 'returns 400 when trying to move an issue' do
1418
        post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
1419
                 to_project_id: target_project2.id
1420

1421
        expect(response).to have_gitlab_http_status(400)
1422 1423
        expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!')
      end
1424 1425 1426
    end

    it 'moves the issue to another namespace if I am admin' do
1427
      post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin),
1428
               to_project_id: target_project2.id
1429

1430
      expect(response).to have_gitlab_http_status(201)
1431 1432 1433
      expect(json_response['project_id']).to eq(target_project2.id)
    end

1434 1435 1436 1437 1438
    context 'when using the issue ID instead of iid' do
      it 'returns 404 when trying to move an issue' do
        post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
             to_project_id: target_project.id

1439
        expect(response).to have_gitlab_http_status(404)
1440 1441 1442 1443
        expect(json_response['message']).to eq('404 Issue Not Found')
      end
    end

1444 1445 1446 1447
    context 'when issue does not exist' do
      it 'returns 404 when trying to move an issue' do
        post api("/projects/#{project.id}/issues/123/move", user),
                 to_project_id: target_project.id
1448

1449
        expect(response).to have_gitlab_http_status(404)
Robert Schilling's avatar
Robert Schilling committed
1450
        expect(json_response['message']).to eq('404 Issue Not Found')
1451
      end
1452 1453
    end

1454 1455
    context 'when source project does not exist' do
      it 'returns 404 when trying to move an issue' do
1456
        post api("/projects/0/issues/#{issue.iid}/move", user),
1457
                 to_project_id: target_project.id
1458

1459
        expect(response).to have_gitlab_http_status(404)
Robert Schilling's avatar
Robert Schilling committed
1460
        expect(json_response['message']).to eq('404 Project Not Found')
1461 1462 1463 1464 1465
      end
    end

    context 'when target project does not exist' do
      it 'returns 404 when trying to move an issue' do
1466
        post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
1467
                 to_project_id: 0
1468

1469
        expect(response).to have_gitlab_http_status(404)
1470
      end
1471 1472
    end
  end
1473

1474
  describe 'POST :id/issues/:issue_iid/subscribe' do
1475
    it 'subscribes to an issue' do
1476
      post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2)
1477

1478
      expect(response).to have_gitlab_http_status(201)
1479 1480 1481 1482
      expect(json_response['subscribed']).to eq(true)
    end

    it 'returns 304 if already subscribed' do
1483
      post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user)
1484

1485
      expect(response).to have_gitlab_http_status(304)
1486
    end
1487 1488

    it 'returns 404 if the issue is not found' do
1489
      post api("/projects/#{project.id}/issues/123/subscribe", user)
1490

1491
      expect(response).to have_gitlab_http_status(404)
1492
    end
1493

1494 1495 1496
    it 'returns 404 if the issue ID is used instead of the iid' do
      post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user)

1497
      expect(response).to have_gitlab_http_status(404)
1498 1499
    end

1500
    it 'returns 404 if the issue is confidential' do
1501
      post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/subscribe", non_member)
1502

1503
      expect(response).to have_gitlab_http_status(404)
1504
    end
1505 1506
  end

1507
  describe 'POST :id/issues/:issue_id/unsubscribe' do
1508
    it 'unsubscribes from an issue' do
1509
      post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user)
1510

1511
      expect(response).to have_gitlab_http_status(201)
1512 1513 1514 1515
      expect(json_response['subscribed']).to eq(false)
    end

    it 'returns 304 if not subscribed' do
1516
      post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user2)
1517

1518
      expect(response).to have_gitlab_http_status(304)
1519
    end
1520 1521

    it 'returns 404 if the issue is not found' do
1522
      post api("/projects/#{project.id}/issues/123/unsubscribe", user)
1523

1524
      expect(response).to have_gitlab_http_status(404)
1525
    end
1526

1527 1528 1529
    it 'returns 404 if using the issue ID instead of iid' do
      post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user)

1530
      expect(response).to have_gitlab_http_status(404)
1531 1532
    end

1533
    it 'returns 404 if the issue is confidential' do
1534
      post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/unsubscribe", non_member)
1535

1536
      expect(response).to have_gitlab_http_status(404)
1537
    end
1538
  end
1539 1540 1541 1542 1543 1544

  describe 'time tracking endpoints' do
    let(:issuable) { issue }

    include_examples 'time tracking endpoints', 'issue'
  end
1545

1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576
  describe 'GET :id/issues/:issue_iid/closed_by' do
    let(:merge_request) do
      create(:merge_request,
             :simple,
             author: user,
             source_project: project,
             target_project: project,
             description: "closes #{issue.to_reference}")
    end

    before do
      create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request)
    end

    it 'returns merge requests that will close issue on merge' do
      get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user)

      expect_paginated_array_response(size: 1)
    end

    context 'when no merge requests will close issue' do
      it 'returns empty array' do
        get api("/projects/#{project.id}/issues/#{closed_issue.iid}/closed_by", user)

        expect_paginated_array_response(size: 0)
      end
    end

    it "returns 404 when issue doesn't exists" do
      get api("/projects/#{project.id}/issues/9999/closed_by", user)

1577
      expect(response).to have_gitlab_http_status(404)
1578 1579 1580
    end
  end

1581
  describe "GET /projects/:id/issues/:issue_iid/user_agent_detail" do
1582
    let!(:user_agent_detail) { create(:user_agent_detail, subject: issue) }
1583 1584 1585 1586

    it 'exposes known attributes' do
      get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin)

1587
      expect(response).to have_gitlab_http_status(200)
1588 1589
      expect(json_response['user_agent']).to eq(user_agent_detail.user_agent)
      expect(json_response['ip_address']).to eq(user_agent_detail.ip_address)
1590
      expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
1591 1592 1593 1594 1595
    end

    it "returns unautorized for non-admin users" do
      get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", user)

1596
      expect(response).to have_gitlab_http_status(403)
1597 1598 1599
    end
  end

1600
  def expect_paginated_array_response(size: nil)
1601
    expect(response).to have_gitlab_http_status(200)
1602 1603 1604 1605
    expect(response).to include_pagination_headers
    expect(json_response).to be_an Array
    expect(json_response.length).to eq(size) if size
  end
1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617

  describe 'GET projects/:id/issues/:issue_iid/participants' do
    it_behaves_like 'issuable participants endpoint' do
      let(:entity) { issue }
    end

    it 'returns 404 if the issue is confidential' do
      post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/participants", non_member)

      expect(response).to have_gitlab_http_status(404)
    end
  end
Nihad Abbasov's avatar
Nihad Abbasov committed
1618
end