merge_requests_spec.rb 37.8 KB
Newer Older
V
Valeriy Sizov 已提交
1 2
require "spec_helper"

3
describe API::MergeRequests do
Z
Zeger-Jan van de Weg 已提交
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) }
Z
Zeger-Jan van de Weg 已提交
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
V
Valeriy Sizov 已提交
28 29 30

  describe "GET /projects/:id/merge_requests" do
    context "when unauthenticated" do
31
      it "returns authentication error" do
H
Hiroyuki Sato 已提交
32
        get api("/projects/#{project.id}/merge_requests")
33

Z
Z.J. van de Weg 已提交
34
        expect(response).to have_http_status(401)
V
Valeriy Sizov 已提交
35 36 37 38
      end
    end

    context "when authenticated" do
S
Stan Hu 已提交
39 40 41 42 43 44 45 46 47 48 49 50
      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

51
      it "returns an array of all merge_requests" do
H
Hiroyuki Sato 已提交
52
        get api("/projects/#{project.id}/merge_requests", user)
53

Z
Z.J. van de Weg 已提交
54
        expect(response).to have_http_status(200)
55
        expect(response).to include_pagination_headers
56 57 58
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(3)
        expect(json_response.last['title']).to eq(merge_request.title)
59
        expect(json_response.last).to have_key('web_url')
60 61 62
        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)
63 64 65
        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])
66 67 68 69
        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)
V
Valeriy Sizov 已提交
70
      end
71

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

Z
Z.J. van de Weg 已提交
75
        expect(response).to have_http_status(200)
76
        expect(response).to include_pagination_headers
77 78 79
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(3)
        expect(json_response.last['title']).to eq(merge_request.title)
80
      end
81

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

Z
Z.J. van de Weg 已提交
85
        expect(response).to have_http_status(200)
86
        expect(response).to include_pagination_headers
87 88 89
        expect(json_response).to be_an Array
        expect(json_response.length).to eq(1)
        expect(json_response.last['title']).to eq(merge_request.title)
90
      end
91

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

Z
Z.J. van de Weg 已提交
95
        expect(response).to have_http_status(200)
96
        expect(response).to include_pagination_headers
97
        expect(json_response).to be_an Array
98 99
        expect(json_response.length).to eq(1)
        expect(json_response.first['title']).to eq(merge_request_closed.title)
100
      end
101

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

Z
Z.J. van de Weg 已提交
105
        expect(response).to have_http_status(200)
106
        expect(response).to include_pagination_headers
107 108 109
        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)
110
      end
111

112 113 114 115 116 117 118 119 120 121
      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

122 123 124 125 126 127 128
      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

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
      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)
167
        expect(json_response.first['labels']).to eq([label2.title, label.title])
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
      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

186 187 188 189 190 191
      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

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

Z
Z.J. van de Weg 已提交
195
          expect(response).to have_http_status(200)
196
          expect(response).to include_pagination_headers
197 198
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(3)
S
Stan Hu 已提交
199 200
          response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
          expect(response_dates).to eq(response_dates.sort)
201
        end
D
Dmitriy Zaporozhets 已提交
202

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

Z
Z.J. van de Weg 已提交
206
          expect(response).to have_http_status(200)
207
          expect(response).to include_pagination_headers
208 209
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(3)
S
Stan Hu 已提交
210 211
          response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
          expect(response_dates).to eq(response_dates.sort.reverse)
212
        end
D
Dmitriy Zaporozhets 已提交
213

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

Z
Z.J. van de Weg 已提交
217
          expect(response).to have_http_status(200)
218
          expect(response).to include_pagination_headers
219 220
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(3)
S
Stan Hu 已提交
221 222
          response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
          expect(response_dates).to eq(response_dates.sort.reverse)
223
        end
D
Dmitriy Zaporozhets 已提交
224

225
        it "returns an array of merge_requests ordered by created_at" do
S
Stan Hu 已提交
226
          get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user)
227

Z
Z.J. van de Weg 已提交
228
          expect(response).to have_http_status(200)
229
          expect(response).to include_pagination_headers
230 231
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(3)
S
Stan Hu 已提交
232 233
          response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
          expect(response_dates).to eq(response_dates.sort)
