BigW Consortium Gitlab

merge_requests_controller_spec.rb 22.5 KB
Newer Older
1 2
require 'spec_helper'

3
describe Projects::MergeRequestsController do
4 5
  include ProjectForksHelper

6
  let(:project) { create(:project, :repository) }
7
  let(:user)    { project.owner }
8
  let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
9
  let(:merge_request_with_conflicts) do
10
    create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr|
11 12 13
      mr.mark_as_unmergeable
    end
  end
14 15 16 17 18

  before do
    sign_in(user)
  end

19 20 21 22 23 24 25 26 27 28 29 30
  describe 'GET commit_change_content' do
    it 'renders commit_change_content template' do
      get :commit_change_content,
        namespace_id: project.namespace.to_param,
        project_id: project,
        id: merge_request.iid,
        format: 'html'

      expect(response).to render_template('_commit_change_content')
    end
  end

31 32 33 34
  shared_examples "loads labels" do |action|
    it "loads labels into the @labels variable" do
      get action,
          namespace_id: project.namespace.to_param,
35
          project_id: project,
36 37 38 39 40 41
          id: merge_request.iid,
          format: 'html'
      expect(assigns(:labels)).not_to be_nil
    end
  end

42
  describe "GET show" do
43 44 45 46 47 48
    def go(extra_params = {})
      params = {
        namespace_id: project.namespace.to_param,
        project_id: project,
        id: merge_request.iid
      }
49

50 51
      get :show, params.merge(extra_params)
    end
52

53
    it_behaves_like "loads labels", :show
54

55 56 57
    describe 'as html' do
      it "renders merge request page" do
        go(format: :html)
58

59
        expect(response).to be_success
60
      end
micael.bergeron committed
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

      context "loads notes" do
        let(:first_contributor) { create(:user) }
        let(:contributor) { create(:user) }
        let(:merge_request) { create(:merge_request, author: first_contributor, target_project: project, source_project: project) }
        let(:contributor_merge_request) { create(:merge_request, :merged, author: contributor, target_project: project, source_project: project) }
        # the order here is important
        # as the controller reloads these from DB, references doesn't correspond after
        let!(:first_contributor_note) { create(:note, author: first_contributor, noteable: merge_request, project: project) }
        let!(:contributor_note) { create(:note, author: contributor, noteable: merge_request, project: project) }
        let!(:owner_note) { create(:note, author: user, noteable: merge_request, project: project) }

        it "with special_role FIRST_TIME_CONTRIBUTOR" do
          go(format: :html)

          notes = assigns(:notes)
          expect(notes).to match(a_collection_containing_exactly(an_object_having_attributes(special_role: Note::SpecialRole::FIRST_TIME_CONTRIBUTOR),
                                                                 an_object_having_attributes(special_role: nil),
                                                                 an_object_having_attributes(special_role: nil)
                                                                ))
        end
      end
83
    end
84

85
    describe 'as json' do
86
      context 'with basic serializer param' do
87
        it 'renders basic MR entity as json' do
88
          go(serializer: 'basic', format: :json)
89

90 91
          expect(response).to match_response_schema('entities/merge_request_basic')
        end
92 93
      end

94 95 96
      context 'with widget serializer param' do
        it 'renders widget MR entity as json' do
          go(serializer: 'widget', format: :json)
97

98
          expect(response).to match_response_schema('entities/merge_request_widget')
99
        end
100
      end
101 102 103 104 105 106 107 108

      context 'when no serialiser was passed' do
        it 'renders widget MR entity as json' do
          go(serializer: nil, format: :json)

          expect(response).to match_response_schema('entities/merge_request_widget')
        end
      end
109 110 111
    end

    describe "as diff" do
112
      it "triggers workhorse to serve the request" do
113
        go(format: :diff)
114

Douwe Maan committed
115
        expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-diff:")
116 117 118 119
      end
    end

    describe "as patch" do
120
      it 'triggers workhorse to serve the request' do
121
        go(format: :patch)
122

Douwe Maan committed
123
        expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-format-patch:")
124 125 126
      end
    end
  end
127

128
  describe 'GET index' do
129
    let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
130

131
    def get_merge_requests(page = nil)
132 133
      get :index,
          namespace_id: project.namespace.to_param,
134
          project_id: project,
135 136 137
          state: 'opened', page: page.to_param
    end

