BigW Consortium Gitlab

merge_requests_spec.rb 43.2 KB
Newer Older
1 2
require "spec_helper"

3
describe API::MergeRequests do
4 5 6
  let(:base_time)   { Time.now }
  let(:user)        { create(:user) }
  let(:admin)       { create(:user, :admin) }
7
  let(:non_member)  { create(:user) }
8
  let!(:project)    { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) }
9
  let(:milestone)   { create(:milestone, title: '1.0.0', project: project) }
10 11 12 13 14 15 16 17 18
  let(:milestone1)   { create(:milestone, title: '0.9', project: project) }
  let!(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
  let!(:merge_request_closed) { create(:merge_request, state: "closed", milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
  let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') }
  let!(:note)       { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
  let!(:note2)      { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
  let!(:label) do
    create(:label, title: 'label', color: '#FFAABB', project: project)
  end
19
  let!(:label2) { create(:label, title: 'a-test', color: '#FFFFFF', project: project) }
20
  let!(:label_link) { create(:label_link, label: label, target: merge_request) }
21 22 23
  let!(:label_link2) { create(:label_link, label: label2, target: merge_request) }
  let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) }
  let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) }
24 25

  before do
26
    project.team << [user, :reporter]
27
  end
28

29 30 31 32 33 34 35 36 37 38
  describe 'GET /merge_requests' do
    context 'when unauthenticated' do
      it 'returns authentication error' do
        get api('/merge_requests')

        expect(response).to have_http_status(401)
      end
    end

    context 'when authenticated' do
39
      let!(:project2) { create(:project, :public, namespace: user.namespace) }
40 41 42 43
      let!(:merge_request2) { create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2) }
      let(:user2) { create(:user) }

      it 'returns an array of all merge requests' do
44
        get api('/merge_requests', user), scope: :all
45 46 47 48 49 50 51 52 53

        expect(response).to have_http_status(200)
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.map { |mr| mr['id'] })
          .to contain_exactly(merge_request.id, merge_request_closed.id, merge_request_merged.id, merge_request2.id)
      end

      it 'does not return unauthorized merge requests' do
54
        private_project = create(:project, :private)
55 56
        merge_request3 = create(:merge_request, :simple, source_project: private_project, target_project: private_project, source_branch: 'other-branch')

57
        get api('/merge_requests', user), scope: :all
58 59 60 61 62 63 64 65

        expect(response).to have_http_status(200)
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.map { |mr| mr['id'] })
          .not_to include(merge_request3.id)
      end

66 67 68 69 70 71 72 73 74 75 76
      it 'returns an array of merge requests created by current user if no scope is given' do
        merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')

        get api('/merge_requests', user2)

        expect(response).to have_http_status(200)
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(1)
        expect(json_response.first['id']).to eq(merge_request3.id)
      end

77 78 79
      it 'returns an array of merge requests authored by the given user' do
        merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')

80
        get api('/merge_requests', user), author_id: user2.id, scope: :all
81 82 83 84 85 86 87 88 89 90

        expect(response).to have_http_status(200)
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(1)
        expect(json_response.first['id']).to eq(merge_request3.id)
      end

      it 'returns an array of merge requests assigned to the given user' do
        merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch')

91
        get api('/merge_requests', user), assignee_id: user2.id, scope: :all
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122

        expect(response).to have_http_status(200)
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(1)
        expect(json_response.first['id']).to eq(merge_request3.id)
      end

      it 'returns an array of merge requests assigned to me' do
        merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch')

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

        expect(response).to have_http_status(200)
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(1)
        expect(json_response.first['id']).to eq(merge_request3.id)
      end

      it 'returns an array of merge requests created by me' do
        merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')

        get api('/merge_requests', user2), scope: 'created-by-me'

        expect(response).to have_http_status(200)
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(1)
        expect(json_response.first['id']).to eq(merge_request3.id)
      end
    end
  end

123 124
  describe "GET /projects/:id/merge_requests" do
    context "when unauthenticated" do
125
      it "returns authentication error" do
Hiroyuki Sato committed
126
        get api("/projects/#{project.id}/merge_requests")
127

128
        expect(response).to have_http_status(401)
129 130 131 132
      end
    end

    context "when authenticated" do
