create_pipeline_service_spec.rb 35.2 KB
Newer Older
1 2
# frozen_string_literal: true

3 4
require 'spec_helper'

5
describe Ci::CreatePipelineService do
B
Bob Van Landuyt 已提交
6 7
  include ProjectForksHelper

8
  set(:project) { create(:project, :repository) }
9
  let(:user) { create(:admin) }
10
  let(:ref_name) { 'refs/heads/master' }
11 12

  before do
13
    stub_repository_ci_yaml_file(sha: anything)
14 15 16
  end

  describe '#execute' do
17
    # rubocop:disable Metrics/ParameterLists
18 19 20 21 22
    def execute_service(
      source: :push,
      after: project.commit.id,
      message: 'Message',
      ref: ref_name,
23
      trigger_request: nil,
S
Shinya Maeda 已提交
24
      variables_attributes: nil,
25
      merge_request: nil,
26 27
      push_options: nil,
      source_sha: nil,
K
Kamil Trzciński 已提交
28 29
      target_sha: nil,
      save_on_errors: true)
30 31 32
      params = { ref: ref,
                 before: '00000000',
                 after: after,
33
                 commits: [{ message: message }],
34
                 variables_attributes: variables_attributes,
35 36 37
                 push_options: push_options,
                 source_sha: source_sha,
                 target_sha: target_sha }
38

39
      described_class.new(project, user, params).execute(
K
Kamil Trzciński 已提交
40
        source, save_on_errors: save_on_errors, trigger_request: trigger_request, merge_request: merge_request)
41
    end
42
    # rubocop:enable Metrics/ParameterLists
43

44 45 46
    context 'valid params' do
      let(:pipeline) { execute_service }

47 48 49 50 51 52
      let(:pipeline_on_previous_commit) do
        execute_service(
          after: previous_commit_sha_from_ref('master')
        )
      end

53 54 55
      it 'creates a pipeline' do
        expect(pipeline).to be_kind_of(Ci::Pipeline)
        expect(pipeline).to be_valid
56
        expect(pipeline).to be_persisted
57
        expect(pipeline).to be_push
58
        expect(pipeline).to eq(project.ci_pipelines.last)
59 60
        expect(pipeline).to have_attributes(user: user)
        expect(pipeline).to have_attributes(status: 'pending')
K
Kamil Trzciński 已提交
61
        expect(pipeline.iid).not_to be_nil
62
        expect(pipeline.repository_source?).to be true
63 64
        expect(pipeline.builds.first).to be_kind_of(Ci::Build)
      end
65

66 67
      it 'increments the prometheus counter' do
        expect(Gitlab::Metrics).to receive(:counter)
B
Ben Kochie 已提交
68
          .with(:pipelines_created_total, "Counter of pipelines created")
69 70 71 72 73
          .and_call_original

        pipeline
      end

74
      context 'when merge requests already exist for this source branch' do
75
        let(:merge_request_1) do
S
Shinya Maeda 已提交
76
          create(:merge_request, source_branch: 'feature', target_branch: "master", source_project: project)
77 78
        end
        let(:merge_request_2) do
S
Shinya Maeda 已提交
79
          create(:merge_request, source_branch: 'feature', target_branch: "v1.1.0", source_project: project)
80
        end
81

82 83 84 85 86 87 88 89 90 91 92 93
        context 'when related merge request is already merged' do
          let!(:merged_merge_request) do
            create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project, state: 'merged')
          end

          it 'does not schedule update head pipeline job' do
            expect(UpdateHeadPipelineForMergeRequestWorker).not_to receive(:perform_async).with(merged_merge_request.id)

            execute_service
          end
        end

94 95 96 97 98
        context 'when the head pipeline sha equals merge request sha' do
          it 'updates head pipeline of each merge request' do
            merge_request_1
            merge_request_2

S
Shinya Maeda 已提交
99
            head_pipeline = execute_service(ref: 'feature', after: nil)
100

101 102 103 104 105 106
            expect(merge_request_1.reload.head_pipeline).to eq(head_pipeline)
            expect(merge_request_2.reload.head_pipeline).to eq(head_pipeline)
          end
        end

        context 'when the head pipeline sha does not equal merge request sha' do
107
          it 'does not update the head piepeline of MRs' do
108 109 110 111
            merge_request_1
            merge_request_2

            allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(true)
112

