create_pipeline_service_spec.rb 16.6 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 114 115 116 117

            pipeline

            expect(merge_request.reload.head_pipeline).to be_nil
          end
        end
118 119
      end

120 121 122 123 124
      context 'auto-cancel enabled' do
        before do
          project.update(auto_cancel_pending_pipelines: 'enabled')
        end

125 126
        it 'does not cancel HEAD pipeline' do
          pipeline
R
Rydkin Maxim 已提交
127
          pipeline_on_previous_commit
128

R
Rydkin Maxim 已提交
129
          expect(pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
130
        end
131 132

        it 'auto cancel pending non-HEAD pipelines' do
R
Rydkin Maxim 已提交
133
          pipeline_on_previous_commit
134 135
          pipeline

R
Rydkin Maxim 已提交
136
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
137 138 139
        end

        it 'does not cancel running outdated pipelines' do
R
Rydkin Maxim 已提交
140
          pipeline_on_previous_commit.run
141 142
          execute_service

R
Rydkin Maxim 已提交
143
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'running', auto_canceled_by_id: nil)
144 145 146
        end

        it 'cancel created outdated pipelines' do
R
Rydkin Maxim 已提交
147
          pipeline_on_previous_commit.update(status: 'created')
148 149
          pipeline

R
Rydkin Maxim 已提交
150
          expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
151 152 153 154 155 156 157 158 159
        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

160
          expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
161 162
        end
      end
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180

      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
181 182
    end

183 184
    context "skip tag if there is no build for it" do
      it "creates commit if there is appropriate job" do
185
        expect(execute_service).to be_persisted
186 187 188 189 190 191
      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)

192
        expect(execute_service).to be_persisted
193 194 195 196 197 198
      end
    end

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

199
      expect(execute_service).not_to be_persisted
200 201 202
      expect(Ci::Pipeline.count).to eq(0)
    end

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
    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
229 230 231 232
    end

    context 'when commit contains a [ci skip] directive' do
      let(:message) { "some message[ci skip]" }
233 234 235 236 237 238 239 240 241 242 243

      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]"
      ]
244 245 246 247 248

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

249 250
      ci_messages.each do |ci_message|
        it "skips builds creation if the commit message is #{ci_message}" do
251
          pipeline = execute_service(message: ci_message)
252 253 254 255 256

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

259
      shared_examples 'creating a pipeline' do
R
Rydkin Maxim 已提交
260
        it 'does not skip pipeline creation' do
261
          allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
262

263
          pipeline = execute_service(message: commit_message)
264

265 266 267
          expect(pipeline).to be_persisted
          expect(pipeline.builds.first.name).to eq("rspec")
        end
268 269
      end

270 271
      context 'when commit message does not contain [ci skip] nor [skip ci]' do
        let(:commit_message) { 'some message' }
272

273
        it_behaves_like 'creating a pipeline'
274 275
      end

276 277
      context 'when commit message is nil' do
        let(:commit_message) { nil }
278

279
        it_behaves_like 'creating a pipeline'
280 281
      end

282 283 284 285 286
      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
287 288 289 290 291 292 293 294 295
    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
296
        result = execute_service
297 298 299 300 301 302 303 304 305 306 307 308 309 310

        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
311
        result = execute_service
312 313 314 315 316

        expect(result).to be_persisted
        expect(result.manual_actions).not_to be_empty
      end
    end
317 318 319

    context 'with environment' do
      before do
Z
Z.J. van de Weg 已提交
320
        config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' })
321 322 323 324
        stub_ci_pipeline_yaml_file(config)
      end

      it 'creates the environment' do
325
        result = execute_service
326 327 328 329 330

        expect(result).to be_persisted
        expect(Environment.find_by(name: "review/master")).not_to be_nil
      end
    end
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345

    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
346

347 348
    context 'when builds with auto-retries are configured' do
      before do
G
Grzegorz Bizon 已提交
349
        config = YAML.dump(rspec: { script: 'rspec', retry: 2 })
350 351 352 353 354 355 356
        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 已提交
357
        expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
358 359
      end
    end
360

361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
    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
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 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431

      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
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
    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
451 452 453 454 455 456

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

457
        it 'creates a pipeline' do
458
          expect(execute_service(trigger_request: trigger_request))
459 460
            .to be_persisted
          expect(Ci::Pipeline.count).to eq(1)
461 462 463
        end
      end
    end
464
  end
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 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564

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