Stan Hu committed
133 134 135 136 137 138 139 140 141 142 143 144
      it 'avoids N+1 queries' do
        control_count = ActiveRecord::QueryRecorder.new do
          get api("/projects/#{project.id}/merge_requests", user)
        end.count

        create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time)

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

145
      it "returns an array of all merge_requests" do
Hiroyuki Sato committed
146
        get api("/projects/#{project.id}/merge_requests", user)
147

148
        expect(response).to have_http_status(200)
149
        expect(response).to include_pagination_headers
150 151 152
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(3)
        expect(json_response.last['title']).to eq(merge_request.title)
153
        expect(json_response.last).to have_key('web_url')
154 155 156
        expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
        expect(json_response.last['merge_commit_sha']).to be_nil
        expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
157 158 159
        expect(json_response.last['downvotes']).to eq(1)
        expect(json_response.last['upvotes']).to eq(1)
        expect(json_response.last['labels']).to eq([label2.title, label.title])
160 161 162 163
        expect(json_response.first['title']).to eq(merge_request_merged.title)
        expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
        expect(json_response.first['merge_commit_sha']).not_to be_nil
        expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
164
      end
165

166
      it "returns an array of all merge_requests using simple mode" do
167
        get api("/projects/#{project.id}/merge_requests?view=simple", user)
168 169 170 171 172 173 174 175 176 177 178 179 180 181

        expect(response).to have_http_status(200)
        expect(response).to include_pagination_headers
        expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at))
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(3)
        expect(json_response.last['iid']).to eq(merge_request.iid)
        expect(json_response.last['title']).to eq(merge_request.title)
        expect(json_response.last).to have_key('web_url')
        expect(json_response.first['iid']).to eq(merge_request_merged.iid)
        expect(json_response.first['title']).to eq(merge_request_merged.title)
        expect(json_response.first).to have_key('web_url')
      end

182
      it "returns an array of all merge_requests" do
183
        get api("/projects/#{project.id}/merge_requests?state", user)
184

185
        expect(response).to have_http_status(200)
186
        expect(response).to include_pagination_headers
187 188 189
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(3)
        expect(json_response.last['title']).to eq(merge_request.title)
190
      end
191

192
      it "returns an array of open merge_requests" do
193
        get api("/projects/#{project.id}/merge_requests?state=opened", user)
194

195
        expect(response).to have_http_status(200)
196
        expect(response).to include_pagination_headers
197 198 199
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(1)
        expect(json_response.last['title']).to eq(merge_request.title)
200
      end
201

202
      it "returns an array of closed merge_requests" do
203
        get api("/projects/#{project.id}/merge_requests?state=closed", user)
204

205
        expect(response).to have_http_status(200)
206
        expect(response).to include_pagination_headers
207
        expect(json_response).to be_an Array
208 209
        expect(json_response.length).to eq(1)
        expect(json_response.first['title']).to eq(merge_request_closed.title)
210
      end
211

212
      it "returns an array of merged merge_requests" do
213
        get api("/projects/#{project.id}/merge_requests?state=merged", user)
214

215
        expect(response).to have_http_status(200)
216
        expect(response).to include_pagination_headers
217 218 219
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(1)
        expect(json_response.first['title']).to eq(merge_request_merged.title)
220
      end
221

222 223 224 225 226 227 228 229 230 231
      it 'returns merge_request by "iids" array' do
        get api("/projects/#{project.id}/merge_requests", user), iids: [merge_request.iid, merge_request_closed.iid]

        expect(response).to have_http_status(200)
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(2)
        expect(json_response.first['title']).to eq merge_request_closed.title
        expect(json_response.first['id']).to eq merge_request_closed.id
      end

