create_pipeline_service_spec.rb 17.5 KB
Newer Older
1 2
require 'spec_helper'

3
describe Ci::CreatePipelineService do
4
  let(:project) { create(:project, :repository) }
5
  let(:user) { create(:admin) }
6
  let(:ref_name) { 'refs/heads/master' }
7 8 9 10 11 12

  before do
    stub_ci_pipeline_to_return_yaml_file
  end

  describe '#execute' do
13 14 15 16 17 18
    def execute_service(
      source: :push,
      after: project.commit.id,
      message: 'Message',
      ref: ref_name,
      trigger_request: nil)
19 20 21 22 23
      params = { ref: ref,
                 before: '00000000',
                 after: after,
                 commits: [{ message: message }] }

24 25
      described_class.new(project, user, params).execute(
        source, trigger_request: trigger_request)
26 27
    end

28 29 30
    context 'valid params' do
      let(:pipeline) { execute_service }

31 32 33 34 35 36
      let(:pipeline_on_previous_commit) do
        execute_service(
          after: previous_commit_sha_from_ref('master')
        )
      end

37 38 39
      it 'creates a pipeline' do
        expect(pipeline).to be_kind_of(Ci::Pipeline)
        expect(pipeline).to be_valid
40
        expect(pipeline).to be_persisted
41
        expect(pipeline).to be_push
42 43 44 45 46
        expect(pipeline).to eq(project.pipelines.last)
        expect(pipeline).to have_attributes(user: user)
        expect(pipeline).to have_attributes(status: 'pending')
        expect(pipeline.builds.first).to be_kind_of(Ci::Build)
      end
47

48 49
      it 'increments the prometheus counter' do
        expect(Gitlab::Metrics).to receive(:counter)
B
Ben Kochie 已提交
50
          .with(:pipelines_created_total, "Counter of pipelines created")
51 52 53 54 55
          .and_call_original

        pipeline
      end

56
      context 'when merge requests already exist for this source branch' do
57
        it 'updates head pipeline of each merge request' do
58 59 60
          merge_request_1 = create(:merge_request, source_branch: 'master',
                                                   target_branch: "branch_1",
                                                   source_project: project)
61

62 63 64 65 66
          merge_request_2 = create(:merge_request, source_branch: 'master',
                                                   target_branch: "branch_2",
                                                   source_project: project)

          head_pipeline = execute_service
67

68 69 70 71 72 73
          expect(merge_request_1.reload.head_pipeline).to eq(head_pipeline)
          expect(merge_request_2.reload.head_pipeline).to eq(head_pipeline)
        end

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

78
            head_pipeline = execute_service
79 80 81 82 83 84

            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
85
          let!(:target_project) { create(:project, :repository) }
86 87 88 89 90

          let!(:forked_project_link) do
            create(:forked_project_link, forked_to_project: project,
                                         forked_from_project: target_project)
          end
91 92

          it 'updates head pipeline for merge request' do
93 94 95 96
            merge_request = create(:merge_request, source_branch: 'master',
                                                   target_branch: "branch_1",
                                                   source_project: project,
                                                   target_project: target_project)
97

98
            head_pipeline = execute_service
99 100 101 102

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

104
        context 'when the pipeline is not the latest for the branch' do
105
          it 'does not update merge request head pipeline' do
106 107 108
            merge_request = create(:merge_request, source_branch: 'master',
                                                   target_branch: "branch_1",
                                                   source_project: project)
109

110 111
            allow_any_instance_of(Ci::Pipeline)
              .to receive(:latest?).and_return(false)
112

113
            execute_service
114 115 116 117

            expect(merge_request.reload.head_pipeline).to be_nil
          end
        end
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

        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
136 137
      end

138 139 140 141 142
      context 'auto-cancel enabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'enabled')
        end

143 144
        it 'does not cancel HEAD pipeline' do
          pipeline
R
Rydkin Maxim 已提交
145
          pipeline_on_previous_commit
146

R
Rydkin Maxim 已提交
147
          expect(pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
148
        end
149 150

        it 'auto cancel pending non-HEAD pipelines' do
R
Rydkin Maxim 已提交
151
          pipeline_on_previous_commit
152 153
          pipeline

R
Rydkin Maxim 已提交
154
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
155 156 157
        end

        it 'does not cancel running outdated pipelines' do
R
Rydkin Maxim 已提交
158
          pipeline_on_previous_commit.run
159 160
          execute_service

R
Rydkin Maxim 已提交
161
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'running', auto_canceled_by_id: nil)
162 163 164
        end

        it 'cancel created outdated pipelines' do
R
Rydkin Maxim 已提交
165
          pipeline_on_previous_commit.update(status: 'created')
166 167
          pipeline

R
Rydkin Maxim 已提交
168
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
169 170 171 172 173 174 175 176 177
        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

178
          expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
179 180
        end
      end
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198

      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
199 200
    end

201 202
    context "skip tag if there is no build for it" do
      it "creates commit if there is appropriate job" do
203
        expect(execute_service).to be_persisted
204 205 206 207 208 209
      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)

210
        expect(execute_service).to be_persisted
211 212 213 214 215 216
      end
    end

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

217
      expect(execute_service).not_to be_persisted
218 219 220
      expect(Ci::Pipeline.count).to eq(0)
    end

221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    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
247 248 249 250
    end

    context 'when commit contains a [ci skip] directive' do
      let(:message) { "some message[ci skip]" }
251 252 253 254 255 256 257 258 259 260 261

      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]"
      ]
262 263 264 265 266

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

267 268
      ci_messages.each do |ci_message|
        it "skips builds creation if the commit message is #{ci_message}" do
