create_pipeline_service_spec.rb 16.0 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 58 59
        it 'updates head pipeline of each merge request' do
          merge_request_1 = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
          merge_request_2 = create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
60

61
          head_pipeline = pipeline
62

63 64 65 66 67 68
          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
69
            merge_request = create(:merge_request, source_branch: 'feature', target_branch: "branch_1", source_project: project)
70 71 72 73 74 75 76 77

            head_pipeline = pipeline

            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
78
          let!(:target_project) { create(:project, :repository) }
79 80 81 82 83 84 85 86 87 88 89
          let!(:forked_project_link) { create(:forked_project_link, forked_to_project: project, forked_from_project: target_project) }

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

            head_pipeline = pipeline

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

91
        context 'when the pipeline is not the latest for the branch' do
92 93
          it 'does not update merge request head pipeline' do
            merge_request = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
94 95

            allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false)
96 97 98 99 100 101

            pipeline

            expect(merge_request.reload.head_pipeline).to be_nil
          end
        end
102 103
      end

104 105 106 107 108
      context 'auto-cancel enabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'enabled')
        end

109 110
        it 'does not cancel HEAD pipeline' do
          pipeline
R
Rydkin Maxim 已提交
111
          pipeline_on_previous_commit
112

R
Rydkin Maxim 已提交
113
          expect(pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
114
        end
115 116

        it 'auto cancel pending non-HEAD pipelines' do
R
Rydkin Maxim 已提交
117
          pipeline_on_previous_commit
118 119
          pipeline

R
Rydkin Maxim 已提交
120
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
121 122 123
        end

        it 'does not cancel running outdated pipelines' do
R
Rydkin Maxim 已提交
124
          pipeline_on_previous_commit.run
125 126
          execute_service

R
Rydkin Maxim 已提交
127
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'running', auto_canceled_by_id: nil)
128 129 130
        end

        it 'cancel created outdated pipelines' do
R
Rydkin Maxim 已提交
131
          pipeline_on_previous_commit.update(status: 'created')
132 133
          pipeline

R
Rydkin Maxim 已提交
134
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
135 136 137 138 139 140 141 142 143
        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

144
          expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
145 146
        end
      end
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164

      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
165 166
    end

167 168
    context "skip tag if there is no build for it" do
      it "creates commit if there is appropriate job" do
169
        expect(execute_service).to be_persisted
170 171 172 173 174 175
      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)

176
        expect(execute_service).to be_persisted
177 178 179 180 181 182
      end
    end

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

183
      expect(execute_service).not_to be_persisted
184 185 186
      expect(Ci::Pipeline.count).to eq(0)
    end

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
    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
213 214 215 216
    end

    context 'when commit contains a [ci skip] directive' do
      let(:message) { "some message[ci skip]" }
217 218 219 220 221 222 223 224 225 226 227

      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]"
      ]
228 229 230 231 232

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

233 234
      ci_messages.each do |ci_message|
        it "skips builds creation if the commit message is #{ci_message}" do
235
          pipeline = execute_service(message: ci_message)
236 237 238 239 240

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

243
      shared_examples 'creating a pipeline' do
R
Rydkin Maxim 已提交
244
        it 'does not skip pipeline creation' do
245
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
246

247
          pipeline = execute_service(message: commit_message)
248

249 250 251
          expect(pipeline).to be_persisted
          expect(pipeline.builds.first.name).to eq("rspec")
        end
252 253
      end

254 255
      context 'when commit message does not contain [ci skip] nor [skip ci]' do
        let(:commit_message) { 'some message' }
256

257
        it_behaves_like 'creating a pipeline'
258 259
      end

260 261
      context 'when commit message is nil' do
        let(:commit_message) { nil }
262

263
        it_behaves_like 'creating a pipeline'
264 265
      end

266 267 268 269 270
      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
271 272 273 274 275 276 277 278 279
    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
280
        result = execute_service
281 282 283 284 285 286 287 288 289 290 291 292 293 294

        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
295
        result = execute_service
296 297 298 299 300

        expect(result).to be_persisted
        expect(result.manual_actions).not_to be_empty
      end
    end
301 302 303

    context 'with environment' do
      before do
Z
Z.J. van de Weg 已提交
304
        config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' })
305 306 307 308
        stub_ci_pipeline_yaml_file(config)
      end

      it 'creates the environment' do
309
        result = execute_service
310 311 312 313 314

        expect(result).to be_persisted
        expect(Environment.find_by(name: "review/master")).not_to be_nil
      end
    end
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329

    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
330

331 332
    context 'when builds with auto-retries are configured' do
      before do
G
Grzegorz Bizon 已提交
333
        config = YAML.dump(rspec: { script: 'rspec', retry: 2 })
334 335 336 337 338 339 340
        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 已提交
341
        expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
342 343
      end
    end
344

345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
    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
        before do
          project.add_master(user)
        end

        it 'creates a pipeline' do
          expect(execute_service).to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415

      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
        let(:user) {}

        let(:trigger_request) do
          create(:ci_trigger_request).tap do |request|
            user = create(:user)
            project.add_developer(user)
            request.trigger.update(owner: user)
          end
        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
        let(:user) {}

        let(:trigger_request) do
          create(:ci_trigger_request).tap do |request|
            user = create(:user)
            project.add_master(user)
            request.trigger.update(owner: user)
          end
        end

        it 'does not create a pipeline' do
          expect(execute_service(trigger_request: trigger_request))
            .to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
        end
      end
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
    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
435 436 437 438 439 440

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

441
        it 'creates a pipeline' do
442
          expect(execute_service(trigger_request: trigger_request))
443 444
            .to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
445 446 447
        end
      end
    end
448
  end
449 450 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 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

  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
549
end