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

3
describe Ci::CreatePipelineService do
B
Bob Van Landuyt 已提交
4 5
  include ProjectForksHelper

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

  before do
11
    stub_repository_ci_yaml_file(sha: anything)
12 13 14
  end

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

28 29
      described_class.new(project, user, params).execute(
        source, trigger_request: trigger_request)
30 31
    end

32 33 34
    context 'valid params' do
      let(:pipeline) { execute_service }

35 36 37 38 39 40
      let(:pipeline_on_previous_commit) do
        execute_service(
          after: previous_commit_sha_from_ref('master')
        )
      end

41 42 43
      it 'creates a pipeline' do
        expect(pipeline).to be_kind_of(Ci::Pipeline)
        expect(pipeline).to be_valid
44
        expect(pipeline).to be_persisted
45
        expect(pipeline).to be_push
46 47 48
        expect(pipeline).to eq(project.pipelines.last)
        expect(pipeline).to have_attributes(user: user)
        expect(pipeline).to have_attributes(status: 'pending')
49
        expect(pipeline.repository_source?).to be true
50 51
        expect(pipeline.builds.first).to be_kind_of(Ci::Build)
      end
52

53 54
      it 'increments the prometheus counter' do
        expect(Gitlab::Metrics).to receive(:counter)
B
Ben Kochie 已提交
55
          .with(:pipelines_created_total, "Counter of pipelines created")
56 57 58 59 60
          .and_call_original

        pipeline
      end

61
      context 'when merge requests already exist for this source branch' do
62 63 64 65 66 67
        let(:merge_request_1) do
          create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
        end
        let(:merge_request_2) do
          create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
        end
68

69 70 71 72 73 74 75 76 77 78 79 80
        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

81 82 83 84 85 86
        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

            head_pipeline = execute_service
87

88 89 90 91 92 93
            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
94
          it 'does not update the head piepeline of MRs' do
95 96 97 98
            merge_request_1
            merge_request_2

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

100
            expect { execute_service(after: 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }.not_to raise_error
101 102 103 104 105 106

            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
107 108 109 110
        end

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

115
            head_pipeline = execute_service
116 117 118 119 120 121

            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 已提交
122
          let!(:project) { fork_project(target_project, nil, repository: true) }
123
          let!(:target_project) { create(:project, :repository) }
124

125
          it 'updates head pipeline for merge request' do
126 127 128 129
            merge_request = create(:merge_request, source_branch: 'master',
                                                   target_branch: "branch_1",
                                                   source_project: project,
                                                   target_project: target_project)
130

131
            head_pipeline = execute_service
132 133 134 135

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

137
        context 'when the pipeline is not the latest for the branch' do
138
          it 'does not update merge request head pipeline' do
139 140 141
            merge_request = create(:merge_request, source_branch: 'master',
                                                   target_branch: "branch_1",
                                                   source_project: project)
142

143
            allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false)
144

145
            execute_service
146 147 148 149

            expect(merge_request.reload.head_pipeline).to be_nil
          end
        end
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167

        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
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187

        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
188 189
      end

190 191 192 193 194
      context 'auto-cancel enabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'enabled')
        end

195 196
        it 'does not cancel HEAD pipeline' do
          pipeline
R
Rydkin Maxim 已提交
197
          pipeline_on_previous_commit
198

R
Rydkin Maxim 已提交
199
          expect(pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
200
        end
201 202

        it 'auto cancel pending non-HEAD pipelines' do
R
Rydkin Maxim 已提交
203
          pipeline_on_previous_commit
204 205
          pipeline

R
Rydkin Maxim 已提交
206
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
207 208 209
        end

        it 'does not cancel running outdated pipelines' do
R
Rydkin Maxim 已提交
210
          pipeline_on_previous_commit.run
211 212
          execute_service

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

        it 'cancel created outdated pipelines' do
R
Rydkin Maxim 已提交
217
          pipeline_on_previous_commit.update(status: 'created')
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 224 225 226 227 228 229
        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

230
          expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
231 232
        end
      end
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250

      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
251 252
    end

253 254
    context "skip tag if there is no build for it" do
      it "creates commit if there is appropriate job" do
255
        expect(execute_service).to be_persisted
256 257 258 259 260 261
      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)

262
        expect(execute_service).to be_persisted
263 264 265 266 267 268
      end
    end

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

269
      expect(execute_service).not_to be_persisted
270 271 272
      expect(Ci::Pipeline.count).to eq(0)
    end

273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
    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
