merge_requests_spec.rb 43.2 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 31 32 33 34 35 36 37 38 39 40 41 42 43
  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
      let!(:project2) { create(:empty_project, :public, namespace: user.namespace) }
      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 54 55 56

        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
        private_project = create(:empty_project, :private)
        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

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

Z
Z.J. van de Weg 已提交
128
        expect(response).to have_http_status(401)
V
Valeriy Sizov 已提交
129 130 131 132
      end
    end

    context "when authenticated" do
S
Stan Hu 已提交
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
H
Hiroyuki Sato 已提交
146
        get api("/projects/#{project.id}/merge_requests", user)
147

Z
Z.J. van de Weg 已提交
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)
V
Valeriy Sizov 已提交
164
      end
165

S
Stan Hu 已提交
166
      it "returns an array of all merge_requests using simple mode" do
167
        get api("/projects/#{project.id}/merge_requests?view=simple", user)
S
Stan Hu 已提交
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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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)
S
Stan Hu 已提交
309 310
          response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
          expect(response_dates).to eq(response_dates.sort)
311
        end
D
Dmitriy Zaporozhets 已提交
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

Z
Z.J. van de Weg 已提交
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)
S
Stan Hu 已提交
320 321
          response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
          expect(response_dates).to eq(response_dates.sort.reverse)
322
        end
D
Dmitriy Zaporozhets 已提交
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

Z
Z.J. van de Weg 已提交
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)
S
Stan Hu 已提交
331 332
          response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
          expect(response_dates).to eq(response_dates.sort.reverse)
333
        end
D
Dmitriy Zaporozhets 已提交
334

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

Z
Z.J. van de Weg 已提交
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)
S
Stan Hu 已提交
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
V
Valeriy Sizov 已提交
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

Z
Z.J. van de Weg 已提交
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
J
James Lopez 已提交
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)
Z
Z.J. van de Weg 已提交
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
V
Valeriy Sizov 已提交
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)
Z
Z.J. van de Weg 已提交
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)
Z
Z.J. van de Weg 已提交
406
        expect(response).to have_http_status(200)
407 408 409
        expect(json_response['work_in_progress']).to eq(true)
      end
    end
V
Valeriy Sizov 已提交
410 411
  end

412
  describe 'GET /projects/:id/merge_requests/:merge_request_iid/commits' do
V
Valery Sizov 已提交
413
    it 'returns a 200 when merge request is valid' do
414
      get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/commits", user)
V
Valery Sizov 已提交
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
V
Valery Sizov 已提交
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)
Z
Z.J. van de Weg 已提交
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)
Z
Z.J. van de Weg 已提交
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

V
Valeriy Sizov 已提交
457
  describe "POST /projects/:id/merge_requests" do
I
Izaak Alpert 已提交
458
    context 'between branches projects' do
459
      it "returns merge_request" do
H
Hiroyuki Sato 已提交
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

Z
Z.J. van de Weg 已提交
468
        expect(response).to have_http_status(201)
469
        expect(json_response['title']).to eq('Test merge_request')
D
Douwe Maan 已提交
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
I
Izaak Alpert 已提交
473
      end
474

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

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

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

493
      it "returns 400 when title is missing" do
H
Hiroyuki Sato 已提交
494
        post api("/projects/#{project.id}/merge_requests", user),
495
        target_branch: 'master', source_branch: 'markdown'
Z
Z.J. van de Weg 已提交
496
        expect(response).to have_http_status(400)
I
Izaak Alpert 已提交
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
J
jubianchi 已提交
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',
J
jubianchi 已提交
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
J
jubianchi 已提交
525 526 527
          expect do
            post api("/projects/#{project.id}/merge_requests", user),
                 title: 'New test merge_request',
528
                 source_branch: 'feature_conflict',
J
jubianchi 已提交
529 530 531
                 target_branch: 'master',
                 author: user
          end.to change { MergeRequest.count }.by(0)
Z
Z.J. van de Weg 已提交
532
          expect(response).to have_http_status(409)
J
jubianchi 已提交
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

I
Izaak Alpert 已提交
558
    context 'forked projects' do
D
Dmitriy Zaporozhets 已提交
559
      let!(:user2) { create(:user) }
560 561
      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 已提交
562 563

      before :each do |each|
564
        fork_project.team << [user2, :reporter]
I
Izaak Alpert 已提交
565 566
      end

567
      it "returns merge_request" do
H
Hiroyuki Sato 已提交
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'
Z
Z.J. van de Weg 已提交
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')
I
Izaak Alpert 已提交
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)
H
Hiroyuki Sato 已提交
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
Z
Z.J. van de Weg 已提交
582
        expect(response).to have_http_status(201)
583
        expect(json_response['title']).to eq('Test merge_request')
I
Izaak Alpert 已提交
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
H
Hiroyuki Sato 已提交
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
Z
Z.J. van de Weg 已提交
602
        expect(response).to have_http_status(400)
I
Izaak Alpert 已提交
603 604
      end

605
      it "returns 400 when target_branch is missing" do
H
Hiroyuki Sato 已提交
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
Z
Z.J. van de Weg 已提交
608
        expect(response).to have_http_status(400)
I
Izaak Alpert 已提交
609 610
      end

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

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

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

639
      it "returns 201 when target_branch is specified and for the same project" do
H
Hiroyuki Sato 已提交
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
Z
Z.J. van de Weg 已提交
642
        expect(response).to have_http_status(201)
I
Izaak Alpert 已提交
643
      end
644
    end
V
Valeriy Sizov 已提交
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) }
Z
Zeger-Jan van de Weg 已提交
650

651 652 653
      before do
        project.team << [developer, :developer]
      end
Z
Zeger-Jan van de Weg 已提交
654

655
      it "denies the deletion of the merge request" do
656
        delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", developer)
Z
Z.J. van de Weg 已提交
657
        expect(response).to have_http_status(403)
658
      end
Z
Zeger-Jan van de Weg 已提交
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
Z
Zeger-Jan van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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

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

701
    it "returns 405 if merge_request is not open" do
D
Dmitriy Zaporozhets 已提交
702
      merge_request.close
703
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
Z
Z.J. van de Weg 已提交
704
      expect(response).to have_http_status(405)
705
      expect(json_response['message']).to eq('405 Method Not Allowed')
D
Dmitriy Zaporozhets 已提交
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)
Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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
D
Dmitriy Zaporozhets 已提交
725 726
      user2 = create(:user)
      project.team << [user2, :reporter]
727
      put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user2)
Z
Z.J. van de Weg 已提交
728
      expect(response).to have_http_status(401)
729
      expect(json_response['message']).to eq('401 Unauthorized')
D
Dmitriy Zaporozhets 已提交
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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
751
      expect(response).to have_http_status(200)
752
      expect(json_response['title']).to eq('Test')
J
James Lopez 已提交
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
A
Andrew8xx8 已提交
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"
Z
Z.J. van de Weg 已提交
793
      expect(response).to have_http_status(200)
794
      expect(json_response['title']).to eq('New title')
V
Valeriy Sizov 已提交
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"
Z
Z.J. van de Weg 已提交
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
Z
Z.J. van de Weg 已提交
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"
Z
Z.J. van de Weg 已提交
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
V
Valeriy Sizov 已提交
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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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 890

    it 'handles external issues' do
      jira_project = create(:jira_project, :public, 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

Z
Z.J. van de Weg 已提交
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 912 913 914 915 916

    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]

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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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

Z
Z.J. van de Weg 已提交
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
V
Valeriy Sizov 已提交
1028
end