138 139
    it_behaves_like "issuables list meta-data", :merge_request

140
    context 'when page param' do
141 142 143
      let(:last_page) { project.merge_requests.page().total_pages }
      let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }

144 145 146
      it 'redirects to last_page if page number is larger than number of pages' do
        get_merge_requests(last_page + 1)

147
        expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
148 149 150 151 152 153
      end

      it 'redirects to specified page' do
        get_merge_requests(last_page)

        expect(assigns(:merge_requests).current_page).to eq(last_page)
154
        expect(response).to have_gitlab_http_status(200)
155
      end
156 157 158 159 160 161 162 163 164 165 166 167

      it 'does not redirect to external sites when provided a host field' do
        external_host = "www.example.com"
        get :index,
          namespace_id: project.namespace.to_param,
          project_id: project,
          state: 'opened',
          page: (last_page + 1).to_param,
          host: external_host

        expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
      end
168 169 170 171
    end

    context 'when filtering by opened state' do
      context 'with opened merge requests' do
172
        it 'lists those merge requests' do
173 174
          expect(merge_request).to be_persisted

175 176 177 178 179 180 181 182 183 184 185 186
          get_merge_requests

          expect(assigns(:merge_requests)).to include(merge_request)
        end
      end

      context 'with reopened merge requests' do
        before do
          merge_request.close!
          merge_request.reopen!
        end

187
        it 'lists those merge requests' do
188 189 190 191 192 193 194 195
          get_merge_requests

          expect(assigns(:merge_requests)).to include(merge_request)
        end
      end
    end
  end

196
  describe 'PUT update' do
197 198 199 200 201 202 203 204 205 206 207
    def update_merge_request(mr_params, additional_params = {})
      params = {
        namespace_id: project.namespace,
        project_id: project,
        id: merge_request.iid,
        merge_request: mr_params
      }.merge(additional_params)

      put :update, params
    end

208 209 210 211 212
    context 'changing the assignee' do
      it 'limits the attributes exposed on the assignee' do
        assignee = create(:user)
        project.add_developer(assignee)

213
        update_merge_request({ assignee_id: assignee.id }, format: :json)
214 215 216 217 218 219 220
        body = JSON.parse(response.body)

        expect(body['assignee'].keys)
          .to match_array(%w(name username avatar_url))
      end
    end

221 222 223 224 225 226 227 228 229 230 231 232 233 234
    context 'when user does not have access to update issue' do
      before do
        reporter = create(:user)
        project.add_reporter(reporter)
        sign_in(reporter)
      end

      it 'responds with 404' do
        update_merge_request(title: 'New title')

        expect(response).to have_http_status(:not_found)
      end
    end

235
    context 'there is no source project' do
236
      let(:project)       { create(:project, :repository) }
237 238
      let(:forked_project)  { fork_project_with_submodules(project) }
      let!(:merge_request) { create(:merge_request, source_project: forked_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
239 240

      before do
241
        forked_project.destroy
242 243 244
      end

      it 'closes MR without errors' do
245
        update_merge_request(state_event: 'close')
246 247 248 249

        expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
        expect(merge_request.reload.closed?).to be_truthy
      end
250

Katarzyna Kobierska committed
251
      it 'allows editing of a closed merge request' do
252 253
        merge_request.close!

254
        update_merge_request(title: 'New title')
255 256 257 258 259

        expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
        expect(merge_request.reload.title).to eq 'New title'
      end

Katarzyna Kobierska committed
260
      it 'does not allow to update target branch closed merge request' do
261 262
        merge_request.close!

263
        update_merge_request(target_branch: 'new_branch')
264 265 266

        expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch }
      end
267 268

      it_behaves_like 'update invalid issuable', MergeRequest
269 270 271
    end
  end

272
  describe 'POST merge' do
273 274
    let(:base_params) do
      {
275 276
        namespace_id: project.namespace,
        project_id: project,
277
        id: merge_request.iid,
278
        format: 'json'
279 280 281
      }
    end

282
    context 'when user cannot access' do
283 284
      let(:user) { create(:user) }

285
      before do
286 287
        project.add_reporter(user)
        xhr :post, :merge, base_params
288 289
      end

290
      it 'returns 404' do
291
        expect(response).to have_gitlab_http_status(404)
292 293 294 295 296 297 298 299 300 301 302
      end
    end

    context 'when the merge request is not mergeable' do
      before do
        merge_request.update_attributes(title: "WIP: #{merge_request.title}")

        post :merge, base_params
      end

      it 'returns :failed' do
303
        expect(json_response).to eq('status' => 'failed')
304 305 306 307
      end
    end

    context 'when the sha parameter does not match the source SHA' do
308 309 310
      before do
        post :merge, base_params.merge(sha: 'foo')
      end
311 312

      it 'returns :sha_mismatch' do
313
        expect(json_response).to eq('status' => 'sha_mismatch')
314 315 316 317 318
      end
    end

    context 'when the sha parameter matches the source SHA' do
      def merge_with_sha
319
        post :merge, base_params.merge(sha: merge_request.diff_head_sha)
320 321 322 323 324
      end

      it 'returns :success' do
        merge_with_sha

325
        expect(json_response).to eq('status' => 'success')
326 327 328 329 330 331 332 333
      end

      it 'starts the merge immediately' do
        expect(MergeWorker).to receive(:perform_async).with(merge_request.id, anything, anything)

        merge_with_sha
      end

334
      context 'when the pipeline succeeds is passed' do
335 336
        let!(:head_pipeline) do
          create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch, head_pipeline_of: merge_request)