269
          pipeline = execute_service(message: ci_message)
270 271 272 273 274

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

277
      shared_examples 'creating a pipeline' do
R
Rydkin Maxim 已提交
278
        it 'does not skip pipeline creation' do
279
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
280

281
          pipeline = execute_service(message: commit_message)
282

283 284 285
          expect(pipeline).to be_persisted
          expect(pipeline.builds.first.name).to eq("rspec")
        end
286 287
      end

288 289
      context 'when commit message does not contain [ci skip] nor [skip ci]' do
        let(:commit_message) { 'some message' }
290

291
        it_behaves_like 'creating a pipeline'
292 293
      end

294 295
      context 'when commit message is nil' do
        let(:commit_message) { nil }
296

297
        it_behaves_like 'creating a pipeline'
298 299
      end

300 301 302 303 304
      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
305 306 307 308 309 310 311 312 313
    end

    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
314
        result = execute_service
315 316 317 318 319 320 321 322 323 324 325 326 327 328

        expect(result).not_to be_persisted
        expect(Ci::Build.all).to be_empty
        expect(Ci::Pipeline.count).to eq(0)
      end
    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
329
        result = execute_service
330 331 332 333 334

        expect(result).to be_persisted
        expect(result.manual_actions).not_to be_empty
      end
    end
335 336 337

    context 'with environment' do
      before do
Z
Z.J. van de Weg 已提交
338
        config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' })
339 340 341 342
        stub_ci_pipeline_yaml_file(config)
      end

      it 'creates the environment' do
343
        result = execute_service
344 345 346 347 348

        expect(result).to be_persisted
        expect(Environment.find_by(name: "review/master")).not_to be_nil
      end
    end
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363

    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
364

365 366
    context 'when builds with auto-retries are configured' do
      before do
G
Grzegorz Bizon 已提交
367
        config = YAML.dump(rspec: { script: 'rspec', retry: 2 })
368 369 370 371 372 373 374
        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
G
Grzegorz Bizon 已提交
375
        expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
376 377
      end
    end
378

379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
    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

      context 'when user is master' do
S
Shinya Maeda 已提交
394 395
        let(:pipeline) { execute_service }

396 397 398 399
        before do
          project.add_master(user)
        end

S
Shinya Maeda 已提交
400 401 402
        it 'creates a protected pipeline' do
          expect(pipeline).to be_persisted
          expect(pipeline).to be_protected
403 404 405
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
406 407 408 409 410 411 412 413 414 415 416 417 418

      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 已提交
419 420 421
        let(:user) { create(:user) }
        let(:trigger) { create(:ci_trigger, owner: user) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
422

S
Shinya Maeda 已提交
423 424
        before do
          project.add_developer(user)
425 426 427 428 429 430 431 432 433 434
        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

      context 'when trigger belongs to a master' do
S
Shinya Maeda 已提交
435 436 437
        let(:user) { create(:user) }
        let(:trigger) { create(:ci_trigger, owner: user) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
438

S
Shinya Maeda 已提交
439 440
        before do
          project.add_master(user)
441 442
        end

S
Shinya Maeda 已提交
443
        it 'creates a pipeline' do
444 445 446 447 448
          expect(execute_service(trigger_request: trigger_request))
            .to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
    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
468 469 470 471

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

S
Shinya Maeda 已提交
476 477 478
        it 'creates an unprotected pipeline' do
          expect(pipeline).to be_persisted
          expect(pipeline).not_to be_protected
479
          expect(Ci::Pipeline.count).to eq(1)
480 481 482
        end
      end
    end
483
  end
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 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 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583

  describe '#allowed_to_create?' do
    let(:user) { create(:user) }
    let(:project) { create(:project, :repository) }
    let(:ref) { 'master' }

    subject do
      described_class.new(project, user, ref: ref)
        .send(:allowed_to_create?, user)
    end

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

      it { is_expected.to be_truthy }

      context 'when the branch is protected' do
        let!(:protected_branch) do
          create(:protected_branch, project: project, name: ref)
        end

        it { is_expected.to be_falsey }

        context 'when developers are allowed to merge' do
          let!(:protected_branch) do
            create(:protected_branch,
                   :developers_can_merge,
                   project: project,
                   name: ref)
          end

          it { is_expected.to be_truthy }
        end
      end

      context 'when the tag is protected' do
        let(:ref) { 'v1.0.0' }

        let!(:protected_tag) do
          create(:protected_tag, project: project, name: ref)
        end

        it { is_expected.to be_falsey }

        context 'when developers are allowed to create the tag' do
          let!(:protected_tag) do
            create(:protected_tag,
                   :developers_can_create,
                   project: project,
                   name: ref)
          end

          it { is_expected.to be_truthy }
        end
      end
    end

    context 'when user is a master' do
      before do
        project.add_master(user)
      end

      it { is_expected.to be_truthy }

      context 'when the branch is protected' do
        let!(:protected_branch) do
          create(:protected_branch, project: project, name: ref)
        end

        it { is_expected.to be_truthy }
      end

      context 'when the tag is protected' do
        let(:ref) { 'v1.0.0' }

        let!(:protected_tag) do
          create(:protected_tag, project: project, name: ref)
        end

        it { is_expected.to be_truthy }

        context 'when no one can create the tag' do
          let!(:protected_tag) do
            create(:protected_tag,
                   :no_one_can_create,
                   project: project,
                   name: ref)
          end

          it { is_expected.to be_falsey }
        end
      end
    end

    context 'when owner cannot create pipeline' do
      it { is_expected.to be_falsey }
    end
  end
584
end