232 233 234 235 236 237 238
      it 'matches V4 response schema' do
        get api("/projects/#{project.id}/merge_requests", user)

        expect(response).to have_http_status(200)
        expect(response).to match_response_schema('public_api/v4/merge_requests')
      end

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
      it 'returns an empty array if no issue matches milestone' do
        get api("/projects/#{project.id}/merge_requests", user), milestone: '1.0.0'

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

      it 'returns an empty array if milestone does not exist' do
        get api("/projects/#{project.id}/merge_requests", user), milestone: 'foo'

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

      it 'returns an array of merge requests in given milestone' do
        get api("/projects/#{project.id}/merge_requests", user), milestone: '0.9'

        expect(json_response.first['title']).to eq merge_request_closed.title
        expect(json_response.first['id']).to eq merge_request_closed.id
      end

      it 'returns an array of merge requests matching state in milestone' do
        get api("/projects/#{project.id}/merge_requests", user), milestone: '0.9', state: 'closed'

        expect(response).to have_http_status(200)
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(1)
        expect(json_response.first['id']).to eq(merge_request_closed.id)
      end

      it 'returns an array of labeled merge requests' do
        get api("/projects/#{project.id}/merge_requests?labels=#{label.title}", user)

        expect(response).to have_http_status(200)
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(1)
277
        expect(json_response.first['labels']).to eq([label2.title, label.title])
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
      end

      it 'returns an array of labeled merge requests where all labels match' do
        get api("/projects/#{project.id}/merge_requests?labels=#{label.title},foo,bar", user)

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

      it 'returns an empty array if no merge request matches labels' do
        get api("/projects/#{project.id}/merge_requests?labels=foo,bar", user)

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

296 297 298 299 300 301
      context "with ordering" do
        before do
          @mr_later = mr_with_later_created_and_updated_at_time
          @mr_earlier = mr_with_earlier_created_and_updated_at_time
        end

302
        it "returns an array of merge_requests in ascending order" do
303
          get api("/projects/#{project.id}/merge_requests?sort=asc", user)
304

305
          expect(response).to have_http_status(200)
306
          expect(response).to include_pagination_headers
307 308
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(3)
309 310
          response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
          expect(response_dates).to eq(response_dates.sort)
311
        end
Dmitriy Zaporozhets committed
312

313
        it "returns an array of merge_requests in descending order" do
314
          get api("/projects/#{project.id}/merge_requests?sort=desc", user)
315

316
          expect(response).to have_http_status(200)
317
          expect(response).to include_pagination_headers
318 319
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(3)
320 321
          response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
          expect(response_dates).to eq(response_dates.sort.reverse)
322
        end
Dmitriy Zaporozhets committed
323

324
        it "returns an array of merge_requests ordered by updated_at" do
325
          get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user)
326

327
          expect(response).to have_http_status(200)
328
          expect(response).to include_pagination_headers
329 330
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(3)
331 332
          response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
          expect(response_dates).to eq(response_dates.sort.reverse)
333
        end
Dmitriy Zaporozhets committed
334

335
        it "returns an array of merge_requests ordered by created_at" do
336
          get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user)
337

338
          expect(response).to have_http_status(200)
339
          expect(response).to include_pagination_headers
340 341
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(3)
342 343
          response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
          expect(response_dates).to eq(response_dates.sort)
344
        end
345
      end
346 347 348
    end
  end

349
  describe "GET /projects/:id/merge_requests/:merge_request_iid" do
350
    it 'exposes known attributes' do
351
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
352

353
      expect(response).to have_http_status(200)
354 355 356 357 358 359 360 361 362
      expect(json_response['id']).to eq(merge_request.id)
      expect(json_response['iid']).to eq(merge_request.iid)
      expect(json_response['project_id']).to eq(merge_request.project.id)
      expect(json_response['title']).to eq(merge_request.title)
      expect(json_response['description']).to eq(merge_request.description)
      expect(json_response['state']).to eq(merge_request.state)
      expect(json_response['created_at']).to be_present
      expect(json_response['updated_at']).to be_present
      expect(json_response['labels']).to eq(merge_request.label_names)
363
      expect(json_response['milestone']).to be_a Hash
364 365 366 367
      expect(json_response['assignee']).to be_a Hash
      expect(json_response['author']).to be_a Hash
      expect(json_response['target_branch']).to eq(merge_request.target_branch)
      expect(json_response['source_branch']).to eq(merge_request.source_branch)
368 369
      expect(json_response['upvotes']).to eq(1)
      expect(json_response['downvotes']).to eq(1)
370 371 372
      expect(json_response['source_project_id']).to eq(merge_request.source_project.id)
      expect(json_response['target_project_id']).to eq(merge_request.target_project.id)
      expect(json_response['work_in_progress']).to be_falsy
373
      expect(json_response['merge_when_pipeline_succeeds']).to be_falsy
374
      expect(json_response['merge_status']).to eq('can_be_merged')
375 376
      expect(json_response['should_close_merge_request']).to be_falsy
      expect(json_response['force_close_merge_request']).to be_falsy
377 378
    end

379
    it "returns merge_request" do
380
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
381
      expect(response).to have_http_status(200)