299 300 301 302
    end

    context 'when commit contains a [ci skip] directive' do
      let(:message) { "some message[ci skip]" }
303 304 305 306 307 308 309 310 311 312 313

      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]"
      ]
314 315 316 317 318

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

319 320
      ci_messages.each do |ci_message|
        it "skips builds creation if the commit message is #{ci_message}" do
321
          pipeline = execute_service(message: ci_message)
322 323 324 325 326

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

329
      shared_examples 'creating a pipeline' do
R
Rydkin Maxim 已提交
330
        it 'does not skip pipeline creation' do
331
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
332

333
          pipeline = execute_service(message: commit_message)
334

335 336 337
          expect(pipeline).to be_persisted
          expect(pipeline.builds.first.name).to eq("rspec")
        end
338 339
      end

340 341
      context 'when commit message does not contain [ci skip] nor [skip ci]' do
        let(:commit_message) { 'some message' }
342

343
        it_behaves_like 'creating a pipeline'
344 345
      end

346 347
      context 'when commit message is nil' do
        let(:commit_message) { nil }
348

349
        it_behaves_like 'creating a pipeline'
350 351
      end

352 353 354 355 356
      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
357 358 359 360 361 362 363 364 365
    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
366
        result = execute_service
367 368 369 370 371 372 373 374 375 376 377 378 379 380

        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
381
        result = execute_service
382 383 384 385 386

        expect(result).to be_persisted
        expect(result.manual_actions).not_to be_empty
      end
    end
387 388 389

    context 'with environment' do
      before do
Z
Z.J. van de Weg 已提交
390
        config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' })
391 392 393 394
        stub_ci_pipeline_yaml_file(config)
      end

      it 'creates the environment' do
395
        result = execute_service
396 397 398 399 400

        expect(result).to be_persisted
        expect(Environment.find_by(name: "review/master")).not_to be_nil
      end
    end
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415

    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
416

417 418
    context 'when builds with auto-retries are configured' do
      before do
G
Grzegorz Bizon 已提交
419
        config = YAML.dump(rspec: { script: 'rspec', retry: 2 })
420 421 422 423 424 425 426
        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 已提交
427
        expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
428 429
      end
    end
430

431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
    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 已提交
446 447
        let(:pipeline) { execute_service }

448 449 450 451
        before do
          project.add_master(user)
        end

S
Shinya Maeda 已提交
452 453 454
        it 'creates a protected pipeline' do
          expect(pipeline).to be_persisted
          expect(pipeline).to be_protected
455 456 457
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
458 459 460 461 462 463 464 465 466 467 468 469 470

      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 已提交
471 472 473
        let(:user) { create(:user) }
        let(:trigger) { create(:ci_trigger, owner: user) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
474

S
Shinya Maeda 已提交
475 476
        before do
          project.add_developer(user)
477 478 479 480 481 482 483 484 485 486
        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 已提交
487 488 489
        let(:user) { create(:user) }
        let(:trigger) { create(:ci_trigger, owner: user) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
490

S
Shinya Maeda 已提交
491 492
        before do
          project.add_master(user)
493 494
        end

S
Shinya Maeda 已提交
495
        it 'creates a pipeline' do
496 497 498 499 500
          expect(execute_service(trigger_request: trigger_request))
            .to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
    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
520 521 522 523

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

S
Shinya Maeda 已提交
528 529 530
        it 'creates an unprotected pipeline' do
          expect(pipeline).to be_persisted
          expect(pipeline).not_to be_protected
531
          expect(Ci::Pipeline.count).to eq(1)
532 533 534
        end
      end
    end
535

K
Kamil Trzcinski 已提交
536
    context 'when pipeline is running for a tag' do
537 538
      before do
        config = YAML.dump(test: { script: 'test', only: ['branches'] },
K
Kamil Trzcinski 已提交
539
                           deploy: { script: 'deploy', only: ['tags'] })
540 541 542 543 544

        stub_ci_pipeline_yaml_file(config)
      end

      it 'creates a tagged pipeline' do
K
Kamil Trzcinski 已提交
545
        pipeline = execute_service(ref: 'v1.0.0')
546 547 548 549

        expect(pipeline.tag?).to be true
      end
    end
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564

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

      subject { execute_service(variables: variables) }

      it 'creates a pipeline with specified variables' do
        expect(subject.variables.count).to eq(variables.count)
        expect(subject.variables.first.key).to eq(variables.first[:key])
        expect(subject.variables.last.secret_value).to eq(variables.last[:secret_value])
      end
    end
565 566
  end
end