337 338
        end

339 340
        def merge_when_pipeline_succeeds
          post :merge, base_params.merge(sha: merge_request.diff_head_sha, merge_when_pipeline_succeeds: '1')
341 342
        end

343 344
        it 'returns :merge_when_pipeline_succeeds' do
          merge_when_pipeline_succeeds
345

346
          expect(json_response).to eq('status' => 'merge_when_pipeline_succeeds')
347 348
        end

349 350
        it 'sets the MR to merge when the pipeline succeeds' do
          service = double(:merge_when_pipeline_succeeds_service)
351

352 353 354
          expect(MergeRequests::MergeWhenPipelineSucceedsService)
            .to receive(:new).with(project, anything, anything)
            .and_return(service)
355 356
          expect(service).to receive(:execute).with(merge_request)

357
          merge_when_pipeline_succeeds
358
        end
359

360
        context 'when project.only_allow_merge_if_pipeline_succeeds? is true' do
361
          before do
362
            project.update_column(:only_allow_merge_if_pipeline_succeeds, true)
363 364
          end

365 366 367 368 369 370 371 372 373 374 375 376
          context 'and head pipeline is not the current one' do
            before do
              head_pipeline.update(sha: 'not_current_sha')
            end

            it 'returns :failed' do
              merge_when_pipeline_succeeds

              expect(json_response).to eq('status' => 'failed')
            end
          end

377 378
          it 'returns :merge_when_pipeline_succeeds' do
            merge_when_pipeline_succeeds
379

380
            expect(json_response).to eq('status' => 'merge_when_pipeline_succeeds')
381 382
          end
        end
383
      end
384

385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
      describe 'only_allow_merge_if_all_discussions_are_resolved? setting' do
        let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }

        context 'when enabled' do
          before do
            project.update_column(:only_allow_merge_if_all_discussions_are_resolved, true)
          end

          context 'with unresolved discussion' do
            before do
              expect(merge_request).not_to be_discussions_resolved
            end

            it 'returns :failed' do
              merge_with_sha

401
              expect(json_response).to eq('status' => 'failed')
402 403
            end
          end
404

405 406 407 408 409
          context 'with all discussions resolved' do
            before do
              merge_request.discussions.each { |d| d.resolve!(user) }
              expect(merge_request).to be_discussions_resolved
            end
410

411 412
            it 'returns :success' do
              merge_with_sha
413

414
              expect(json_response).to eq('status' => 'success')
415
            end
416 417 418
          end
        end

419 420 421 422 423 424 425 426 427 428 429 430 431
        context 'when disabled' do
          before do
            project.update_column(:only_allow_merge_if_all_discussions_are_resolved, false)
          end

          context 'with unresolved discussion' do
            before do
              expect(merge_request).not_to be_discussions_resolved
            end

            it 'returns :success' do
              merge_with_sha

432
              expect(json_response).to eq('status' => 'success')
433 434 435 436 437 438 439 440
            end
          end

          context 'with all discussions resolved' do
            before do
              merge_request.discussions.each { |d| d.resolve!(user) }
              expect(merge_request).to be_discussions_resolved
            end
441