234
        end
235
      end
V
Valeriy Sizov 已提交
236 237 238
    end
  end

239
  describe "GET /projects/:id/merge_requests/:merge_request_iid" do
240
    it 'exposes known attributes' do
241
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
242

Z
Z.J. van de Weg 已提交
243
      expect(response).to have_http_status(200)
244 245 246 247 248 249 250 251 252
      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)
253
      expect(json_response['milestone']).to be_a Hash
254 255 256 257
      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)
258 259
      expect(json_response['upvotes']).to eq(1)
      expect(json_response['downvotes']).to eq(1)
260 261 262
      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
J
James Lopez 已提交
263
      expect(json_response['merge_when_pipeline_succeeds']).to be_falsy
264
      expect(json_response['merge_status']).to eq('can_be_merged')
265 266
      expect(json_response['should_close_merge_request']).to be_falsy
      expect(json_response['force_close_merge_request']).to be_falsy
267 268
    end

269
    it "returns merge_request" do
270
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
Z
Z.J. van de Weg 已提交
271
      expect(response).to have_http_status(200)
272 273
      expect(json_response['title']).to eq(merge_request.title)
      expect(json_response['iid']).to eq(merge_request.iid)
274
      expect(json_response['work_in_progress']).to eq(false)
275
      expect(json_response['merge_status']).to eq('can_be_merged')
276 277
      expect(json_response['should_close_merge_request']).to be_falsy
      expect(json_response['force_close_merge_request']).to be_falsy
V
Valeriy Sizov 已提交
278
    end
279

280
    it "returns a 404 error if merge_request_iid not found" do
281
      get api("/projects/#{project.id}/merge_requests/999", user)
Z
Z.J. van de Weg 已提交
282
      expect(response).to have_http_status(404)
283
    end
284

285 286 287 288 289 290
    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

291 292 293
    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) }

294
      it "returns merge_request" do
295
        get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.iid}", user)
Z
Z.J. van de Weg 已提交
296
        expect(response).to have_http_status(200)
297 298 299
        expect(json_response['work_in_progress']).to eq(true)
      end
    end
V
Valeriy Sizov 已提交
300 301
  end

302
  describe 'GET /projects/:id/merge_requests/:merge_request_iid/commits' do
V
Valery Sizov 已提交
303
    it 'returns a 200 when merge request is valid' do
304
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/commits", user)
V
Valery Sizov 已提交
305 306 307
      commit = merge_request.commits.first

      expect(response.status).to eq 200
308 309
      expect(response).to include_pagination_headers
      expect(json_response).to be_an Array
V
Valery Sizov 已提交
310 311 312
      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)
313 314
    end

315
    it 'returns a 404 when merge_request_iid not found' do
316
      get api("/projects/#{project.id}/merge_requests/999/commits", user)
Z
Z.J. van de Weg 已提交
317
      expect(response).to have_http_status(404)
318
    end
319 320 321 322 323 324

    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
325 326
  end

327
  describe 'GET /projects/:id/merge_requests/:merge_request_iid/changes' do
328
    it 'returns the change information of the merge_request' do
329
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/changes", user)
330

331 332 333 334
      expect(response.status).to eq 200
      expect(json_response['changes'].size).to eq(merge_request.diffs.size)
    end

335
    it 'returns a 404 when merge_request_iid not found' do
336
      get api("/projects/#{project.id}/merge_requests/999/changes", user)
Z
Z.J. van de Weg 已提交
337
      expect(response).to have_http_status(404)
338
    end
339 340 341 342 343 344

    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
345 346
  end

V
Valeriy Sizov 已提交
347
  describe "POST /projects/:id/merge_requests" do
I
Izaak Alpert 已提交
348
    context 'between branches projects' do
349
      it "returns merge_request" do
H
Hiroyuki Sato 已提交
350
        post api("/projects/#{project.id}/merge_requests", user),
351
             title: 'Test merge_request',
352
             source_branch: 'feature_conflict',
353 354
             target_branch: 'master',
             author: user,
355
             labels: 'label, label2',
356
             milestone_id: milestone.id
357