113
            expect { execute_service(after: 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }.not_to raise_error
114 115 116 117 118 119

            last_pipeline = Ci::Pipeline.last

            expect(merge_request_1.reload.head_pipeline).not_to eq(last_pipeline)
            expect(merge_request_2.reload.head_pipeline).not_to eq(last_pipeline)
          end
120 121 122 123
        end

        context 'when there is no pipeline for source branch' do
          it "does not update merge request head pipeline" do
124 125 126
            merge_request = create(:merge_request, source_branch: 'feature',
                                                   target_branch: "branch_1",
                                                   source_project: project)
127

128
            head_pipeline = execute_service
129 130 131 132 133 134

            expect(merge_request.reload.head_pipeline).not_to eq(head_pipeline)
          end
        end

        context 'when merge request target project is different from source project' do
B
Bob Van Landuyt 已提交
135
          let!(:project) { fork_project(target_project, nil, repository: true) }
136
          let!(:target_project) { create(:project, :repository) }
137

138
          it 'updates head pipeline for merge request' do
S
Shinya Maeda 已提交
139 140
            merge_request = create(:merge_request, source_branch: 'feature',
                                                   target_branch: "master",
141 142
                                                   source_project: project,
                                                   target_project: target_project)
143

S
Shinya Maeda 已提交
144
            head_pipeline = execute_service(ref: 'feature', after: nil)
145 146 147 148

            expect(merge_request.reload.head_pipeline).to eq(head_pipeline)
          end
        end
149

150
        context 'when the pipeline is not the latest for the branch' do
151
          it 'does not update merge request head pipeline' do
152 153 154
            merge_request = create(:merge_request, source_branch: 'master',
                                                   target_branch: "branch_1",
                                                   source_project: project)
155

156 157
            allow_any_instance_of(MergeRequest)
              .to receive(:find_actual_head_pipeline) { }
158

159
            execute_service
160 161 162 163

            expect(merge_request.reload.head_pipeline).to be_nil
          end
        end
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181

        context 'when pipeline has errors' do
          before do
            stub_ci_pipeline_yaml_file('some invalid syntax')
          end

          it 'updates merge request head pipeline reference' do
            merge_request = create(:merge_request, source_branch: 'master',
                                                   target_branch: 'feature',
                                                   source_project: project)

            head_pipeline = execute_service

            expect(head_pipeline).to be_persisted
            expect(head_pipeline.yaml_errors).to be_present
            expect(merge_request.reload.head_pipeline).to eq head_pipeline
          end
        end
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201

        context 'when pipeline has been skipped' do
          before do
            allow_any_instance_of(Ci::Pipeline)
              .to receive(:git_commit_message)
              .and_return('some commit [ci skip]')
          end

          it 'updates merge request head pipeline' do
            merge_request = create(:merge_request, source_branch: 'master',
                                                   target_branch: 'feature',
                                                   source_project: project)

            head_pipeline = execute_service

            expect(head_pipeline).to be_skipped
            expect(head_pipeline).to be_persisted
            expect(merge_request.reload.head_pipeline).to eq head_pipeline
          end
        end
202 203
      end

204 205 206 207 208
      context 'auto-cancel enabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'enabled')
        end

209 210
        it 'does not cancel HEAD pipeline' do
          pipeline
R
Rydkin Maxim 已提交
211
          pipeline_on_previous_commit
212

R
Rydkin Maxim 已提交
213
          expect(pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
214
        end
215 216

        it 'auto cancel pending non-HEAD pipelines' do
R
Rydkin Maxim 已提交
217
          pipeline_on_previous_commit
218 219
          pipeline

R
Rydkin Maxim 已提交
220
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
221 222 223
        end

        it 'does not cancel running outdated pipelines' do
R
Rydkin Maxim 已提交
224
          pipeline_on_previous_commit.run
225 226
          execute_service

R
Rydkin Maxim 已提交
227
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'running', auto_canceled_by_id: nil)
228 229 230
        end

        it 'cancel created outdated pipelines' do
R
Rydkin Maxim 已提交
231
          pipeline_on_previous_commit.update(status: 'created')
232 233
          pipeline

R
Rydkin Maxim 已提交
234
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
235 236 237 238 239 240 241 242 243
        end

        it 'does not cancel pipelines from the other branches' do
          pending_pipeline = execute_service(
            ref: 'refs/heads/feature',
            after: previous_commit_sha_from_ref('feature')
          )
          pipeline

244
          expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
245 246
        end
      end
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264

      context 'auto-cancel disabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'disabled')
        end

        it 'does not auto cancel pending non-HEAD pipelines' do
          pipeline_on_previous_commit
          pipeline

          expect(pipeline_on_previous_commit.reload)
            .to have_attributes(status: 'pending', auto_canceled_by_id: nil)
        end
      end

      def previous_commit_sha_from_ref(ref)
        project.commit(ref).parent.sha
      end
265 266
    end

267 268
    context "skip tag if there is no build for it" do
      it "creates commit if there is appropriate job" do
269
        expect(execute_service).to be_persisted
270 271 272 273 274 275
      end

      it "creates commit if there is no appropriate job but deploy job has right ref setting" do
        config = YAML.dump({ deploy: { script: "ls", only: ["master"] } })
        stub_ci_pipeline_yaml_file(config)

276
        expect(execute_service).to be_persisted
277 278 279 280 281 282
      end
    end

    it 'skips creating pipeline for refs without .gitlab-ci.yml' do
      stub_ci_pipeline_yaml_file(nil)

283
      expect(execute_service).not_to be_persisted
284 285 286
      expect(Ci::Pipeline.count).to eq(0)
    end

287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
    shared_examples 'a failed pipeline' do
      it 'creates failed pipeline' do
        stub_ci_pipeline_yaml_file(ci_yaml)

        pipeline = execute_service(message: message)

        expect(pipeline).to be_persisted
        expect(pipeline.builds.any?).to be false
        expect(pipeline.status).to eq('failed')
        expect(pipeline.yaml_errors).not_to be_nil
      end
    end

    context 'when yaml is invalid' do
      let(:ci_yaml) { 'invalid: file: fiile' }
      let(:message) { 'Message' }

      it_behaves_like 'a failed pipeline'

      context 'when receive git commit' do
        before do
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
        end

        it_behaves_like 'a failed pipeline'
      end
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362

      context 'when config has ports' do
        context 'in the main image' do
          let(:ci_yaml) do
            <<-EOS
              image:
                name: ruby:2.2
                ports:
                  - 80
            EOS
          end

          it_behaves_like 'a failed pipeline'
        end

        context 'in the job image' do
          let(:ci_yaml) do
            <<-EOS
              image: ruby:2.2

              test:
                script: rspec
                image:
                  name: ruby:2.2
                  ports:
                    - 80
            EOS
          end

          it_behaves_like 'a failed pipeline'
        end

        context 'in the service' do
          let(:ci_yaml) do
            <<-EOS
              image: ruby:2.2

              test:
                script: rspec
                image: ruby:2.2
                services:
                  - name: test
                    ports:
                      - 80
            EOS
          end

          it_behaves_like 'a failed pipeline'
        end
      end
363 364 365 366
    end

    context 'when commit contains a [ci skip] directive' do
      let(:message) { "some message[ci skip]" }
367 368 369 370 371 372 373 374 375 376 377

      ci_messages = [
        "some message[ci skip]",
        "some message[skip ci]",
        "some message[CI SKIP]",
        "some message[SKIP CI]",
        "some message[ci_skip]",
        "some message[skip_ci]",
        "some message[ci-skip]",
        "some message[skip-ci]"
      ]
378 379 380 381 382

      before do
        allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
      end

383 384
      ci_messages.each do |ci_message|
        it "skips builds creation if the commit message is #{ci_message}" do
385
          pipeline = execute_service(message: ci_message)
386 387 388 389 390

          expect(pipeline).to be_persisted
          expect(pipeline.builds.any?).to be false
          expect(pipeline.status).to eq("skipped")
        end
391 392
      end

393
      shared_examples 'creating a pipeline' do
R
Rydkin Maxim 已提交
394
        it 'does not skip pipeline creation' do
395
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
396

397
          pipeline = execute_service(message: commit_message)
398

399 400 401
          expect(pipeline).to be_persisted
          expect(pipeline.builds.first.name).to eq("rspec")
        end
402 403
      end

404 405
      context 'when commit message does not contain [ci skip] nor [skip ci]' do
        let(:commit_message) { 'some message' }
406

407
        it_behaves_like 'creating a pipeline'
408 409
      end

410 411
      context 'when commit message is nil' do
        let(:commit_message) { nil }
412

413
        it_behaves_like 'creating a pipeline'
414 415
      end

416 417 418 419 420
      context 'when there is [ci skip] tag in commit message and yaml is invalid' do
        let(:ci_yaml) { 'invalid: file: fiile' }

        it_behaves_like 'a failed pipeline'
      end
421 422
    end

423 424
    context 'when push options contain ci.skip' do
      let(:push_options) do
425
        { 'ci' => { 'skip' => true } }
426 427 428 429 430 431 432 433 434 435 436 437
      end

      it 'creates a pipline in the skipped state' do
        pipeline = execute_service(push_options: push_options)

        # TODO: DRY these up with "skips builds creation if the commit message"
        expect(pipeline).to be_persisted
        expect(pipeline.builds.any?).to be false
        expect(pipeline.status).to eq("skipped")
      end
    end

438 439 440 441 442 443 444
    context 'when there are no jobs for this pipeline' do
      before do
        config = YAML.dump({ test: { script: 'ls', only: ['feature'] } })
        stub_ci_pipeline_yaml_file(config)
      end

      it 'does not create a new pipeline' do
445
        result = execute_service
446 447 448 449 450

        expect(result).not_to be_persisted
        expect(Ci::Build.all).to be_empty
        expect(Ci::Pipeline.count).to eq(0)
      end
K
Kamil Trzciński 已提交
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487

      describe '#iid' do
        let(:internal_id) do
          InternalId.find_by(project_id: project.id, usage: :ci_pipelines)
        end

        before do
          expect_any_instance_of(Ci::Pipeline).to receive(:ensure_project_iid!)
            .and_call_original
        end

        context 'when ci_pipeline_rewind_iid is enabled' do
          before do
            stub_feature_flags(ci_pipeline_rewind_iid: true)
          end

          it 'rewinds iid' do
            result = execute_service

            expect(result).not_to be_persisted
            expect(internal_id.last_value).to eq(0)
          end
        end

        context 'when ci_pipeline_rewind_iid is disabled' do
          before do
            stub_feature_flags(ci_pipeline_rewind_iid: false)
          end

          it 'does not rewind iid' do
            result = execute_service

            expect(result).not_to be_persisted
            expect(internal_id.last_value).to eq(1)
          end
        end
      end
488 489 490 491 492 493 494 495 496
    end

    context 'with manual actions' do
      before do
        config = YAML.dump({ deploy: { script: 'ls', when: 'manual' } })
        stub_ci_pipeline_yaml_file(config)
      end

      it 'does not create a new pipeline' do
497
        result = execute_service
498 499 500 501 502

        expect(result).to be_persisted
        expect(result.manual_actions).not_to be_empty
      end
    end
503 504 505

    context 'with environment' do
      before do
K
Kamil Trzciński 已提交
506 507 508 509 510 511 512
        config = YAML.dump(
          deploy: {
            environment: { name: "review/$CI_COMMIT_REF_NAME" },
            script: 'ls',
            tags: ['hello']
          })

513 514 515
        stub_ci_pipeline_yaml_file(config)
      end

K
Kamil Trzciński 已提交
516
      it 'creates the environment with tags' do
517
        result = execute_service
518 519

        expect(result).to be_persisted
520
        expect(Environment.find_by(name: "review/master")).to be_present
K
Kamil Trzciński 已提交
521
        expect(result.builds.first.tag_list).to contain_exactly('hello')
K
Kamil Trzciński 已提交
522 523
        expect(result.builds.first.deployment).to be_persisted
        expect(result.builds.first.deployment.deployable).to be_a(Ci::Build)
K
Kamil Trzciński 已提交
524
      end
525 526 527 528 529 530 531
    end

    context 'with environment name including persisted variables' do
      before do
        config = YAML.dump(
          deploy: {
            environment: { name: "review/id1$CI_PIPELINE_ID/id2$CI_BUILD_ID" },
532 533
            script: 'ls'
          }
534 535 536 537 538 539 540 541 542 543
        )

        stub_ci_pipeline_yaml_file(config)
      end

      it 'skipps persisted variables in environment name' do
        result = execute_service

        expect(result).to be_persisted
        expect(Environment.find_by(name: "review/id1/id2")).to be_present
544 545
      end
    end
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560

    context 'when environment with invalid name' do
      before do
        config = YAML.dump(deploy: { environment: { name: 'name,with,commas' }, script: 'ls' })
        stub_ci_pipeline_yaml_file(config)
      end

      it 'does not create an environment' do
        expect do
          result = execute_service

          expect(result).to be_persisted
        end.not_to change { Environment.count }
      end
    end
561

562
    context 'when builds with auto-retries are configured' do
563 564 565 566 567 568 569 570 571 572 573 574 575
      context 'as an integer' do
        before do
          config = YAML.dump(rspec: { script: 'rspec', retry: 2 })
          stub_ci_pipeline_yaml_file(config)
        end

        it 'correctly creates builds with auto-retry value configured' do
          pipeline = execute_service

          expect(pipeline).to be_persisted
          expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
          expect(pipeline.builds.find_by(name: 'rspec').retry_when).to eq ['always']
        end
576 577
      end

578 579 580 581 582
      context 'as hash' do
        before do
          config = YAML.dump(rspec: { script: 'rspec', retry: { max: 2, when: 'runner_system_failure' } })
          stub_ci_pipeline_yaml_file(config)
        end
583

584 585 586 587 588 589 590
        it 'correctly creates builds with auto-retry value configured' do
          pipeline = execute_service

          expect(pipeline).to be_persisted
          expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
          expect(pipeline.builds.find_by(name: 'rspec').retry_when).to eq ['runner_system_failure']
        end
591 592
      end
    end
593

594 595 596 597 598 599 600 601 602 603 604 605 606 607
    shared_examples 'when ref is protected' do
      let(:user) { create(:user) }

      context 'when user is developer' do
        before do
          project.add_developer(user)
        end

        it 'does not create a pipeline' do
          expect(execute_service).not_to be_persisted
          expect(Ci::Pipeline.count).to eq(0)
        end
      end

608
      context 'when user is maintainer' do
S
Shinya Maeda 已提交
609 610
        let(:pipeline) { execute_service }

611
        before do
612
          project.add_maintainer(user)
613 614
        end

S
Shinya Maeda 已提交
615 616 617
        it 'creates a protected pipeline' do
          expect(pipeline).to be_persisted
          expect(pipeline).to be_protected
618 619 620
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
621 622 623 624 625 626 627 628 629 630 631 632 633

      context 'when trigger belongs to no one' do
        let(:user) {}
        let(:trigger_request) { create(:ci_trigger_request) }

        it 'does not create a pipeline' do
          expect(execute_service(trigger_request: trigger_request))
            .not_to be_persisted
          expect(Ci::Pipeline.count).to eq(0)
        end
      end

      context 'when trigger belongs to a developer' do
S
Shinya Maeda 已提交
634 635 636
        let(:user) { create(:user) }
        let(:trigger) { create(:ci_trigger, owner: user) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
637

S
Shinya Maeda 已提交
638 639
        before do
          project.add_developer(user)
640 641 642 643 644 645 646 647 648
        end

        it 'does not create a pipeline' do
          expect(execute_service(trigger_request: trigger_request))
            .not_to be_persisted
          expect(Ci::Pipeline.count).to eq(0)
        end
      end

649
      context 'when trigger belongs to a maintainer' do
S
Shinya Maeda 已提交
650 651 652
        let(:user) { create(:user) }
        let(:trigger) { create(:ci_trigger, owner: user) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
653

S
Shinya Maeda 已提交
654
        before do
655
          project.add_maintainer(user)
656 657
        end

S
Shinya Maeda 已提交
658
        it 'creates a pipeline' do
659 660 661 662 663
          expect(execute_service(trigger_request: trigger_request))
            .to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
    end

    context 'when ref is a protected branch' do
      before do
        create(:protected_branch, project: project, name: 'master')
      end

      it_behaves_like 'when ref is protected'
    end

    context 'when ref is a protected tag' do
      let(:ref_name) { 'refs/tags/v1.0.0' }

      before do
        create(:protected_tag, project: project, name: '*')
      end

      it_behaves_like 'when ref is protected'
    end
683 684 685 686

    context 'when ref is not protected' do
      context 'when trigger belongs to no one' do
        let(:user) {}
687 688
        let(:trigger) { create(:ci_trigger, owner: nil) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
S
Shinya Maeda 已提交
689
        let(:pipeline) { execute_service(trigger_request: trigger_request) }
690

S
Shinya Maeda 已提交
691 692 693
        it 'creates an unprotected pipeline' do
          expect(pipeline).to be_persisted
          expect(pipeline).not_to be_protected
694
          expect(Ci::Pipeline.count).to eq(1)
695 696 697
        end
      end
    end
698

K
Kamil Trzcinski 已提交
699
    context 'when pipeline is running for a tag' do
700 701
      before do
        config = YAML.dump(test: { script: 'test', only: ['branches'] },
K
Kamil Trzcinski 已提交
702
                           deploy: { script: 'deploy', only: ['tags'] })
703 704 705 706 707

        stub_ci_pipeline_yaml_file(config)
      end

      it 'creates a tagged pipeline' do
K
Kamil Trzcinski 已提交
708
        pipeline = execute_service(ref: 'v1.0.0')
709 710 711 712

        expect(pipeline.tag?).to be true
      end
    end
713 714

    context 'when pipeline variables are specified' do
715
      let(:variables_attributes) do
716 717 718 719
        [{ key: 'first', secret_value: 'world' },
         { key: 'second', secret_value: 'second_world' }]
      end

720
      subject { execute_service(variables_attributes: variables_attributes) }
721 722

      it 'creates a pipeline with specified variables' do
M
Matija Čupić 已提交
723 724
        expect(subject.variables.map { |var| var.slice(:key, :secret_value) })
          .to eq variables_attributes.map(&:with_indifferent_access)
725 726
      end
    end
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774

    context 'when pipeline has a job with environment' do
      let(:pipeline) { execute_service }

      before do
        stub_ci_pipeline_yaml_file(YAML.dump(config))
      end

      context 'when environment name is valid' do
        let(:config) do
          {
            review_app: {
              script: 'deploy',
              environment: {
                name: 'review/${CI_COMMIT_REF_NAME}',
                url: 'http://${CI_COMMIT_REF_SLUG}-staging.example.com'
              }
            }
          }
        end

        it 'has a job with environment' do
          expect(pipeline.builds.count).to eq(1)
          expect(pipeline.builds.first.persisted_environment.name).to eq('review/master')
          expect(pipeline.builds.first.deployment).to be_created
        end
      end

      context 'when environment name is invalid' do
        let(:config) do
          {
            'job:deploy-to-test-site': {
              script: 'deploy',
              environment: {
                name: '${CI_JOB_NAME}',
                url: 'https://$APP_URL'
              }
            }
          }
        end

        it 'has a job without environment' do
          expect(pipeline.builds.count).to eq(1)
          expect(pipeline.builds.first.persisted_environment).to be_nil
          expect(pipeline.builds.first.deployment).to be_nil
        end
      end
    end
S
Shinya Maeda 已提交
775

S
Shinya Maeda 已提交
776
    describe 'Pipelines for merge requests' do
S
Shinya Maeda 已提交
777
      let(:pipeline) do
778 779 780 781 782
        execute_service(source: source,
                        merge_request: merge_request,
                        ref: ref_name,
                        source_sha: source_sha,
                        target_sha: target_sha)
S
Shinya Maeda 已提交
783 784 785 786 787 788
      end

      before do
        stub_ci_pipeline_yaml_file(YAML.dump(config))
      end

789
      let(:ref_name) { 'refs/heads/feature' }
790 791
      let(:source_sha) { project.commit(ref_name).id }
      let(:target_sha) { nil }
S
Shinya Maeda 已提交
792 793

      context 'when source is merge request' do
794
        let(:source) { :merge_request_event }
S
Shinya Maeda 已提交
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819

        context "when config has merge_requests keywords" do
          let(:config) do
            {
              build: {
                stage: 'build',
                script: 'echo'
              },
              test: {
                stage: 'test',
                script: 'echo',
                only: ['merge_requests']
              },
              pages: {
                stage: 'deploy',
                script: 'echo',
                except: ['merge_requests']
              }
            }
          end

          context 'when merge request is specified' do
            let(:merge_request) do
              create(:merge_request,
                source_project: project,
S
Shinya Maeda 已提交
820
                source_branch: 'feature',
S
Shinya Maeda 已提交
821 822 823 824
                target_project: project,
                target_branch: 'master')
            end

S
Shinya Maeda 已提交
825 826 827
            let(:ref_name) { merge_request.ref_path }

            it 'creates a detached merge request pipeline' do
S
Shinya Maeda 已提交
828
              expect(pipeline).to be_persisted
829
              expect(pipeline).to be_merge_request_event
S
Shinya Maeda 已提交
830 831 832 833
              expect(pipeline.merge_request).to eq(merge_request)
              expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[test])
            end

834 835 836 837 838 839 840 841
            it 'persists the specified source sha' do
              expect(pipeline.source_sha).to eq(source_sha)
            end

            it 'does not persist target sha for detached merge request pipeline' do
              expect(pipeline.target_sha).to be_nil
            end

S
Shinya Maeda 已提交
842 843 844 845 846 847 848
            it 'schedules update for the head pipeline of the merge request' do
              expect(UpdateHeadPipelineForMergeRequestWorker)
                .to receive(:perform_async).with(merge_request.id)

              pipeline
            end

849 850 851 852 853 854 855 856
            context 'when target sha is specified' do
              let(:target_sha) { merge_request.target_branch_sha }

              it 'persists the target sha' do
                expect(pipeline.target_sha).to eq(target_sha)
              end
            end

S
Shinya Maeda 已提交
857
            context 'when ref is tag' do
858
              let(:ref_name) { 'refs/tags/v1.1.0' }
S
Shinya Maeda 已提交
859 860 861 862 863 864 865 866 867 868 869

              it 'does not create a merge request pipeline' do
                expect(pipeline).not_to be_persisted
                expect(pipeline.errors[:tag]).to eq(["is not included in the list"])
              end
            end

            context 'when merge request is created from a forked project' do
              let(:merge_request) do
                create(:merge_request,
                  source_project: project,
S
Shinya Maeda 已提交
870
                  source_branch: 'feature',
S
Shinya Maeda 已提交
871 872 873 874
                  target_project: target_project,
                  target_branch: 'master')
              end

S
Shinya Maeda 已提交
875
              let(:ref_name) { 'refs/heads/feature' }
S
Shinya Maeda 已提交
876 877 878
              let!(:project) { fork_project(target_project, nil, repository: true) }
              let!(:target_project) { create(:project, :repository) }

S
Shinya Maeda 已提交
879
              it 'creates a legacy detached merge request pipeline in the forked project' do
S
Shinya Maeda 已提交
880
                expect(pipeline).to be_persisted
881 882
                expect(project.ci_pipelines).to eq([pipeline])
                expect(target_project.ci_pipelines).to be_empty
S
Shinya Maeda 已提交
883 884 885 886 887 888 889 890 891 892 893 894 895 896
              end
            end

            context "when there are no matched jobs" do
              let(:config) do
                {
                  test: {
                    stage: 'test',
                    script: 'echo',
                    except: ['merge_requests']
                  }
                }
              end

S
Shinya Maeda 已提交
897
              it 'does not create a detached merge request pipeline' do
S
Shinya Maeda 已提交
898 899 900 901 902 903 904 905 906
                expect(pipeline).not_to be_persisted
                expect(pipeline.errors[:base]).to eq(["No stages / jobs for this pipeline."])
              end
            end
          end

          context 'when merge request is not specified' do
            let(:merge_request) { nil }

S
Shinya Maeda 已提交
907
            it 'does not create a detached merge request pipeline' do
S
Shinya Maeda 已提交
908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
              expect(pipeline).not_to be_persisted
              expect(pipeline.errors[:merge_request]).to eq(["can't be blank"])
            end
          end
        end

        context "when config does not have merge_requests keywords" do
          let(:config) do
            {
              build: {
                stage: 'build',
                script: 'echo'
              },
              test: {
                stage: 'test',
                script: 'echo'
              },
              pages: {
                stage: 'deploy',
                script: 'echo'
              }
            }
          end

          context 'when merge request is specified' do
            let(:merge_request) do
              create(:merge_request,
                source_project: project,
936
                source_branch: Gitlab::Git.ref_name(ref_name),
S
Shinya Maeda 已提交
937 938 939 940
                target_project: project,
                target_branch: 'master')
            end

S
Shinya Maeda 已提交
941
            it 'does not create a detached merge request pipeline' do
S
Shinya Maeda 已提交
942 943 944 945 946 947 948 949 950 951
              expect(pipeline).not_to be_persisted

              expect(pipeline.errors[:base])
                .to eq(['No stages / jobs for this pipeline.'])
            end
          end

          context 'when merge request is not specified' do
            let(:merge_request) { nil }

S
Shinya Maeda 已提交
952
            it 'does not create a detached merge request pipeline' do
S
Shinya Maeda 已提交
953 954 955 956 957 958 959
              expect(pipeline).not_to be_persisted

              expect(pipeline.errors[:base])
                .to eq(['No stages / jobs for this pipeline.'])
            end
          end
        end
S
Shinya Maeda 已提交
960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975

        context "when config uses regular expression for only keyword" do
          let(:config) do
            {
              build: {
                stage: 'build',
                script: 'echo',
                only: ["/^#{ref_name}$/"]
              }
            }
          end

          context 'when merge request is specified' do
            let(:merge_request) do
              create(:merge_request,
                source_project: project,
976
                source_branch: Gitlab::Git.ref_name(ref_name),
S
Shinya Maeda 已提交
977 978 979 980
                target_project: project,
                target_branch: 'master')
            end

S
Shinya Maeda 已提交
981
            it 'does not create a detached merge request pipeline' do
S
Shinya Maeda 已提交
982 983 984 985 986 987 988 989
              expect(pipeline).not_to be_persisted

              expect(pipeline.errors[:base])
                .to eq(['No stages / jobs for this pipeline.'])
            end
          end
        end

990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006
        context "when config uses variables for only keyword" do
          let(:config) do
            {
              build: {
                stage: 'build',
                script: 'echo',
                only: {
                  variables: %w($CI)
                }
              }
            }
          end

          context 'when merge request is specified' do
            let(:merge_request) do
              create(:merge_request,
                source_project: project,
1007
                source_branch: Gitlab::Git.ref_name(ref_name),
1008 1009 1010 1011
                target_project: project,
                target_branch: 'master')
            end

S
Shinya Maeda 已提交
1012
            it 'does not create a detached merge request pipeline' do
1013 1014 1015 1016 1017 1018 1019 1020
              expect(pipeline).not_to be_persisted

              expect(pipeline.errors[:base])
                .to eq(['No stages / jobs for this pipeline.'])
            end
          end
        end

S
Shinya Maeda 已提交
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
        context "when config has 'except: [tags]'" do
          let(:config) do
            {
              build: {
                stage: 'build',
                script: 'echo',
                except: ['tags']
              }
            }
          end

          context 'when merge request is specified' do
            let(:merge_request) do
              create(:merge_request,
                source_project: project,
1036
                source_branch: Gitlab::Git.ref_name(ref_name),
S
Shinya Maeda 已提交
1037 1038 1039 1040
                target_project: project,
                target_branch: 'master')
            end

S
Shinya Maeda 已提交
1041
            it 'does not create a detached merge request pipeline' do
S
Shinya Maeda 已提交
1042 1043 1044 1045 1046 1047 1048
              expect(pipeline).not_to be_persisted

              expect(pipeline.errors[:base])
                .to eq(['No stages / jobs for this pipeline.'])
            end
          end
        end
S
Shinya Maeda 已提交
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
      end

      context 'when source is web' do
        let(:source) { :web }

        context "when config has merge_requests keywords" do
          let(:config) do
            {
              build: {
                stage: 'build',
                script: 'echo'
              },
              test: {
                stage: 'test',
                script: 'echo',
                only: ['merge_requests']
              },
              pages: {
                stage: 'deploy',
                script: 'echo',
                except: ['merge_requests']
              }
            }
          end

          context 'when merge request is specified' do
            let(:merge_request) do
              create(:merge_request,
                source_project: project,
1078
                source_branch: Gitlab::Git.ref_name(ref_name),
S
Shinya Maeda 已提交
1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
                target_project: project,
                target_branch: 'master')
            end

            it 'does not create a merge request pipeline' do
              expect(pipeline).not_to be_persisted
              expect(pipeline.errors[:merge_request]).to eq(["must be blank"])
            end
          end

          context 'when merge request is not specified' do
            let(:merge_request) { nil }

            it 'creates a branch pipeline' do
              expect(pipeline).to be_persisted
              expect(pipeline).to be_web
              expect(pipeline.merge_request).to be_nil
              expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[build pages])
            end
          end
        end
      end
    end
1102
  end
S
Shinya Maeda 已提交
1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134

  describe '#execute!' do
    subject { service.execute!(*args) }

    let(:service) { described_class.new(project, user, ref: ref_name) }
    let(:args) { [:push] }

    context 'when user has a permission to create a pipeline' do
      let(:user) { create(:user) }

      before do
        project.add_developer(user)
      end

      it 'does not raise an error' do
        expect { subject }.not_to raise_error
      end

      it 'creates a pipeline' do
        expect { subject }.to change { Ci::Pipeline.count }.by(1)
      end
    end

    context 'when user does not have a permission to create a pipeline' do
      let(:user) { create(:user) }

      it 'raises an error' do
        expect { subject }
          .to raise_error(described_class::CreateError)
          .with_message('Insufficient permissions to create a new pipeline')
      end
    end
1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146

    context 'when a user with permissions has been blocked' do
      before do
        user.block!
      end

      it 'raises an error' do
        expect { subject }
          .to raise_error(described_class::CreateError)
          .with_message('Insufficient permissions to create a new pipeline')
      end
    end
S
Shinya Maeda 已提交
1147
  end
1148
end