442 443
            it 'returns :success' do
              merge_with_sha
444

445
              expect(json_response).to eq('status' => 'success')
446
            end
447 448 449
          end
        end
      end
450 451 452
    end
  end

453
  describe "DELETE destroy" do
454 455
    let(:user) { create(:user) }

456
    it "denies access to users unless they're admin or project owner" do
457
      delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
458

459
      expect(response).to have_gitlab_http_status(404)
460 461
    end

462 463 464
    context "when the user is owner" do
      let(:owner)     { create(:user) }
      let(:namespace) { create(:namespace, owner: owner) }
465
      let(:project)   { create(:project, :repository, namespace: namespace) }
466

467 468 469
      before do
        sign_in owner
      end
470

471
      it "deletes the merge request" do
472
        delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
473

474
        expect(response).to have_gitlab_http_status(302)
475
        expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./)
476
      end
477 478

      it 'delegates the update of the todos count cache to TodoService' do
479
        expect_any_instance_of(TodoService).to receive(:destroy_target).with(merge_request).once
480

481
        delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
482
      end
483 484 485
    end
  end

486
  describe 'GET commits' do
487
    def go(format: 'html')
488 489
      get :commits,
          namespace_id: project.namespace.to_param,
490
          project_id: project,
491 492
          id: merge_request.iid,
          format: format
493 494
    end

495 496
    it 'renders the commits template to a string' do
      go format: 'json'
497

498 499
      expect(response).to render_template('projects/merge_requests/_commits')
      expect(json_response).to have_key('html')
500 501
    end
  end
502

503
  describe 'GET pipelines' do
504 505 506 507
    before do
      create(:ci_pipeline, project: merge_request.source_project,
                           ref: merge_request.source_branch,
                           sha: merge_request.diff_head_sha)
508

509 510 511 512 513
      get :pipelines,
          namespace_id: project.namespace.to_param,
          project_id: project,
          id: merge_request.iid,
          format: :json
514 515
    end

516
    it 'responds with serialized pipelines' do
517 518
      expect(json_response['pipelines']).not_to be_empty
      expect(json_response['count']['all']).to eq 1
519 520
    end
  end
521

522 523
  describe 'POST remove_wip' do
    before do
524 525 526
      merge_request.title = merge_request.wip_title
      merge_request.save

527 528 529 530 531 532
      xhr :post, :remove_wip,
        namespace_id: merge_request.project.namespace.to_param,
        project_id: merge_request.project,
        id: merge_request.iid,
        format: :json
    end
533

534
    it 'removes the wip status' do
535
      expect(merge_request.reload.title).to eq(merge_request.wipless_title)
536
    end
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563

    it 'renders MergeRequest as JSON' do
      expect(json_response.keys).to include('id', 'iid', 'description')
    end
  end

  describe 'POST cancel_merge_when_pipeline_succeeds' do
    subject do
      xhr :post, :cancel_merge_when_pipeline_succeeds,
        namespace_id: merge_request.project.namespace.to_param,
        project_id: merge_request.project,
        id: merge_request.iid,
        format: :json
    end

    it 'calls MergeRequests::MergeWhenPipelineSucceedsService' do
      mwps_service = double

      allow(MergeRequests::MergeWhenPipelineSucceedsService)
        .to receive(:new)
        .and_return(mwps_service)

      expect(mwps_service).to receive(:cancel).with(merge_request)

      subject
    end

564
    it { is_expected.to have_gitlab_http_status(:success) }
565 566 567 568 569 570

    it 'renders MergeRequest as JSON' do
      subject

      expect(json_response.keys).to include('id', 'iid', 'description')
    end
571 572
  end

573 574 575 576 577 578 579 580 581 582 583 584
  describe 'POST assign_related_issues' do
    let(:issue1) { create(:issue, project: project) }
    let(:issue2) { create(:issue, project: project) }

    def post_assign_issues
      merge_request.update!(description: "Closes #{issue1.to_reference} and #{issue2.to_reference}",
                            author: user,
                            source_branch: 'feature',
                            target_branch: 'master')

      post :assign_related_issues,
           namespace_id: project.namespace.to_param,
585
           project_id: project,
586 587 588 589 590 591 592 593 594 595
           id: merge_request.iid
    end

    it 'shows a flash message on success' do
      post_assign_issues

      expect(flash[:notice]).to eq '2 issues have been assigned to you'
    end

    it 'correctly pluralizes flash message on success' do