Z
Z.J. van de Weg 已提交
358
        expect(response).to have_http_status(201)
359
        expect(json_response['title']).to eq('Test merge_request')
D
Douwe Maan 已提交
360
        expect(json_response['labels']).to eq(%w(label label2))
361
        expect(json_response['milestone']['id']).to eq(milestone.id)
362
        expect(json_response['force_remove_source_branch']).to be_falsy
I
Izaak Alpert 已提交
363
      end
364

365
      it "returns 422 when source_branch equals target_branch" do
H
Hiroyuki Sato 已提交
366
        post api("/projects/#{project.id}/merge_requests", user),
367
        title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
Z
Z.J. van de Weg 已提交
368
        expect(response).to have_http_status(422)
I
Izaak Alpert 已提交
369
      end
370

371
      it "returns 400 when source_branch is missing" do
H
Hiroyuki Sato 已提交
372
        post api("/projects/#{project.id}/merge_requests", user),
373
        title: "Test merge_request", target_branch: "master", author: user
Z
Z.J. van de Weg 已提交
374
        expect(response).to have_http_status(400)
I
Izaak Alpert 已提交
375
      end
376

377
      it "returns 400 when target_branch is missing" do
H
Hiroyuki Sato 已提交
378
        post api("/projects/#{project.id}/merge_requests", user),
379
        title: "Test merge_request", source_branch: "markdown", author: user
Z
Z.J. van de Weg 已提交
380
        expect(response).to have_http_status(400)
I
Izaak Alpert 已提交
381 382
      end

383
      it "returns 400 when title is missing" do
H
Hiroyuki Sato 已提交
384
        post api("/projects/#{project.id}/merge_requests", user),
385
        target_branch: 'master', source_branch: 'markdown'
Z
Z.J. van de Weg 已提交
386
        expect(response).to have_http_status(400)
I
Izaak Alpert 已提交
387
      end
388

389
      it 'allows special label names' do
390 391
        post api("/projects/#{project.id}/merge_requests", user),
             title: 'Test merge_request',
392
             source_branch: 'markdown',
393 394
             target_branch: 'master',
             author: user,
395 396 397 398 399 400 401
             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 '&'
402
      end
J
jubianchi 已提交
403 404 405 406 407

      context 'with existing MR' do
        before do
          post api("/projects/#{project.id}/merge_requests", user),
               title: 'Test merge_request',
408
               source_branch: 'feature_conflict',
J
jubianchi 已提交
409 410 411 412 413
               target_branch: 'master',
               author: user
          @mr = MergeRequest.all.last
        end

414
        it 'returns 409 when MR already exists for source/target' do
J
jubianchi 已提交
415 416 417
          expect do
            post api("/projects/#{project.id}/merge_requests", user),
                 title: 'New test merge_request',
418
                 source_branch: 'feature_conflict',
J
jubianchi 已提交
419 420 421
                 target_branch: 'master',
                 author: user
          end.to change { MergeRequest.count }.by(0)
Z
Z.J. van de Weg 已提交
422
          expect(response).to have_http_status(409)
J
jubianchi 已提交
423 424
        end
      end
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445

      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
446
    end
447

I
Izaak Alpert 已提交
448
    context 'forked projects' do
D
Dmitriy Zaporozhets 已提交
449
      let!(:user2) { create(:user) }
450 451
      let!(:fork_project) { create(:empty_project, forked_from_project: project,  namespace: user2.namespace, creator_id: user2.id) }
      let!(:unrelated_project) { create(:empty_project,  namespace: create(:user).namespace, creator_id: user2.id) }
I
Izaak Alpert 已提交
452 453

      before :each do |each|
454
        fork_project.team << [user2, :reporter]
I
Izaak Alpert 已提交
455 456
      end

457
      it "returns merge_request" do
H
Hiroyuki Sato 已提交
458
        post api("/projects/#{fork_project.id}/merge_requests", user2),
459 460
          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'
Z
Z.J. van de Weg 已提交
461
        expect(response).to have_http_status(201)
462 463
        expect(json_response['title']).to eq('Test merge_request')
        expect(json_response['description']).to eq('Test description for Test merge_request')
