create_pipeline_service_spec.rb 15.7 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 11 12 13 14

  before do
    stub_ci_pipeline_to_return_yaml_file
  end

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

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

30 31 32
    context 'valid params' do
      let(:pipeline) { execute_service }

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

39 40 41
      it 'creates a pipeline' do
        expect(pipeline).to be_kind_of(Ci::Pipeline)
        expect(pipeline).to be_valid
42
        expect(pipeline).to be_persisted
43
        expect(pipeline).to be_push
44 45 46 47 48
        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
49

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

        pipeline
      end

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

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

          head_pipeline = execute_service
69

70 71 72 73 74 75
          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
76 77 78
            merge_request = create(:merge_request, source_branch: 'feature',
                                                   target_branch: "branch_1",
                                                   source_project: project)
79

80
            head_pipeline = execute_service
81 82 83 84 85 86

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

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

96
            head_pipeline = execute_service
97 98 99 100

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

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

108 109
            allow_any_instance_of(Ci::Pipeline)
              .to receive(:latest?).and_return(false)
110

111
            execute_service
112 113 114 115

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

        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
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153

        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
154 155
      end

156 157 158 159 160
      context 'auto-cancel enabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'enabled')
        end

161 162
        it 'does not cancel HEAD pipeline' do
          pipeline
R
Rydkin Maxim 已提交
163
          pipeline_on_previous_commit
164

R
Rydkin Maxim 已提交
165
          expect(pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
166
        end
167 168

        it 'auto cancel pending non-HEAD pipelines' do
R
Rydkin Maxim 已提交
169
          pipeline_on_previous_commit
170 171
          pipeline

R
Rydkin Maxim 已提交
172
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
173 174 175
        end

        it 'does not cancel running outdated pipelines' do
R
Rydkin Maxim 已提交
176
          pipeline_on_previous_commit.run
177 178
          execute_service

R
Rydkin Maxim 已提交
179
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'running', auto_canceled_by_id: nil)
180 181 182
        end

        it 'cancel created outdated pipelines' do
R
Rydkin Maxim 已提交
183
          pipeline_on_previous_commit.update(status: 'created')
184 185
          pipeline

R
Rydkin Maxim 已提交
186
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
187 188 189 190 191 192 193 194 195
        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

196
          expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
197 198
        end
      end
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216

      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
217 218
    end

219 220
    context "skip tag if there is no build for it" do
      it "creates commit if there is appropriate job" do
221
        expect(execute_service).to be_persisted
222 223 224 225 226 227
      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)

228
        expect(execute_service).to be_persisted
229 230 231 232 233 234
      end
    end

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

235
      expect(execute_service).not_to be_persisted
236 237 238
      expect(Ci::Pipeline.count).to eq(0)
    end

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
    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
265 266 267 268
    end

    context 'when commit contains a [ci skip] directive' do
      let(:message) { "some message[ci skip]" }
269 270 271 272 273 274 275 276 277 278 279

      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]"
      ]
280 281 282 283 284

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

285 286
      ci_messages.each do |ci_message|
        it "skips builds creation if the commit message is #{ci_message}" do
287
          pipeline = execute_service(message: ci_message)
288 289 290 291 292

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

295
      shared_examples 'creating a pipeline' do
R
Rydkin Maxim 已提交
296
        it 'does not skip pipeline creation' do
297
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
298

299
          pipeline = execute_service(message: commit_message)
300

301 302 303
          expect(pipeline).to be_persisted
          expect(pipeline.builds.first.name).to eq("rspec")
        end
304 305
      end

306 307
      context 'when commit message does not contain [ci skip] nor [skip ci]' do
        let(:commit_message) { 'some message' }
308

309
        it_behaves_like 'creating a pipeline'
310 311
      end

312 313
      context 'when commit message is nil' do
        let(:commit_message) { nil }
314

315
        it_behaves_like 'creating a pipeline'
316 317
      end

318 319 320 321 322
      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
323 324 325 326 327 328 329 330 331
    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
332
        result = execute_service
333 334 335 336 337 338 339 340 341 342 343 344 345 346

        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
347
        result = execute_service
348 349 350 351 352

        expect(result).to be_persisted
        expect(result.manual_actions).not_to be_empty
      end
    end
353 354 355

    context 'with environment' do
      before do
Z
Z.J. van de Weg 已提交
356
        config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' })
357 358 359 360
        stub_ci_pipeline_yaml_file(config)
      end

      it 'creates the environment' do
361
        result = execute_service
362 363 364 365 366

        expect(result).to be_persisted
        expect(Environment.find_by(name: "review/master")).not_to be_nil
      end
    end
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381

    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
382

383 384
    context 'when builds with auto-retries are configured' do
      before do
G
Grzegorz Bizon 已提交
385
        config = YAML.dump(rspec: { script: 'rspec', retry: 2 })
386 387 388 389 390 391 392
        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 已提交
393
        expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
394 395
      end
    end
396

397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
    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 已提交
412 413
        let(:pipeline) { execute_service }

414 415 416 417
        before do
          project.add_master(user)
        end

S
Shinya Maeda 已提交
418 419 420
        it 'creates a protected pipeline' do
          expect(pipeline).to be_persisted
          expect(pipeline).to be_protected
421 422 423
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
424 425 426 427 428 429 430 431 432 433 434 435 436

      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 已提交
437 438 439
        let(:user) { create(:user) }
        let(:trigger) { create(:ci_trigger, owner: user) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
440

S
Shinya Maeda 已提交
441 442
        before do
          project.add_developer(user)
443 444 445 446 447 448 449 450 451 452
        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 已提交
453 454 455
        let(:user) { create(:user) }
        let(:trigger) { create(:ci_trigger, owner: user) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
456

S
Shinya Maeda 已提交
457 458
        before do
          project.add_master(user)
459 460
        end

S
Shinya Maeda 已提交
461
        it 'creates a pipeline' do
462 463 464 465 466
          expect(execute_service(trigger_request: trigger_request))
            .to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
    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
486 487 488 489

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

S
Shinya Maeda 已提交
494 495 496
        it 'creates an unprotected pipeline' do
          expect(pipeline).to be_persisted
          expect(pipeline).not_to be_protected
497
          expect(Ci::Pipeline.count).to eq(1)
498 499 500
        end
      end
    end
501 502
  end
end