596
      issue2.assignees = [user]
597 598 599 600 601

      post_assign_issues

      expect(flash[:notice]).to eq '1 issue has been assigned to you'
    end
602 603

    it 'calls MergeRequests::AssignIssuesService' do
604 605 606
      expect(MergeRequests::AssignIssuesService).to receive(:new)
        .with(project, user, merge_request: merge_request)
        .and_return(double(execute: { count: 1 }))
607 608 609 610 611 612 613 614 615 616 617 618

      post_assign_issues
    end

    it 'is skipped when not signed in' do
      project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
      sign_out(:user)

      expect(MergeRequests::AssignIssuesService).not_to receive(:new)

      post_assign_issues
    end
619
  end
620 621

  describe 'GET ci_environments_status' do
622
    context 'the environment is from a forked project' do
623
      let!(:forked)       { fork_project(project, user, repository: true) }
624 625 626 627 628 629 630 631 632 633 634
      let!(:environment)  { create(:environment, project: forked) }
      let!(:deployment)   { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
      let(:admin)         { create(:admin) }

      let(:merge_request) do
        create(:merge_request, source_project: forked, target_project: project)
      end

      before do
        get :ci_environments_status,
          namespace_id: merge_request.project.namespace.to_param,
635
          project_id: merge_request.project,
636 637 638 639
          id: merge_request.iid, format: 'json'
      end

      it 'links to the environment on that project' do
640
        expect(json_response.first['url']).to match /#{forked.full_path}/
641 642 643
      end
    end
  end
644

645
  describe 'GET pipeline_status.json' do
646 647
    context 'when head_pipeline exists' do
      let!(:pipeline) do
Shinya Maeda committed
648
        create(:ci_pipeline, project: merge_request.source_project,
Shinya Maeda committed
649
                             ref: merge_request.source_branch,
650 651
                             sha: merge_request.diff_head_sha,
                             head_pipeline_of: merge_request)
Shinya Maeda committed
652 653
      end

654 655
      let(:status) { pipeline.detailed_status(double('user')) }

Felipe Artur committed
656 657 658
      before do
        get_pipeline_status
      end
659 660

      it 'return a detailed head_pipeline status in json' do
661
        expect(response).to have_gitlab_http_status(:ok)
Shinya Maeda committed
662 663 664
        expect(json_response['text']).to eq status.text
        expect(json_response['label']).to eq status.label
        expect(json_response['icon']).to eq status.icon
665
        expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.ico"
Shinya Maeda committed
666 667
      end
    end
668 669

    context 'when head_pipeline does not exist' do
670 671 672
      before do
        get_pipeline_status
      end
673 674

      it 'return empty' do
675
        expect(response).to have_gitlab_http_status(:ok)
676 677 678 679 680 681 682 683 684 685
        expect(json_response).to be_empty
      end
    end

    def get_pipeline_status
      get :pipeline_status, namespace_id: project.namespace,
                            project_id: project,
                            id: merge_request.iid,
                            format: :json
    end
Shinya Maeda committed
686
  end
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744

  describe 'POST #rebase' do
    let(:viewer)        { user }

    def post_rebase
      post :rebase, namespace_id: project.namespace, project_id: project, id: merge_request
    end

    def expect_rebase_worker_for(user)
      expect(RebaseWorker).to receive(:perform_async).with(merge_request.id, user.id)
    end

    context 'successfully' do
      it 'enqeues a RebaseWorker' do
        expect_rebase_worker_for(viewer)

        post_rebase

        expect(response.status).to eq(200)
      end
    end

    context 'with a forked project' do
      let(:fork_project) { create(:project, :repository, forked_from_project: project) }
      let(:fork_owner) { fork_project.owner }

      before do
        merge_request.update!(source_project: fork_project)
        fork_project.add_reporter(user)
      end

      context 'user cannot push to source branch' do
        it 'returns 404' do
          expect_rebase_worker_for(viewer).never

          post_rebase

          expect(response.status).to eq(404)
        end
      end

      context 'user can push to source branch' do
        before do
          project.add_reporter(fork_owner)

          sign_in(fork_owner)
        end

        it 'returns 200' do
          expect_rebase_worker_for(fork_owner)

          post_rebase

          expect(response.status).to eq(200)
        end
      end
    end
  end
745
end