I
Izaak Alpert 已提交
464 465
      end

466
      it "does not return 422 when source_branch equals target_branch" do
467 468 469
        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)
H
Hiroyuki Sato 已提交
470
        post api("/projects/#{fork_project.id}/merge_requests", user2),
471
        title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
Z
Z.J. van de Weg 已提交
472
        expect(response).to have_http_status(201)
473
        expect(json_response['title']).to eq('Test merge_request')
I
Izaak Alpert 已提交
474 475
      end

476 477 478 479 480 481 482 483 484 485 486 487 488
      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

489
      it "returns 400 when source_branch is missing" do
H
Hiroyuki Sato 已提交
490
        post api("/projects/#{fork_project.id}/merge_requests", user2),
491
        title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
Z
Z.J. van de Weg 已提交
492
        expect(response).to have_http_status(400)
I
Izaak Alpert 已提交
493 494
      end

495
      it "returns 400 when target_branch is missing" do
H
Hiroyuki Sato 已提交
496
        post api("/projects/#{fork_project.id}/merge_requests", user2),
497
        title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
Z
Z.J. van de Weg 已提交
498
        expect(response).to have_http_status(400)
I
Izaak Alpert 已提交
499 500
      end

501
      it "returns 400 when title is missing" do
H
Hiroyuki Sato 已提交
502
        post api("/projects/#{fork_project.id}/merge_requests", user2),
503
        target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
Z
Z.J. van de Weg 已提交
504
        expect(response).to have_http_status(400)
I
Izaak Alpert 已提交
505 506
      end

J
jubianchi 已提交
507
      context 'when target_branch is specified' do
508
        it 'returns 422 if not a forked project' do
J
jubianchi 已提交
509 510 511
          post api("/projects/#{project.id}/merge_requests", user),
               title: 'Test merge_request',
               target_branch: 'master',
512
               source_branch: 'markdown',
J
jubianchi 已提交
513 514
               author: user,
               target_project_id: fork_project.id
Z
Z.J. van de Weg 已提交
515
          expect(response).to have_http_status(422)
J
jubianchi 已提交
516
        end
I
Izaak Alpert 已提交
517

518
        it 'returns 422 if targeting a different fork' do
J
jubianchi 已提交
519 520 521
          post api("/projects/#{fork_project.id}/merge_requests", user2),
               title: 'Test merge_request',
               target_branch: 'master',
522
               source_branch: 'markdown',
J
jubianchi 已提交
523 524
               author: user2,
               target_project_id: unrelated_project.id
Z
Z.J. van de Weg 已提交
525
          expect(response).to have_http_status(422)
J
jubianchi 已提交
526
        end
I
Izaak Alpert 已提交
527 528
      end

529
      it "returns 201 when target_branch is specified and for the same project" do
H
Hiroyuki Sato 已提交
530
        post api("/projects/#{fork_project.id}/merge_requests", user2),
531
        title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id
Z
Z.J. van de Weg 已提交
532
        expect(response).to have_http_status(201)
I
Izaak Alpert 已提交
533
      end
534
    end
V
Valeriy Sizov 已提交
535 536
  end

537
  describe "DELETE /projects/:id/merge_requests/:merge_request_iid" do
538 539
    context "when the user is developer" do
      let(:developer) { create(:user) }
Z
Zeger-Jan van de Weg 已提交
540

541 542 543
      before do
        project.team << [developer, :developer]
      end
Z
Zeger-Jan van de Weg 已提交
544

545
      it "denies the deletion of the merge request" do
546
        delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", developer)
Z
Z.J. van de Weg 已提交
547
        expect(response).to have_http_status(403)
548
      end
Z
Zeger-Jan van de Weg 已提交
549
    end
550

551 552
    context "when the user is project owner" do
      it "destroys the merge request owners can destroy" do
553
        delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
554

555
        expect(response).to have_http_status(204)
556
      end
557 558 559 560 561 562 563 564 565 566 567 568

      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
569
    end
Z
Zeger-Jan van de Weg 已提交
570 571
  end

572
  describe "PUT /projects/:id/merge_requests/:merge_request_iid/merge" do