382 383
      expect(json_response['title']).to eq(merge_request.title)
      expect(json_response['iid']).to eq(merge_request.iid)
384
      expect(json_response['work_in_progress']).to eq(false)
385
      expect(json_response['merge_status']).to eq('can_be_merged')
386 387
      expect(json_response['should_close_merge_request']).to be_falsy
      expect(json_response['force_close_merge_request']).to be_falsy
388
    end
389

390
    it "returns a 404 error if merge_request_iid not found" do
391
      get api("/projects/#{project.id}/merge_requests/999", user)
392
      expect(response).to have_http_status(404)
393
    end
394

395 396 397 398 399 400
    it "returns a 404 error if merge_request `id` is used instead of iid" do
      get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)

      expect(response).to have_http_status(404)
    end

401 402 403
    context 'Work in Progress' do
      let!(:merge_request_wip) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) }

404
      it "returns merge_request" do
405
        get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.iid}", user)
406
        expect(response).to have_http_status(200)
407 408 409
        expect(json_response['work_in_progress']).to eq(true)
      end
    end
410 411
  end

412
  describe 'GET /projects/:id/merge_requests/:merge_request_iid/commits' do
Valery Sizov committed
413
    it 'returns a 200 when merge request is valid' do
414
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/commits", user)
Valery Sizov committed
415 416 417
      commit = merge_request.commits.first

      expect(response.status).to eq 200
418 419
      expect(response).to include_pagination_headers
      expect(json_response).to be_an Array
Valery Sizov committed
420 421 422
      expect(json_response.size).to eq(merge_request.commits.size)
      expect(json_response.first['id']).to eq(commit.id)
      expect(json_response.first['title']).to eq(commit.title)
423 424
    end

425
    it 'returns a 404 when merge_request_iid not found' do
426
      get api("/projects/#{project.id}/merge_requests/999/commits", user)
427
      expect(response).to have_http_status(404)
428
    end
429 430 431 432 433 434

    it 'returns a 404 when merge_request id is used instead of iid' do
      get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user)

      expect(response).to have_http_status(404)
    end
435 436
  end

437
  describe 'GET /projects/:id/merge_requests/:merge_request_iid/changes' do
438
    it 'returns the change information of the merge_request' do
439
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/changes", user)
440

441 442 443 444
      expect(response.status).to eq 200
      expect(json_response['changes'].size).to eq(merge_request.diffs.size)
    end

445
    it 'returns a 404 when merge_request_iid not found' do
446
      get api("/projects/#{project.id}/merge_requests/999/changes", user)
447
      expect(response).to have_http_status(404)
448
    end
449 450 451 452 453 454

    it 'returns a 404 when merge_request id is used instead of iid' do
      get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)

      expect(response).to have_http_status(404)
    end
455 456
  end

457
  describe "POST /projects/:id/merge_requests" do
458
    context 'between branches projects' do
459
      it "returns merge_request" do
Hiroyuki Sato committed
460
        post api("/projects/#{project.id}/merge_requests", user),
461
             title: 'Test merge_request',
462
             source_branch: 'feature_conflict',
463 464
             target_branch: 'master',
             author: user,
465
             labels: 'label, label2',
466
             milestone_id: milestone.id
467

468
        expect(response).to have_http_status(201)
469
        expect(json_response['title']).to eq('Test merge_request')
Douwe Maan committed
470
        expect(json_response['labels']).to eq(%w(label label2))
471
        expect(json_response['milestone']['id']).to eq(milestone.id)
472
        expect(json_response['force_remove_source_branch']).to be_falsy
473
      end
474

475
      it "returns 422 when source_branch equals target_branch" do
Hiroyuki Sato committed
476
        post api("/projects/#{project.id}/merge_requests", user),
477
        title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
478
        expect(response).to have_http_status(422)
479
      end
480

481
      it "returns 400 when source_branch is missing" do
Hiroyuki Sato committed
482
        post api("/projects/#{project.id}/merge_requests", user),
483
        title: "Test merge_request", target_branch: "master", author: user
484
        expect(response).to have_http_status(400)
485
      end
486

487
      it "returns 400 when target_branch is missing" do
Hiroyuki Sato committed
488
        post api("/projects/#{project.id}/merge_requests", user),
489
        title: "Test merge_request", source_branch: "markdown", author: user
490
        expect(response).to have_http_status(400)
491 492
      end

493
      it "returns 400 when title is missing" do
