create_pipeline_service_spec.rb 16.5 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 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
        expect(pipeline).to eq(project.pipelines.last)
        expect(pipeline).to have_attributes(user: user)
        expect(pipeline).to have_attributes(status: 'pending')
47
        expect(pipeline.repository_source?).to be true
48 49
        expect(pipeline.builds.first).to be_kind_of(Ci::Build)
      end
50

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

        pipeline
      end

59
      context 'when merge requests already exist for this source branch' do
60 61 62 63 64 65
        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
66

67 68 69 70 71 72
        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
73

74 75 76 77 78 79 80 81 82 83 84
            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
          it 'raises the ArgumentError error from worker and does not update the head piepeline of MRs' do
            merge_request_1
            merge_request_2

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

86 87 88 89 90 91 92
            expect { execute_service(after: 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }.to raise_error(ArgumentError)

            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
93 94 95 96
        end

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

101
            head_pipeline = execute_service
102 103 104 105 106 107

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

111
          it 'updates head pipeline for merge request' do
112 113 114 115
            merge_request = create(:merge_request, source_branch: 'master',
                                                   target_branch: "branch_1",
                                                   source_project: project,
                                                   target_project: target_project)
116

117
            head_pipeline = execute_service
118 119 120 121

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

123
        context 'when the pipeline is not the latest for the branch' do
124
          it 'does not update merge request head pipeline' do
125 126 127
            merge_request = create(:merge_request, source_branch: 'master',
                                                   target_branch: "branch_1",
                                                   source_project: project)
128

129
            allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false)
130

131
            execute_service
132 133 134 135

            expect(merge_request.reload.head_pipeline).to be_nil
          end
        end
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153

        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
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173

        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
174 175
      end

176 177 178 179 180
      context 'auto-cancel enabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'enabled')
        end

181 182
        it 'does not cancel HEAD pipeline' do
          pipeline
R
Rydkin Maxim 已提交
183
          pipeline_on_previous_commit
184

R
Rydkin Maxim 已提交
185
          expect(pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
186
        end
187 188

        it 'auto cancel pending non-HEAD pipelines' do
R
Rydkin Maxim 已提交
189
          pipeline_on_previous_commit
190 191
          pipeline

R
Rydkin Maxim 已提交
192
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
193 194 195
        end

        it 'does not cancel running outdated pipelines' do
R
Rydkin Maxim 已提交
196
          pipeline_on_previous_commit.run
197 198
          execute_service

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

        it 'cancel created outdated pipelines' do
R
Rydkin Maxim 已提交
203
          pipeline_on_previous_commit.update(status: 'created')
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 210 211 212 213 214 215
        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

216
          expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
217 218
        end
      end
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236

      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
237 238
    end

239 240
    context "skip tag if there is no build for it" do
      it "creates commit if there is appropriate job" do
241
        expect(execute_service).to be_persisted
242 243 244 245 246 247
      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)

248
        expect(execute_service).to be_persisted
249 250 251 252 253 254
      end
    end

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

255
      expect(execute_service).not_to be_persisted
256 257 258
      expect(Ci::Pipeline.count).to eq(0)
    end

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
    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
285 286 287 288
    end

    context 'when commit contains a [ci skip] directive' do
      let(:message) { "some message[ci skip]" }
289 290 291 292 293 294 295 296 297 298 299

      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]"
      ]
300 301 302 303 304

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

305 306
      ci_messages.each do |ci_message|
        it "skips builds creation if the commit message is #{ci_message}" do
307
          pipeline = execute_service(message: ci_message)
308 309 310 311 312

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

315
      shared_examples 'creating a pipeline' do
R
Rydkin Maxim 已提交
316
        it 'does not skip pipeline creation' do
317
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
318

319
          pipeline = execute_service(message: commit_message)
320

321 322 323
          expect(pipeline).to be_persisted
          expect(pipeline.builds.first.name).to eq("rspec")
        end
324 325
      end

326 327
      context 'when commit message does not contain [ci skip] nor [skip ci]' do
        let(:commit_message) { 'some message' }
328

329
        it_behaves_like 'creating a pipeline'
330 331
      end

332 333
      context 'when commit message is nil' do
        let(:commit_message) { nil }
334

335
        it_behaves_like 'creating a pipeline'
336 337
      end

338 339 340 341 342
      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
343 344 345 346 347 348 349 350 351
    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
352
        result = execute_service
353 354 355 356 357 358 359 360 361 362 363 364 365 366

        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
367
        result = execute_service
368 369 370 371 372

        expect(result).to be_persisted
        expect(result.manual_actions).not_to be_empty
      end
    end
373 374 375

    context 'with environment' do
      before do
Z
Z.J. van de Weg 已提交
376
        config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' })
377 378 379 380
        stub_ci_pipeline_yaml_file(config)
      end

      it 'creates the environment' do
381
        result = execute_service
382 383 384 385 386

        expect(result).to be_persisted
        expect(Environment.find_by(name: "review/master")).not_to be_nil
      end
    end
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401

    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
402

403 404
    context 'when builds with auto-retries are configured' do
      before do
G
Grzegorz Bizon 已提交
405
        config = YAML.dump(rspec: { script: 'rspec', retry: 2 })
406 407 408 409 410 411 412
        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 已提交
413
        expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
414 415
      end
    end
416

417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
    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 已提交
432 433
        let(:pipeline) { execute_service }

434 435 436 437
        before do
          project.add_master(user)
        end

S
Shinya Maeda 已提交
438 439 440
        it 'creates a protected pipeline' do
          expect(pipeline).to be_persisted
          expect(pipeline).to be_protected
441 442 443
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
444 445 446 447 448 449 450 451 452 453 454 455 456

      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 已提交
457 458 459
        let(:user) { create(:user) }
        let(:trigger) { create(:ci_trigger, owner: user) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
460

S
Shinya Maeda 已提交
461 462
        before do
          project.add_developer(user)
463 464 465 466 467 468 469 470 471 472
        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 已提交
473 474 475
        let(:user) { create(:user) }
        let(:trigger) { create(:ci_trigger, owner: user) }
        let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
476

S
Shinya Maeda 已提交
477 478
        before do
          project.add_master(user)
479 480
        end

S
Shinya Maeda 已提交
481
        it 'creates a pipeline' do
482 483 484 485 486
          expect(execute_service(trigger_request: trigger_request))
            .to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
    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
506 507 508 509

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

S
Shinya Maeda 已提交
514 515 516
        it 'creates an unprotected pipeline' do
          expect(pipeline).to be_persisted
          expect(pipeline).not_to be_protected
517
          expect(Ci::Pipeline.count).to eq(1)
518 519 520
        end
      end
    end
521 522
  end
end