573
    let(:pipeline) { create(:ci_pipeline_without_jobs) }
574

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

Z
Z.J. van de Weg 已提交
578
      expect(response).to have_http_status(200)
579 580
    end

581
    it "returns 406 if branch can't be merged" do
582 583
      allow_any_instance_of(MergeRequest)
        .to receive(:can_be_merged?).and_return(false)
584

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

Z
Z.J. van de Weg 已提交
587
      expect(response).to have_http_status(406)
588
      expect(json_response['message']).to eq('Branch cannot be merged')
A
Andrew8xx8 已提交
589
    end
D
Dmitriy Zaporozhets 已提交
590

591
    it "returns 405 if merge_request is not open" do
D
Dmitriy Zaporozhets 已提交
592
      merge_request.close
593
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
Z
Z.J. van de Weg 已提交
594
      expect(response).to have_http_status(405)
595
      expect(json_response['message']).to eq('405 Method Not Allowed')
D
Dmitriy Zaporozhets 已提交
596 597
    end

598
    it "returns 405 if merge_request is a work in progress" do
599
      merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
600
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
Z
Z.J. van de Weg 已提交
601
      expect(response).to have_http_status(405)
602 603 604
      expect(json_response['message']).to eq('405 Method Not Allowed')
    end

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

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

Z
Z.J. van de Weg 已提交
610
      expect(response).to have_http_status(405)
611 612 613
      expect(json_response['message']).to eq('405 Method Not Allowed')
    end

614
    it "returns 401 if user has no permissions to merge" do
D
Dmitriy Zaporozhets 已提交
615 616
      user2 = create(:user)
      project.team << [user2, :reporter]
617
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user2)
Z
Z.J. van de Weg 已提交
618
      expect(response).to have_http_status(401)
619
      expect(json_response['message']).to eq('401 Unauthorized')
D
Dmitriy Zaporozhets 已提交
620
    end
621

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

Z
Z.J. van de Weg 已提交
625
      expect(response).to have_http_status(409)
626 627 628 629
      expect(json_response['message']).to start_with('SHA does not match HEAD of source branch')
    end

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

Z
Z.J. van de Weg 已提交
632
      expect(response).to have_http_status(200)
633 634
    end

635
    it "enables merge when pipeline succeeds if the pipeline is active" do
636
      allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
637
      allow(pipeline).to receive(:active?).and_return(true)
638

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

Z
Z.J. van de Weg 已提交
641
      expect(response).to have_http_status(200)
642
      expect(json_response['title']).to eq('Test')
J
James Lopez 已提交
643
      expect(json_response['merge_when_pipeline_succeeds']).to eq(true)
644
    end
645

646 647 648 649 650 651 652 653 654 655 656 657
    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

658 659 660 661 662 663 664 665 666 667 668
    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
A
Andrew8xx8 已提交
669 670
  end

671
  describe "PUT /projects/:id/merge_requests/:merge_request_iid" do
672 673
    context "to close a MR" do
      it "returns merge_request" do
674
        put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: "close"
675 676 677 678 679 680

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

681
    it "updates title and returns merge_request" do
682
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), title: "New title"
Z
Z.J. van de Weg 已提交
683
      expect(response).to have_http_status(200)
684
      expect(json_response['title']).to eq('New title')
V
Valeriy Sizov 已提交
685
    end
686

687
    it "updates description and returns merge_request" do
688
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), description: "New description"
Z
Z.J. van de Weg 已提交
689
      expect(response).to have_http_status(200)
690
      expect(json_response['description']).to eq('New description')
691 692
    end

693
    it "updates milestone_id and returns merge_request" do
694
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), milestone_id: milestone.id
Z
Z.J. van de Weg 已提交
695
      expect(response).to have_http_status(200)
696 697 698
      expect(json_response['milestone']['id']).to eq(milestone.id)
    end

699
    it "returns merge_request with renamed target_branch" do
700
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), target_branch: "wiki"
Z
Z.J. van de Weg 已提交
701
      expect(response).to have_http_status(200)
702
      expect(json_response['target_branch']).to eq('wiki')
703
    end
704

705
    it "returns merge_request that removes the source branch" do