Hiroyuki Sato committed
494
        post api("/projects/#{project.id}/merge_requests", user),
495
        target_branch: 'master', source_branch: 'markdown'
496
        expect(response).to have_http_status(400)
497
      end
498

499
      it 'allows special label names' do
500 501
        post api("/projects/#{project.id}/merge_requests", user),
             title: 'Test merge_request',
502
             source_branch: 'markdown',
503 504
             target_branch: 'master',
             author: user,
505 506 507 508 509 510 511
             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 '&'
512
      end
513 514 515 516 517

      context 'with existing MR' do
        before do
          post api("/projects/#{project.id}/merge_requests", user),
               title: 'Test merge_request',
518
               source_branch: 'feature_conflict',
519 520 521 522 523
               target_branch: 'master',
               author: user
          @mr = MergeRequest.all.last
        end

524
        it 'returns 409 when MR already exists for source/target' do
525 526 527
          expect do
            post api("/projects/#{project.id}/merge_requests", user),
                 title: 'New test merge_request',
528
                 source_branch: 'feature_conflict',
529 530 531
                 target_branch: 'master',
                 author: user
          end.to change { MergeRequest.count }.by(0)
532
          expect(response).to have_http_status(409)
533 534
        end
      end
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555

      context 'accepts remove_source_branch parameter' do
        let(:params) do
          { title: 'Test merge_request',
            source_branch: 'markdown',
            target_branch: 'master',
            author: user }
        end

        it 'sets force_remove_source_branch to false' do
          post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: false)

          expect(json_response['force_remove_source_branch']).to be_falsy
        end

        it 'sets force_remove_source_branch to true' do
          post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: true)

          expect(json_response['force_remove_source_branch']).to be_truthy
        end
      end
556
    end
557

558
    context 'forked projects' do
Dmitriy Zaporozhets committed
559
      let!(:user2) { create(:user) }
560 561
      let!(:fork_project) { create(:project, forked_from_project: project,  namespace: user2.namespace, creator_id: user2.id) }
      let!(:unrelated_project) { create(:project,  namespace: create(:user).namespace, creator_id: user2.id) }
562 563

      before :each do |each|
564
        fork_project.team << [user2, :reporter]
565 566
      end

567
      it "returns merge_request" do
Hiroyuki Sato committed
568
        post api("/projects/#{fork_project.id}/merge_requests", user2),
569 570
          title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
          author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
571
        expect(response).to have_http_status(201)
572 573
        expect(json_response['title']).to eq('Test merge_request')
        expect(json_response['description']).to eq('Test description for Test merge_request')
574 575
      end

576
      it "does not return 422 when source_branch equals target_branch" do
577 578 579
        expect(project.id).not_to eq(fork_project.id)
        expect(fork_project.forked?).to be_truthy
        expect(fork_project.forked_from_project).to eq(project)
Hiroyuki Sato committed
580
        post api("/projects/#{fork_project.id}/merge_requests", user2),
581
        title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
582
        expect(response).to have_http_status(201)
583
        expect(json_response['title']).to eq('Test merge_request')
584 585
      end

586 587 588 589 590 591 592 593 594 595 596 597 598
      it 'returns 422 when target project has disabled merge requests' do
        project.project_feature.update(merge_requests_access_level: 0)

        post api("/projects/#{fork_project.id}/merge_requests", user2),
             title: 'Test',
             target_branch: 'master',
             source_branch: 'markdown',
             author: user2,
             target_project_id: project.id

        expect(response).to have_http_status(422)
      end

599
      it "returns 400 when source_branch is missing" do
Hiroyuki Sato committed
600
        post api("/projects/#{fork_project.id}/merge_requests", user2),
601
        title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
602
        expect(response).to have_http_status(400)
603 604
      end

605
      it "returns 400 when target_branch is missing" do
Hiroyuki Sato committed
606
        post api("/projects/#{fork_project.id}/merge_requests", user2),
607
        title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
608
        expect(response).to have_http_status(400)
609 610
      end

611
      it "returns 400 when title is missing" do
Hiroyuki Sato committed
612
        post api("/projects/#{fork_project.id}/merge_requests", user2),
613
        target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
614
        expect(response).to have_http_status(400)
615 616
      end

617
      context 'when target_branch is specified' do
618
        it 'returns 422 if not a forked project' do