706
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), remove_source_branch: true
707 708 709 710 711

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

712
    it 'allows special label names' do
713
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
714 715 716
        title: 'new issue',
        labels: 'label, label?, label&foo, ?, &'

717 718 719 720 721 722
      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 '&'
723
    end
724 725

    it 'does not update state when title is empty' do
726
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', title: nil
727 728 729 730 731 732 733

      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
734
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', target_branch: nil
735 736 737 738 739

      merge_request.reload
      expect(response).to have_http_status(400)
      expect(merge_request.state).to eq('opened')
    end
740 741 742 743 744 745 746 747 748 749 750 751

    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
V
Valeriy Sizov 已提交
752 753
  end

754
  describe 'GET :id/merge_requests/:merge_request_iid/closes_issues' do
755 756 757 758 759
    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
760

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

Z
Z.J. van de Weg 已提交
763
      expect(response).to have_http_status(200)
764
      expect(response).to include_pagination_headers
765 766 767 768 769
      expect(json_response).to be_an Array
      expect(json_response.length).to eq(1)
      expect(json_response.first['id']).to eq(issue.id)
    end

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

Z
Z.J. van de Weg 已提交
773
      expect(response).to have_http_status(200)
774
      expect(response).to include_pagination_headers
775 776 777
      expect(json_response).to be_an Array
      expect(json_response.length).to eq(0)
    end
778 779 780 781 782 783 784

    it 'handles external issues' do
      jira_project = create(:jira_project, :public, name: 'JIR_EXT1')
      issue = ExternalIssue.new("#{jira_project.name}-123", jira_project)
      merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project)
      merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}")

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

Z
Z.J. van de Weg 已提交
787
      expect(response).to have_http_status(200)
788
      expect(response).to include_pagination_headers
789 790 791 792 793
      expect(json_response).to be_an Array
      expect(json_response.length).to eq(1)
      expect(json_response.first['title']).to eq(issue.title)
      expect(json_response.first['id']).to eq(issue.id)
    end
794 795 796 797 798 799 800

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

801
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", guest)
802 803 804

      expect(response).to have_http_status(403)
    end
805 806 807 808 809 810 811 812 813 814 815 816

    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
817 818
  end

819
  describe 'POST :id/merge_requests/:merge_request_iid/subscribe' do
820
    it 'subscribes to a merge request' do
821
      post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", admin)
822

Z
Z.J. van de Weg 已提交
823
      expect(response).to have_http_status(201)
824 825 826 827
      expect(json_response['subscribed']).to eq(true)
    end

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

Z
Z.J. van de Weg 已提交
830
      expect(response).to have_http_status(304)
831
    end
832 833

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

Z
Z.J. van de Weg 已提交
836
      expect(response).to have_http_status(404)
837
    end
838

839 840 841 842 843 844
    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

845 846 847 848
    it 'returns 403 if user has no access to read code' do
      guest = create(:user)
      project.team << [guest, :guest]

849
      post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", guest)
850 851 852

      expect(response).to have_http_status(403)
    end
853 854
  end

855
  describe 'POST :id/merge_requests/:merge_request_iid/unsubscribe' do
856
    it 'unsubscribes from a merge request' do
857
      post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", user)
858

859
      expect(response).to have_http_status(201)
860 861 862 863
      expect(json_response['subscribed']).to eq(false)
    end

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

Z
Z.J. van de Weg 已提交
866
      expect(response).to have_http_status(304)
867
    end
868 869

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

Z
Z.J. van de Weg 已提交
872
      expect(response).to have_http_status(404)
873
    end
874

875 876 877 878 879 880
    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

881 882 883 884
    it 'returns 403 if user has no access to read code' do
      guest = create(:user)
      project.team << [guest, :guest]

885
      post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", guest)
886 887 888

      expect(response).to have_http_status(403)
    end
889 890
  end

891 892 893 894 895 896
  describe 'Time tracking' do
    let(:issuable) { merge_request }

    include_examples 'time tracking endpoints', 'merge_request'
  end

897 898 899 900 901 902 903 904 905 906 907 908 909 910 911
  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
V
Valeriy Sizov 已提交
912
end