619 620 621
          post api("/projects/#{project.id}/merge_requests", user),
               title: 'Test merge_request',
               target_branch: 'master',
622
               source_branch: 'markdown',
623 624
               author: user,
               target_project_id: fork_project.id
625
          expect(response).to have_http_status(422)
626
        end
Izaak Alpert committed
627

628
        it 'returns 422 if targeting a different fork' do
629 630 631
          post api("/projects/#{fork_project.id}/merge_requests", user2),
               title: 'Test merge_request',
               target_branch: 'master',
632
               source_branch: 'markdown',
633 634
               author: user2,
               target_project_id: unrelated_project.id
635
          expect(response).to have_http_status(422)
636
        end
Izaak Alpert committed
637 638
      end

639
      it "returns 201 when target_branch is specified and for the same project" do
Hiroyuki Sato committed
640
        post api("/projects/#{fork_project.id}/merge_requests", user2),
641
        title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id
642
        expect(response).to have_http_status(201)
Izaak Alpert committed
643
      end
644
    end
645 646
  end

647
  describe "DELETE /projects/:id/merge_requests/:merge_request_iid" do
648 649
    context "when the user is developer" do
      let(:developer) { create(:user) }
650

651 652 653
      before do
        project.team << [developer, :developer]
      end
654

655
      it "denies the deletion of the merge request" do
656
        delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", developer)
657
        expect(response).to have_http_status(403)
658
      end
659
    end
660

661 662
    context "when the user is project owner" do
      it "destroys the merge request owners can destroy" do
663
        delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
664

665
        expect(response).to have_http_status(204)
666
      end
667 668 669 670 671 672 673 674 675 676 677 678

      it "returns 404 for an invalid merge request IID" do
        delete api("/projects/#{project.id}/merge_requests/12345", user)

        expect(response).to have_http_status(404)
      end

      it "returns 404 if the merge request id is used instead of iid" do
        delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)

        expect(response).to have_http_status(404)
      end
679
    end
680 681
  end

682
  describe "PUT /projects/:id/merge_requests/:merge_request_iid/merge" do
683
    let(:pipeline) { create(:ci_pipeline_without_jobs) }
684

685
    it "returns merge_request in case of success" do
686
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
687

688
      expect(response).to have_http_status(200)
689 690
    end

691
    it "returns 406 if branch can't be merged" do
692 693
      allow_any_instance_of(MergeRequest)
        .to receive(:can_be_merged?).and_return(false)
694

695
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
696

697
      expect(response).to have_http_status(406)
698
      expect(json_response['message']).to eq('Branch cannot be merged')
699
    end
700

701
    it "returns 405 if merge_request is not open" do
702
      merge_request.close
703
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
704
      expect(response).to have_http_status(405)
705
      expect(json_response['message']).to eq('405 Method Not Allowed')
706 707
    end

708
    it "returns 405 if merge_request is a work in progress" do
709
      merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
710
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
711
      expect(response).to have_http_status(405)
712 713 714
      expect(json_response['message']).to eq('405 Method Not Allowed')
    end

715
    it 'returns 405 if the build failed for a merge request that requires success' do
716
      allow_any_instance_of(MergeRequest).to receive(:mergeable_ci_state?).and_return(false)
717

718
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
719

720
      expect(response).to have_http_status(405)
721 722 723
      expect(json_response['message']).to eq('405 Method Not Allowed')
    end

724
    it "returns 401 if user has no permissions to merge" do
725 726
      user2 = create(:user)
      project.team << [user2, :reporter]
727
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user2)
728
      expect(response).to have_http_status(401)
729
      expect(json_response['message']).to eq('401 Unauthorized')
730
    end
731

732
    it "returns 409 if the SHA parameter doesn't match" do
733
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha.reverse
734

735
      expect(response).to have_http_status(409)
736 737 738 739
      expect(json_response['message']).to start_with('SHA does not match HEAD of source branch')
    end

    it "succeeds if the SHA parameter matches" do
740
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha
741

742
      expect(response).to have_http_status(200)
743 744
    end

745
    it "enables merge when pipeline succeeds if the pipeline is active" do
746
      allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
747
      allow(pipeline).to receive(:active?).and_return(true)
748

749
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true
750

751
      expect(response).to have_http_status(200)
752
      expect(json_response['title']).to eq('Test')
753
      expect(json_response['merge_when_pipeline_succeeds']).to eq(true)
754
    end
755

756 757 758 759 760 761 762 763 764 765 766 767
    it "enables merge when pipeline succeeds if the pipeline is active and only_allow_merge_if_pipeline_succeeds is true" do
      allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
      allow(pipeline).to receive(:active?).and_return(true)
      project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true)

      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true

      expect(response).to have_http_status(200)
      expect(json_response['title']).to eq('Test')
      expect(json_response['merge_when_pipeline_succeeds']).to eq(true)
    end

768 769 770 771 772 773 774 775 776 777 778
    it "returns 404 for an invalid merge request IID" do
      put api("/projects/#{project.id}/merge_requests/12345/merge", user)

      expect(response).to have_http_status(404)
    end

    it "returns 404 if the merge request id is used instead of iid" do
      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)

      expect(response).to have_http_status(404)
    end
779 780
  end

781
  describe "PUT /projects/:id/merge_requests/:merge_request_iid" do
782 783
    context "to close a MR" do
      it "returns merge_request" do
784
        put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: "close"
785 786 787 788 789 790

        expect(response).to have_http_status(200)
        expect(json_response['state']).to eq('closed')
      end
    end

791
    it "updates title and returns merge_request" do
792
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), title: "New title"
793
      expect(response).to have_http_status(200)
794
      expect(json_response['title']).to eq('New title')
795
    end
796

797
    it "updates description and returns merge_request" do
798
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), description: "New description"
799
      expect(response).to have_http_status(200)
800
      expect(json_response['description']).to eq('New description')
801 802
    end

803
    it "updates milestone_id and returns merge_request" do
804
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), milestone_id: milestone.id
805
      expect(response).to have_http_status(200)
806 807 808
      expect(json_response['milestone']['id']).to eq(milestone.id)
    end

809
    it "returns merge_request with renamed target_branch" do
810
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), target_branch: "wiki"
811
      expect(response).to have_http_status(200)
812
      expect(json_response['target_branch']).to eq('wiki')
813
    end
814

815
    it "returns merge_request that removes the source branch" do
816
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), remove_source_branch: true
817 818 819 820 821

      expect(response).to have_http_status(200)
      expect(json_response['force_remove_source_branch']).to be_truthy
    end

822
    it 'allows special label names' do
823
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
824 825 826
        title: 'new issue',
        labels: 'label, label?, label&foo, ?, &'

827 828 829 830 831 832
      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 '&'
833
    end
834 835

    it 'does not update state when title is empty' do
836
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', title: nil
837 838 839 840 841 842 843

      merge_request.reload
      expect(response).to have_http_status(400)
      expect(merge_request.state).to eq('opened')
    end

    it 'does not update state when target_branch is empty' do
844
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', target_branch: nil
845 846 847 848 849

      merge_request.reload
      expect(response).to have_http_status(400)
      expect(merge_request.state).to eq('opened')
    end
850 851 852 853 854 855 856 857 858 859 860 861

    it "returns 404 for an invalid merge request IID" do
      put api("/projects/#{project.id}/merge_requests/12345", user), state_event: "close"

      expect(response).to have_http_status(404)
    end

    it "returns 404 if the merge request id is used instead of iid" do
      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"

      expect(response).to have_http_status(404)
    end
862 863
  end

864
  describe 'GET :id/merge_requests/:merge_request_iid/closes_issues' do
865 866 867 868 869
    it 'returns the issue that will be closed on merge' do
      issue = create(:issue, project: project)
      mr = merge_request.tap do |mr|
        mr.update_attribute(:description, "Closes #{issue.to_reference(mr.project)}")
      end
870

871
      get api("/projects/#{project.id}/merge_requests/#{mr.iid}/closes_issues", user)
872

873
      expect(response).to have_http_status(200)
874
      expect(response).to include_pagination_headers
875 876 877 878 879
      expect(json_response).to be_an Array
      expect(json_response.length).to eq(1)
      expect(json_response.first['id']).to eq(issue.id)
    end

880
    it 'returns an empty array when there are no issues to be closed' do
881
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
882

883
      expect(response).to have_http_status(200)
884
      expect(response).to include_pagination_headers
885 886 887
      expect(json_response).to be_an Array
      expect(json_response.length).to eq(0)
    end
888 889

    it 'handles external issues' do
890
      jira_project = create(:jira_project, :public, :repository, name: 'JIR_EXT1')
891 892 893 894 895
      ext_issue = ExternalIssue.new("#{jira_project.name}-123", jira_project)
      issue = create(:issue, project: jira_project)
      description = "Closes #{ext_issue.to_reference(jira_project)}\ncloses #{issue.to_reference}"
      merge_request = create(:merge_request,
        :simple, author: user, assignee: user, source_project: jira_project, description: description)
896

897
      get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
898

899
      expect(response).to have_http_status(200)
900
      expect(response).to include_pagination_headers
901
      expect(json_response).to be_an Array
902 903 904 905
      expect(json_response.length).to eq(2)
      expect(json_response.second['title']).to eq(ext_issue.title)
      expect(json_response.second['id']).to eq(ext_issue.id)
      expect(json_response.second['confidential']).to be_nil
906 907
      expect(json_response.first['title']).to eq(issue.title)
      expect(json_response.first['id']).to eq(issue.id)
908
      expect(json_response.first['confidential']).not_to be_nil
909
    end
910 911

    it 'returns 403 if the user has no access to the merge request' do
912
      project = create(:project, :private)
913 914 915 916
      merge_request = create(:merge_request, :simple, source_project: project)
      guest = create(:user)
      project.team << [guest, :guest]

917
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", guest)
918 919 920

      expect(response).to have_http_status(403)
    end
921 922 923 924 925 926 927 928 929 930 931 932

    it "returns 404 for an invalid merge request IID" do
      get api("/projects/#{project.id}/merge_requests/12345/closes_issues", user)

      expect(response).to have_http_status(404)
    end

    it "returns 404 if the merge request id is used instead of iid" do
      get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)

      expect(response).to have_http_status(404)
    end
933 934
  end

935
  describe 'POST :id/merge_requests/:merge_request_iid/subscribe' do
936
    it 'subscribes to a merge request' do
937
      post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", admin)
938

939
      expect(response).to have_http_status(201)
940 941 942 943
      expect(json_response['subscribed']).to eq(true)
    end

    it 'returns 304 if already subscribed' do
944
      post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", user)
945

946
      expect(response).to have_http_status(304)
947
    end
948 949

    it 'returns 404 if the merge request is not found' do
950
      post api("/projects/#{project.id}/merge_requests/123/subscribe", user)
951

952
      expect(response).to have_http_status(404)
953
    end
954

955 956 957 958 959 960
    it 'returns 404 if the merge request id is used instead of iid' do
      post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user)

      expect(response).to have_http_status(404)
    end

961 962 963 964
    it 'returns 403 if user has no access to read code' do
      guest = create(:user)
      project.team << [guest, :guest]

965
      post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", guest)
966 967 968

      expect(response).to have_http_status(403)
    end
969 970
  end

971
  describe 'POST :id/merge_requests/:merge_request_iid/unsubscribe' do
972
    it 'unsubscribes from a merge request' do
973
      post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", user)
974

975
      expect(response).to have_http_status(201)
976 977 978 979
      expect(json_response['subscribed']).to eq(false)
    end

    it 'returns 304 if not subscribed' do
980
      post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", admin)
981

982
      expect(response).to have_http_status(304)
983
    end
984 985

    it 'returns 404 if the merge request is not found' do
986
      post api("/projects/#{project.id}/merge_requests/123/unsubscribe", user)
987

988
      expect(response).to have_http_status(404)
989
    end
990

991 992 993 994 995 996
    it 'returns 404 if the merge request id is used instead of iid' do
      post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user)

      expect(response).to have_http_status(404)
    end

997 998 999 1000
    it 'returns 403 if user has no access to read code' do
      guest = create(:user)
      project.team << [guest, :guest]

1001
      post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", guest)
1002 1003 1004

      expect(response).to have_http_status(403)
    end
1005 1006
  end

1007 1008 1009 1010 1011 1012
  describe 'Time tracking' do
    let(:issuable) { merge_request }

    include_examples 'time tracking endpoints', 'merge_request'
  end

1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
  def mr_with_later_created_and_updated_at_time
    merge_request
    merge_request.created_at += 1.hour
    merge_request.updated_at += 30.minutes
    merge_request.save
    merge_request
  end

  def mr_with_earlier_created_and_updated_at_time
    merge_request_closed
    merge_request_closed.created_at -= 1.hour
    merge_request_closed.updated_at -= 30.minutes
    merge_request_closed.save
    merge_request_closed
  end
1028
end