pipeline_spec.rb 49.4 KB
Newer Older
D
Douwe Maan 已提交
1 2
require 'spec_helper'

3
describe Ci::Pipeline, :mailer do
4
  let(:user) { create(:user) }
5
  set(:project) { create(:project) }
6 7 8 9

  let(:pipeline) do
    create(:ci_empty_pipeline, status: :created, project: project)
  end
D
Douwe Maan 已提交
10

K
Kamil Trzcinski 已提交
11
  it { is_expected.to belong_to(:project) }
12
  it { is_expected.to belong_to(:user) }
13
  it { is_expected.to belong_to(:auto_canceled_by) }
14
  it { is_expected.to belong_to(:pipeline_schedule) }
15

16
  it { is_expected.to have_many(:statuses) }
K
Kamil Trzcinski 已提交
17
  it { is_expected.to have_many(:trigger_requests) }
S
init  
Shinya Maeda 已提交
18
  it { is_expected.to have_many(:variables) }
D
Dmitriy Zaporozhets 已提交
19
  it { is_expected.to have_many(:builds) }
20
  it { is_expected.to have_many(:auto_canceled_pipelines) }
21
  it { is_expected.to have_many(:auto_canceled_jobs) }
22

23 24
  it { is_expected.to validate_presence_of(:sha) }
  it { is_expected.to validate_presence_of(:status) }
D
Douwe Maan 已提交
25

D
Dmitriy Zaporozhets 已提交
26 27 28
  it { is_expected.to respond_to :git_author_name }
  it { is_expected.to respond_to :git_author_email }
  it { is_expected.to respond_to :short_sha }
29
  it { is_expected.to delegate_method(:full_path).to(:project).with_prefix }
D
Douwe Maan 已提交
30

31 32 33 34 35 36 37
  describe 'associations' do
    it 'has a bidirectional relationship with projects' do
      expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:pipelines)
      expect(Project.reflect_on_association(:pipelines).has_inverse?).to eq(:project)
    end
  end

38 39 40 41
  describe 'modules' do
    it_behaves_like 'AtomicInternalId' do
      let(:internal_id_attribute) { :iid }
      let(:instance) { build(:ci_pipeline) }
42
      let(:scope) { :project }
43 44
      let(:scope_attrs) { { project: instance.project } }
      let(:usage) { :ci_pipelines }
45
      let(:allow_nil) { true }
46 47 48
    end
  end

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
  describe '#source' do
    context 'when creating new pipeline' do
      let(:pipeline) do
        build(:ci_empty_pipeline, status: :created, project: project, source: nil)
      end

      it "prevents from creating an object" do
        expect(pipeline).not_to be_valid
      end
    end

    context 'when updating existing pipeline' do
      before do
        pipeline.update_attribute(:source, nil)
      end

      it "object is valid" do
        expect(pipeline).to be_valid
      end
    end
  end

71 72 73 74 75 76 77 78
  describe '#block' do
    it 'changes pipeline status to manual' do
      expect(pipeline.block).to be true
      expect(pipeline.reload).to be_manual
      expect(pipeline.reload).to be_blocked
    end
  end

79
  describe '#valid_commit_sha' do
D
Douwe Maan 已提交
80 81
    context 'commit.sha can not start with 00000000' do
      before do
82 83
        pipeline.sha = '0' * 40
        pipeline.valid_commit_sha
D
Douwe Maan 已提交
84 85
      end

86
      it('commit errors should not be empty') { expect(pipeline.errors).not_to be_empty }
D
Douwe Maan 已提交
87 88 89
    end
  end

90
  describe '#short_sha' do
91
    subject { pipeline.short_sha }
D
Douwe Maan 已提交
92

D
Dmitriy Zaporozhets 已提交
93 94 95
    it 'has 8 items' do
      expect(subject.size).to eq(8)
    end
96
    it { expect(pipeline.sha).to start_with(subject) }
D
Douwe Maan 已提交
97 98
  end

99
  describe '#retried' do
100
    subject { pipeline.retried }
K
Kamil Trzcinski 已提交
101 102

    before do
103 104
      @build1 = create(:ci_build, pipeline: pipeline, name: 'deploy', retried: true)
      @build2 = create(:ci_build, pipeline: pipeline, name: 'deploy')
K
Kamil Trzcinski 已提交
105 106 107
    end

    it 'returns old builds' do
108
      is_expected.to contain_exactly(@build1)
K
Kamil Trzcinski 已提交
109 110 111
    end
  end

D
Douwe Maan 已提交
112
  describe "coverage" do
113
    let(:project) { create(:project, build_coverage_regex: "/.*/") }
114
    let(:pipeline) { create(:ci_empty_pipeline, project: project) }
D
Douwe Maan 已提交
115 116

    it "calculates average when there are two builds with coverage" do
117 118
      create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline)
      create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline)
119
      expect(pipeline.coverage).to eq("35.00")
D
Douwe Maan 已提交
120 121 122
    end

    it "calculates average when there are two builds with coverage and one with nil" do
123 124 125
      create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline)
      create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline)
      create(:ci_build, pipeline: pipeline)
126
      expect(pipeline.coverage).to eq("35.00")
D
Douwe Maan 已提交
127 128 129
    end

    it "calculates average when there are two builds with coverage and one is retried" do
130 131 132
      create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline)
      create(:ci_build, name: "rubocop", coverage: 30, pipeline: pipeline, retried: true)
      create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline)
133
      expect(pipeline.coverage).to eq("35.00")
D
Douwe Maan 已提交
134 135 136
    end

    it "calculates average when there is one build without coverage" do
137
      FactoryBot.create(:ci_build, pipeline: pipeline)
138
      expect(pipeline.coverage).to be_nil
D
Douwe Maan 已提交
139 140
    end
  end
K
Kamil Trzcinski 已提交
141 142

  describe '#retryable?' do
143
    subject { pipeline.retryable? }
K
Kamil Trzcinski 已提交
144 145 146

    context 'no failed builds' do
      before do
147
        create_build('rspec', 'success')
K
Kamil Trzcinski 已提交
148 149
      end

150
      it 'is not retryable' do
K
Kamil Trzcinski 已提交
151 152
        is_expected.to be_falsey
      end
153 154 155 156 157 158 159 160 161 162

      context 'one canceled job' do
        before do
          create_build('rubocop', 'canceled')
        end

        it 'is retryable' do
          is_expected.to be_truthy
        end
      end
K
Kamil Trzcinski 已提交
163 164 165 166
    end

    context 'with failed builds' do
      before do
167 168
        create_build('rspec', 'running')
        create_build('rubocop', 'failed')
K
Kamil Trzcinski 已提交
169 170
      end

171
      it 'is retryable' do
K
Kamil Trzcinski 已提交
172 173 174
        is_expected.to be_truthy
      end
    end
175 176 177 178

    def create_build(name, status)
      create(:ci_build, name: name, status: status, pipeline: pipeline)
    end
K
Kamil Trzcinski 已提交
179 180
  end

181 182 183 184 185 186 187 188 189 190 191 192 193 194
  describe '#persisted_variables' do
    context 'when pipeline is not persisted yet' do
      subject { build(:ci_pipeline).persisted_variables }

      it 'does not contain some variables' do
        keys = subject.map { |variable| variable[:key] }

        expect(keys).not_to include 'CI_PIPELINE_ID'
      end
    end

    context 'when pipeline is persisted' do
      subject { build_stubbed(:ci_pipeline).persisted_variables }

195
      it 'does contains persisted variables' do
196 197
        keys = subject.map { |variable| variable[:key] }

198
        expect(keys).to eq %w[CI_PIPELINE_ID]
199 200 201 202
      end
    end
  end

203 204 205
  describe '#predefined_variables' do
    subject { pipeline.predefined_variables }

206
    it 'includes all predefined variables in a valid order' do
207
      keys = subject.map { |variable| variable[:key] }
208

209 210
      expect(keys).to eq %w[CI_PIPELINE_IID
                            CI_CONFIG_PATH
211 212 213 214
                            CI_PIPELINE_SOURCE
                            CI_COMMIT_MESSAGE
                            CI_COMMIT_TITLE
                            CI_COMMIT_DESCRIPTION]
215 216 217
    end
  end

218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
  describe '#protected_ref?' do
    it 'delegates method to project' do
      expect(pipeline).not_to be_protected_ref
    end
  end

  describe '#legacy_trigger' do
    let(:trigger_request) { create(:ci_trigger_request) }

    before do
      pipeline.trigger_requests << trigger_request
    end

    it 'returns first trigger request' do
      expect(pipeline.legacy_trigger).to eq trigger_request
    end
  end

236 237 238 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 265 266 267 268 269 270 271 272
  describe '#auto_canceled?' do
    subject { pipeline.auto_canceled? }

    context 'when it is canceled' do
      before do
        pipeline.cancel
      end

      context 'when there is auto_canceled_by' do
        before do
          pipeline.update(auto_canceled_by: create(:ci_empty_pipeline))
        end

        it 'is auto canceled' do
          is_expected.to be_truthy
        end
      end

      context 'when there is no auto_canceled_by' do
        it 'is not auto canceled' do
          is_expected.to be_falsey
        end
      end

      context 'when it is retried and canceled manually' do
        before do
          pipeline.enqueue
          pipeline.cancel
        end

        it 'is not auto canceled' do
          is_expected.to be_falsey
        end
      end
    end
  end

273
  describe 'pipeline stages' do
274
    describe '#stage_seeds' do
275 276
      let(:pipeline) { build(:ci_pipeline, config: config) }
      let(:config) { { rspec: { script: 'rake' } } }
277 278

      it 'returns preseeded stage seeds object' do
279 280
        expect(pipeline.stage_seeds)
          .to all(be_a Gitlab::Ci::Pipeline::Seed::Base)
281
        expect(pipeline.stage_seeds.count).to eq 1
282 283
      end

284 285 286 287 288 289 290 291 292
      context 'when no refs policy is specified' do
        let(:config) do
          { production: { stage: 'deploy', script: 'cap prod' },
            rspec: { stage: 'test', script: 'rspec' },
            spinach: { stage: 'test', script: 'spinach' } }
        end

        it 'correctly fabricates a stage seeds object' do
          seeds = pipeline.stage_seeds
293

294 295 296 297 298 299
          expect(seeds.size).to eq 2
          expect(seeds.first.attributes[:name]).to eq 'test'
          expect(seeds.second.attributes[:name]).to eq 'deploy'
          expect(seeds.dig(0, 0, :name)).to eq 'rspec'
          expect(seeds.dig(0, 1, :name)).to eq 'spinach'
          expect(seeds.dig(1, 0, :name)).to eq 'production'
300
        end
301 302
      end

303 304 305
      context 'when refs policy is specified' do
        let(:pipeline) do
          build(:ci_pipeline, ref: 'feature', tag: true, config: config)
306
        end
307

308 309 310
        let(:config) do
          { production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
            spinach: { stage: 'test', script: 'spinach', only: ['tags'] } }
311
        end
K
Kamil Trzcinski 已提交
312

313 314
        it 'returns stage seeds only assigned to master to master' do
          seeds = pipeline.stage_seeds
315

316 317 318
          expect(seeds.size).to eq 1
          expect(seeds.first.attributes[:name]).to eq 'test'
          expect(seeds.dig(0, 0, :name)).to eq 'spinach'
319 320
        end
      end
321

322 323 324 325 326 327
      context 'when source policy is specified' do
        let(:pipeline) { build(:ci_pipeline, source: :schedule, config: config) }

        let(:config) do
          { production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] },
            spinach: { stage: 'test', script: 'spinach', only: ['schedules'] } }
328 329
        end

330 331
        it 'returns stage seeds only assigned to schedules' do
          seeds = pipeline.stage_seeds
332

333 334 335
          expect(seeds.size).to eq 1
          expect(seeds.first.attributes[:name]).to eq 'test'
          expect(seeds.dig(0, 0, :name)).to eq 'spinach'
336 337
        end
      end
338

339 340 341 342 343 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 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
      context 'when kubernetes policy is specified' do
        let(:config) do
          {
            spinach: { stage: 'test', script: 'spinach' },
            production: {
              stage: 'deploy',
              script: 'cap',
              only: { kubernetes: 'active' }
            }
          }
        end

        context 'when kubernetes is active' do
          shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
            it 'returns seeds for kubernetes dependent job' do
              seeds = pipeline.stage_seeds

              expect(seeds.size).to eq 2
              expect(seeds.dig(0, 0, :name)).to eq 'spinach'
              expect(seeds.dig(1, 0, :name)).to eq 'production'
            end
          end

          context 'when user configured kubernetes from Integration > Kubernetes' do
            let(:project) { create(:kubernetes_project) }
            let(:pipeline) { build(:ci_pipeline, project: project, config: config) }

            it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
          end

          context 'when user configured kubernetes from CI/CD > Clusters' do
            let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
            let(:project) { cluster.project }
            let(:pipeline) { build(:ci_pipeline, project: project, config: config) }

            it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
          end
        end

        context 'when kubernetes is not active' do
          it 'does not return seeds for kubernetes dependent job' do
            seeds = pipeline.stage_seeds

            expect(seeds.size).to eq 1
            expect(seeds.dig(0, 0, :name)).to eq 'spinach'
          end
        end
386
      end
387 388 389 390 391 392 393 394 395 396 397 398 399

      context 'when variables policy is specified' do
        let(:config) do
          { unit: { script: 'minitest', only: { variables: ['$CI_PIPELINE_SOURCE'] } },
            feature: { script: 'spinach', only: { variables: ['$UNDEFINED'] } } }
        end

        it 'returns stage seeds only when variables expression is truthy' do
          seeds = pipeline.stage_seeds

          expect(seeds.size).to eq 1
          expect(seeds.dig(0, 0, :name)).to eq 'unit'
        end
S
Shinya Maeda 已提交
400 401 402 403 404 405 406 407 408 409 410 411 412 413

        context "when pipeline iid is used for 'only' keyword" do
          let(:config) do
            { rspec: { script: 'rspec', only: { variables: ['$CI_PIPELINE_IID == 2'] } },
              prod: { script: 'cap prod', only: { variables: ['$CI_PIPELINE_IID == 1'] } } }
          end

          it 'returns stage seeds only when variables expression is truthy' do
            seeds = pipeline.stage_seeds

            expect(seeds.size).to eq 1
            expect(seeds.dig(0, 0, :name)).to eq 'prod'
          end
        end
414
      end
415
    end
416

417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
    describe '#seeds_size' do
      context 'when refs policy is specified' do
        let(:config) do
          { production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
            spinach: { stage: 'test', script: 'spinach', only: ['tags'] } }
        end

        let(:pipeline) do
          build(:ci_pipeline, ref: 'feature', tag: true, config: config)
        end

        it 'returns real seeds size' do
          expect(pipeline.seeds_size).to eq 1
        end
      end
    end

434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
    describe 'legacy stages' do
      before do
        create(:commit_status, pipeline: pipeline,
                               stage: 'build',
                               name: 'linux',
                               stage_idx: 0,
                               status: 'success')

        create(:commit_status, pipeline: pipeline,
                               stage: 'build',
                               name: 'mac',
                               stage_idx: 0,
                               status: 'failed')

        create(:commit_status, pipeline: pipeline,
                               stage: 'deploy',
                               name: 'staging',
                               stage_idx: 2,
                               status: 'running')

        create(:commit_status, pipeline: pipeline,
                               stage: 'test',
                               name: 'rspec',
                               stage_idx: 1,
                               status: 'success')
459
      end
K
Kamil Trzcinski 已提交
460

461 462
      describe '#legacy_stages' do
        subject { pipeline.legacy_stages }
K
Kamil Trzcinski 已提交
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
        context 'stages list' do
          it 'returns ordered list of stages' do
            expect(subject.map(&:name)).to eq(%w[build test deploy])
          end
        end

        context 'stages with statuses' do
          let(:statuses) do
            subject.map { |stage| [stage.name, stage.status] }
          end

          it 'returns list of stages with correct statuses' do
            expect(statuses).to eq([%w(build failed),
                                    %w(test success),
                                    %w(deploy running)])
          end

          context 'when commit status is retried' do
            before do
              create(:commit_status, pipeline: pipeline,
                                     stage: 'build',
                                     name: 'mac',
                                     stage_idx: 0,
                                     status: 'success')

              pipeline.process!
            end

            it 'ignores the previous state' do
              expect(statuses).to eq([%w(build success),
                                      %w(test success),
                                      %w(deploy running)])
            end
          end
        end

        context 'when there is a stage with warnings' do
          before do
            create(:commit_status, pipeline: pipeline,
                                   stage: 'deploy',
                                   name: 'prod:2',
                                   stage_idx: 2,
                                   status: 'failed',
                                   allow_failure: true)
          end

          it 'populates stage with correct number of warnings' do
            deploy_stage = pipeline.legacy_stages.third

            expect(deploy_stage).not_to receive(:statuses)
            expect(deploy_stage).to have_warnings
          end
        end
K
Kamil Trzcinski 已提交
517
      end
K
Kamil Trzcinski 已提交
518

519 520 521 522 523 524 525 526 527 528 529
      describe '#stages_count' do
        it 'returns a valid number of stages' do
          expect(pipeline.stages_count).to eq(3)
        end
      end

      describe '#stages_names' do
        it 'returns a valid names of stages' do
          expect(pipeline.stages_names).to eq(%w(build test deploy))
        end
      end
K
Kamil Trzcinski 已提交
530 531
    end

532 533 534 535 536 537 538 539 540 541 542
    describe '#legacy_stage' do
      subject { pipeline.legacy_stage('test') }

      context 'with status in stage' do
        before do
          create(:commit_status, pipeline: pipeline, stage: 'test')
        end

        it { expect(subject).to be_a Ci::LegacyStage }
        it { expect(subject.name).to eq 'test' }
        it { expect(subject.statuses).not_to be_empty }
K
Kamil Trzcinski 已提交
543
      end
K
Kamil Trzcinski 已提交
544

545 546 547 548 549 550 551 552
      context 'without status in stage' do
        before do
          create(:commit_status, pipeline: pipeline, stage: 'build')
        end

        it 'return stage object' do
          is_expected.to be_nil
        end
K
Kamil Trzcinski 已提交
553 554 555 556
      end
    end
  end

557
  describe 'state machine' do
558
    let(:current) { Time.now.change(usec: 0) }
559 560 561
    let(:build) { create_build('build1', queued_at: 0) }
    let(:build_b) { create_build('build2', queued_at: 0) }
    let(:build_c) { create_build('build3', queued_at: 0) }
K
Kamil Trzcinski 已提交
562

563
    describe '#duration' do
564 565 566 567 568 569 570 571
      context 'when multiple builds are finished' do
        before do
          travel_to(current + 30) do
            build.run!
            build.success!
            build_b.run!
            build_c.run!
          end
L
Lin Jen-Shin 已提交
572

573 574 575 576 577 578 579
          travel_to(current + 40) do
            build_b.drop!
          end

          travel_to(current + 70) do
            build_c.success!
          end
580
        end
581

582 583 584 585
        it 'matches sum of builds duration' do
          pipeline.reload

          expect(pipeline.duration).to eq(40)
586
        end
587 588
      end

589 590
      context 'when pipeline becomes blocked' do
        let!(:build) { create_build('build:1') }
591
        let!(:action) { create_build('manual:action', :manual) }
592

593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
        before do
          travel_to(current + 1.minute) do
            build.run!
          end

          travel_to(current + 5.minutes) do
            build.success!
          end
        end

        it 'recalculates pipeline duration' do
          pipeline.reload

          expect(pipeline).to be_manual
          expect(pipeline.duration).to eq 4.minutes
        end
609
      end
K
Kamil Trzcinski 已提交
610 611
    end

612 613 614
    describe '#started_at' do
      it 'updates on transitioning to running' do
        build.run
K
Kamil Trzcinski 已提交
615

616 617 618
        expect(pipeline.reload.started_at).not_to be_nil
      end

619
      it 'does not update on transitioning to success' do
620 621 622
        build.success

        expect(pipeline.reload.started_at).to be_nil
K
Kamil Trzcinski 已提交
623 624 625
      end
    end

626 627 628
    describe '#finished_at' do
      it 'updates on transitioning to success' do
        build.success
K
Kamil Trzcinski 已提交
629

630
        expect(pipeline.reload.finished_at).not_to be_nil
K
Kamil Trzcinski 已提交
631 632
      end

633
      it 'does not update on transitioning to running' do
634 635 636
        build.run

        expect(pipeline.reload.finished_at).to be_nil
K
Kamil Trzcinski 已提交
637 638
      end
    end
639

640
    describe 'merge request metrics' do
641
      let(:project) { create(:project, :repository) }
642
      let(:pipeline) { FactoryBot.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
643 644
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }

645 646 647
      before do
        expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id)
      end
648

649 650 651
      context 'when transitioning to running' do
        it 'schedules metrics workers' do
          pipeline.run
652 653 654 655
        end
      end

      context 'when transitioning to success' do
656 657
        it 'schedules metrics workers' do
          pipeline.succeed
658 659 660
        end
      end
    end
661

662
    describe 'pipeline caching' do
663 664
      it 'performs ExpirePipelinesCacheWorker' do
        expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id)
T
Toon Claes 已提交
665 666 667 668 669

        pipeline.cancel
      end
    end

670 671
    def create_build(name, *traits, queued_at: current, started_from: 0, **opts)
      create(:ci_build, *traits,
L
Lin Jen-Shin 已提交
672 673
             name: name,
             pipeline: pipeline,
674
             queued_at: queued_at,
675 676
             started_at: queued_at + started_from,
             **opts)
L
Lin Jen-Shin 已提交
677
    end
K
Kamil Trzcinski 已提交
678
  end
K
Kamil Trzcinski 已提交
679 680

  describe '#branch?' do
681
    subject { pipeline.branch? }
K
Kamil Trzcinski 已提交
682 683 684

    context 'is not a tag' do
      before do
685
        pipeline.tag = false
K
Kamil Trzcinski 已提交
686 687 688 689 690 691 692 693 694
      end

      it 'return true when tag is set to false' do
        is_expected.to be_truthy
      end
    end

    context 'is not a tag' do
      before do
695
        pipeline.tag = true
K
Kamil Trzcinski 已提交
696 697 698 699 700 701 702
      end

      it 'return false when tag is set to true' do
        is_expected.to be_falsey
      end
    end
  end
703

704
  context 'with non-empty project' do
705
    let(:project) { create(:project, :repository) }
706 707 708 709 710 711 712

    let(:pipeline) do
      create(:ci_pipeline,
             project: project,
             ref: project.default_branch,
             sha: project.commit.sha)
    end
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733

    describe '#latest?' do
      context 'with latest sha' do
        it 'returns true' do
          expect(pipeline).to be_latest
        end
      end

      context 'with not latest sha' do
        before do
          pipeline.update(
            sha: project.commit("#{project.default_branch}~1").sha)
        end

        it 'returns false' do
          expect(pipeline).not_to be_latest
        end
      end
    end
  end

734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
  describe '#manual_actions' do
    subject { pipeline.manual_actions }

    it 'when none defined' do
      is_expected.to be_empty
    end

    context 'when action defined' do
      let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }

      it 'returns one action' do
        is_expected.to contain_exactly(manual)
      end

      context 'there are multiple of the same name' do
        let!(:manual2) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }

751 752 753 754
        before do
          manual.update(retried: true)
        end

755 756 757 758 759 760
        it 'returns latest one' do
          is_expected.to contain_exactly(manual2)
        end
      end
    end
  end
761

762
  describe '#has_kubernetes_active?' do
763
    context 'when kubernetes is active' do
764
      shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
765 766 767 768 769 770 771 772
        it 'returns true' do
          expect(pipeline).to have_kubernetes_active
        end
      end

      context 'when user configured kubernetes from Integration > Kubernetes' do
        let(:project) { create(:kubernetes_project) }

773
        it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
774 775 776 777 778
      end

      context 'when user configured kubernetes from CI/CD > Clusters' do
        let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
        let(:project) { cluster.project }
779

780
        it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
781 782 783
      end
    end

784
    context 'when kubernetes is not active' do
785
      it 'returns false' do
786
        expect(pipeline).not_to have_kubernetes_active
787 788 789 790
      end
    end
  end

C
Connor Shea 已提交
791 792
  describe '#has_warnings?' do
    subject { pipeline.has_warnings? }
793 794 795

    context 'build which is allowed to fail fails' do
      before do
C
Connor Shea 已提交
796 797
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop'
798
      end
799

800 801 802 803 804 805 806
      it 'returns true' do
        is_expected.to be_truthy
      end
    end

    context 'build which is allowed to fail succeeds' do
      before do
C
Connor Shea 已提交
807 808
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :success, pipeline: pipeline, name: 'rubocop'
809
      end
810

811 812 813 814
      it 'returns false' do
        is_expected.to be_falsey
      end
    end
C
Connor Shea 已提交
815 816 817 818 819 820 821 822 823 824 825 826

    context 'build is retried and succeeds' do
      before do
        create :ci_build, :success, pipeline: pipeline, name: 'rubocop'
        create :ci_build, :failed, pipeline: pipeline, name: 'rspec'
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
      end

      it 'returns false' do
        is_expected.to be_falsey
      end
    end
827
  end
828

829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855
  describe '#number_of_warnings' do
    it 'returns the number of warnings' do
      create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop')

      expect(pipeline.number_of_warnings).to eq(1)
    end

    it 'supports eager loading of the number of warnings' do
      pipeline2 = create(:ci_empty_pipeline, status: :created, project: project)

      create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop')
      create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline2, name: 'rubocop')

      pipelines = project.pipelines.to_a

      pipelines.each(&:number_of_warnings)

      # To run the queries we need to actually use the lazy objects, which we do
      # by just sending "to_i" to them.
      amount = ActiveRecord::QueryRecorder
        .new { pipelines.each { |p| p.number_of_warnings.to_i } }
        .count

      expect(amount).to eq(1)
    end
  end

856
  shared_context 'with some outdated pipelines' do
857
    before do
858 859 860 861
      create_pipeline(:canceled, 'ref', 'A', project)
      create_pipeline(:success, 'ref', 'A', project)
      create_pipeline(:failed, 'ref', 'B', project)
      create_pipeline(:skipped, 'feature', 'C', project)
862 863
    end

864 865 866 867 868 869 870 871
    def create_pipeline(status, ref, sha, project)
      create(
        :ci_empty_pipeline,
        status: status,
        ref: ref,
        sha: sha,
        project: project
      )
872 873 874
    end
  end

875
  describe '.newest_first' do
876
    include_context 'with some outdated pipelines'
877

878 879 880
    it 'returns the pipelines from new to old' do
      expect(described_class.newest_first.pluck(:status))
        .to eq(%w[skipped failed success canceled])
881 882 883 884
    end
  end

  describe '.latest_status' do
885
    include_context 'with some outdated pipelines'
886 887

    context 'when no ref is specified' do
888 889
      it 'returns the status of the latest pipeline' do
        expect(described_class.latest_status).to eq('skipped')
890 891 892 893
      end
    end

    context 'when ref is specified' do
894 895
      it 'returns the status of the latest pipeline for the given ref' do
        expect(described_class.latest_status('ref')).to eq('failed')
896 897 898 899
      end
    end
  end

900 901 902 903
  describe '.latest_successful_for' do
    include_context 'with some outdated pipelines'

    let!(:latest_successful_pipeline) do
904
      create_pipeline(:success, 'ref', 'D', project)
905 906 907
    end

    it 'returns the latest successful pipeline' do
908 909
      expect(described_class.latest_successful_for('ref'))
        .to eq(latest_successful_pipeline)
910 911 912
    end
  end

913 914 915
  describe '.latest_successful_for_refs' do
    include_context 'with some outdated pipelines'

916 917 918 919 920 921 922
    let!(:latest_successful_pipeline1) do
      create_pipeline(:success, 'ref1', 'D', project)
    end

    let!(:latest_successful_pipeline2) do
      create_pipeline(:success, 'ref2', 'D', project)
    end
923 924 925 926 927 928 929 930

    it 'returns the latest successful pipeline for both refs' do
      refs = %w(ref1 ref2 ref3)

      expect(described_class.latest_successful_for_refs(refs)).to eq({ 'ref1' => latest_successful_pipeline1, 'ref2' => latest_successful_pipeline2 })
    end
  end

931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986
  describe '.latest_status_per_commit' do
    let(:project) { create(:project) }

    before do
      pairs = [
        %w[success ref1 123],
        %w[manual master 123],
        %w[failed ref 456]
      ]

      pairs.each do |(status, ref, sha)|
        create(
          :ci_empty_pipeline,
          status: status,
          ref: ref,
          sha: sha,
          project: project
        )
      end
    end

    context 'without a ref' do
      it 'returns a Hash containing the latest status per commit for all refs' do
        expect(described_class.latest_status_per_commit(%w[123 456]))
          .to eq({ '123' => 'manual', '456' => 'failed' })
      end

      it 'only includes the status of the given commit SHAs' do
        expect(described_class.latest_status_per_commit(%w[123]))
          .to eq({ '123' => 'manual' })
      end

      context 'when there are two pipelines for a ref and SHA' do
        it 'returns the status of the latest pipeline' do
          create(
            :ci_empty_pipeline,
            status: 'failed',
            ref: 'master',
            sha: '123',
            project: project
          )

          expect(described_class.latest_status_per_commit(%w[123]))
            .to eq({ '123' => 'failed' })
        end
      end
    end

    context 'with a ref' do
      it 'only includes the pipelines for the given ref' do
        expect(described_class.latest_status_per_commit(%w[123 456], 'master'))
          .to eq({ '123' => 'manual' })
      end
    end
  end

987 988 989 990 991 992
  describe '.internal_sources' do
    subject { described_class.internal_sources }

    it { is_expected.to be_an(Array) }
  end

993
  describe '#status' do
994 995 996
    let(:build) do
      create(:ci_build, :created, pipeline: pipeline, name: 'test')
    end
997 998 999 1000

    subject { pipeline.reload.status }

    context 'on queuing' do
1001 1002 1003
      before do
        build.enqueue
      end
1004 1005 1006 1007 1008 1009

      it { is_expected.to eq('pending') }
    end

    context 'on run' do
      before do
1010
        build.enqueue
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
        build.run
      end

      it { is_expected.to eq('running') }
    end

    context 'on drop' do
      before do
        build.drop
      end

      it { is_expected.to eq('failed') }
    end

    context 'on success' do
      before do
        build.success
      end

      it { is_expected.to eq('success') }
    end

    context 'on cancel' do
      before do
        build.cancel
      end

1038 1039 1040 1041 1042 1043 1044
      context 'when build is pending' do
        let(:build) do
          create(:ci_build, :pending, pipeline: pipeline)
        end

        it { is_expected.to eq('canceled') }
      end
1045
    end
1046 1047 1048

    context 'on failure and build retry' do
      before do
S
Shinya Maeda 已提交
1049 1050
        stub_not_protect_default_branch

1051
        build.drop
1052
        project.add_developer(user)
1053 1054

        Ci::Build.retry(build, user)
1055 1056 1057 1058 1059 1060 1061 1062
      end

      # We are changing a state: created > failed > running
      # Instead of: created > failed > pending
      # Since the pipeline already run, so it should not be pending anymore

      it { is_expected.to eq('running') }
    end
1063
  end
1064

1065 1066 1067 1068
  describe '#ci_yaml_file_path' do
    subject { pipeline.ci_yaml_file_path }

    it 'returns the path from project' do
1069
      allow(pipeline.project).to receive(:ci_config_path) { 'custom/path' }
1070 1071 1072 1073 1074

      is_expected.to eq('custom/path')
    end

    it 'returns default when custom path is nil' do
1075
      allow(pipeline.project).to receive(:ci_config_path) { nil }
1076 1077 1078 1079 1080

      is_expected.to eq('.gitlab-ci.yml')
    end

    it 'returns default when custom path is empty' do
1081
      allow(pipeline.project).to receive(:ci_config_path) { '' }
1082 1083 1084 1085 1086

      is_expected.to eq('.gitlab-ci.yml')
    end
  end

1087
  describe '#set_config_source' do
1088 1089 1090
    context 'when pipelines does not contain needed data' do
      it 'defines source to be unknown' do
        pipeline.set_config_source
1091

1092
        expect(pipeline).to be_unknown_source
1093
      end
1094
    end
1095

1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
    context 'when pipeline contains all needed data' do
      let(:pipeline) do
        create(:ci_pipeline, project: project,
                             sha: '1234',
                             ref: 'master',
                             source: :push)
      end

      context 'when the repository has a config file' do
        before do
          allow(project.repository).to receive(:gitlab_ci_yml_for)
            .and_return('config')
1108
        end
1109

1110 1111
        it 'defines source to be from repository' do
          pipeline.set_config_source
1112

1113 1114
          expect(pipeline).to be_repository_source
        end
1115

1116 1117
        context 'when loading an object' do
          let(:new_pipeline) { Ci::Pipeline.find(pipeline.id) }
1118

1119 1120 1121
          it 'does not redefine the source' do
            # force to overwrite the source
            pipeline.unknown_source!
1122

1123
            expect(new_pipeline).to be_unknown_source
1124
          end
1125
        end
1126
      end
1127

1128 1129
      context 'when the repository does not have a config file' do
        let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
1130

1131 1132 1133 1134 1135
        context 'auto devops enabled' do
          before do
            stub_application_setting(auto_devops_enabled: true)
            allow(project).to receive(:ci_config_path) { 'custom' }
          end
1136

1137 1138
          it 'defines source to be auto devops' do
            pipeline.set_config_source
1139

1140
            expect(pipeline).to be_auto_devops_source
1141
          end
1142
        end
1143 1144 1145
      end
    end
  end
1146

1147 1148
  describe '#ci_yaml_file' do
    let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
1149

1150
    context 'the source is unknown' do
1151 1152 1153
      before do
        pipeline.unknown_source!
      end
1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167

      it 'returns the configuration if found' do
        allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
          .and_return('config')

        expect(pipeline.ci_yaml_file).to be_a(String)
        expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
        expect(pipeline.yaml_errors).to be_nil
      end

      it 'sets yaml errors if not found' do
        expect(pipeline.ci_yaml_file).to be_nil
        expect(pipeline.yaml_errors)
            .to start_with('Failed to load CI/CD config file')
1168 1169 1170
      end
    end

1171
    context 'the source is the repository' do
1172 1173 1174
      before do
        pipeline.repository_source!
      end
1175

1176 1177 1178 1179 1180 1181 1182
      it 'returns the configuration if found' do
        allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
          .and_return('config')

        expect(pipeline.ci_yaml_file).to be_a(String)
        expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
        expect(pipeline.yaml_errors).to be_nil
1183
      end
1184
    end
1185

1186
    context 'when the source is auto_devops_source' do
1187
      before do
1188
        stub_application_setting(auto_devops_enabled: true)
1189 1190
        pipeline.auto_devops_source!
      end
1191

1192 1193 1194
      it 'finds the implied config' do
        expect(pipeline.ci_yaml_file).to eq(implied_yml)
        expect(pipeline.yaml_errors).to be_nil
1195
      end
1196 1197 1198
    end
  end

1199
  describe '#detailed_status' do
1200 1201
    subject { pipeline.detailed_status(user) }

1202 1203 1204 1205
    context 'when pipeline is created' do
      let(:pipeline) { create(:ci_pipeline, status: :created) }

      it 'returns detailed status for created pipeline' do
1206
        expect(subject.text).to eq 'created'
1207 1208 1209 1210 1211 1212 1213
      end
    end

    context 'when pipeline is pending' do
      let(:pipeline) { create(:ci_pipeline, status: :pending) }

      it 'returns detailed status for pending pipeline' do
1214
        expect(subject.text).to eq 'pending'
1215 1216 1217 1218 1219 1220 1221
      end
    end

    context 'when pipeline is running' do
      let(:pipeline) { create(:ci_pipeline, status: :running) }

      it 'returns detailed status for running pipeline' do
1222
        expect(subject.text).to eq 'running'
1223 1224 1225 1226 1227 1228 1229
      end
    end

    context 'when pipeline is successful' do
      let(:pipeline) { create(:ci_pipeline, status: :success) }

      it 'returns detailed status for successful pipeline' do
1230
        expect(subject.text).to eq 'passed'
1231 1232 1233 1234 1235 1236 1237
      end
    end

    context 'when pipeline is failed' do
      let(:pipeline) { create(:ci_pipeline, status: :failed) }

      it 'returns detailed status for failed pipeline' do
1238
        expect(subject.text).to eq 'failed'
1239 1240 1241 1242 1243 1244 1245
      end
    end

    context 'when pipeline is canceled' do
      let(:pipeline) { create(:ci_pipeline, status: :canceled) }

      it 'returns detailed status for canceled pipeline' do
1246
        expect(subject.text).to eq 'canceled'
1247 1248 1249 1250 1251 1252 1253
      end
    end

    context 'when pipeline is skipped' do
      let(:pipeline) { create(:ci_pipeline, status: :skipped) }

      it 'returns detailed status for skipped pipeline' do
1254
        expect(subject.text).to eq 'skipped'
1255 1256 1257
      end
    end

1258 1259 1260 1261
    context 'when pipeline is blocked' do
      let(:pipeline) { create(:ci_pipeline, status: :manual) }

      it 'returns detailed status for blocked pipeline' do
1262
        expect(subject.text).to eq 'blocked'
1263 1264 1265
      end
    end

1266 1267 1268 1269 1270 1271 1272 1273
    context 'when pipeline is successful but with warnings' do
      let(:pipeline) { create(:ci_pipeline, status: :success) }

      before do
        create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline)
      end

      it 'retruns detailed status for successful pipeline with warnings' do
1274
        expect(subject.label).to eq 'passed with warnings'
1275 1276 1277 1278
      end
    end
  end

1279
  describe '#cancelable?' do
1280 1281
    %i[created running pending].each do |status0|
      context "when there is a build #{status0}" do
1282
        before do
1283
          create(:ci_build, status0, pipeline: pipeline)
1284 1285
        end

1286 1287 1288
        it 'is cancelable' do
          expect(pipeline.cancelable?).to be_truthy
        end
1289 1290
      end

1291
      context "when there is an external job #{status0}" do
1292
        before do
1293
          create(:generic_commit_status, status0, pipeline: pipeline)
1294 1295
        end

1296 1297 1298
        it 'is cancelable' do
          expect(pipeline.cancelable?).to be_truthy
        end
1299
      end
1300

1301
      %i[success failed canceled].each do |status1|
1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331
        context "when there are generic_commit_status jobs for #{status0} and #{status1}" do
          before do
            create(:generic_commit_status, status0, pipeline: pipeline)
            create(:generic_commit_status, status1, pipeline: pipeline)
          end

          it 'is cancelable' do
            expect(pipeline.cancelable?).to be_truthy
          end
        end

        context "when there are generic_commit_status and ci_build jobs for #{status0} and #{status1}" do
          before do
            create(:generic_commit_status, status0, pipeline: pipeline)
            create(:ci_build, status1, pipeline: pipeline)
          end

          it 'is cancelable' do
            expect(pipeline.cancelable?).to be_truthy
          end
        end

        context "when there are ci_build jobs for #{status0} and #{status1}" do
          before do
            create(:ci_build, status0, pipeline: pipeline)
            create(:ci_build, status1, pipeline: pipeline)
          end

          it 'is cancelable' do
            expect(pipeline.cancelable?).to be_truthy
1332
          end
1333 1334
        end
      end
1335 1336 1337 1338 1339 1340 1341 1342
    end

    %i[success failed canceled].each do |status|
      context "when there is a build #{status}" do
        before do
          create(:ci_build, status, pipeline: pipeline)
        end

1343 1344 1345
        it 'is not cancelable' do
          expect(pipeline.cancelable?).to be_falsey
        end
1346 1347 1348 1349 1350 1351 1352
      end

      context "when there is an external job #{status}" do
        before do
          create(:generic_commit_status, status, pipeline: pipeline)
        end

1353 1354 1355
        it 'is not cancelable' do
          expect(pipeline.cancelable?).to be_falsey
        end
1356 1357
      end
    end
1358 1359 1360 1361 1362 1363 1364 1365 1366 1367

    context 'when there is a manual action present in the pipeline' do
      before do
        create(:ci_build, :manual, pipeline: pipeline)
      end

      it 'is not cancelable' do
        expect(pipeline).not_to be_cancelable
      end
    end
1368 1369
  end

1370
  describe '#cancel_running' do
1371 1372
    let(:latest_status) { pipeline.statuses.pluck(:status) }

1373
    context 'when there is a running external job and a regular job' do
1374
      before do
1375
        create(:ci_build, :running, pipeline: pipeline)
1376 1377 1378 1379 1380 1381
        create(:generic_commit_status, :running, pipeline: pipeline)

        pipeline.cancel_running
      end

      it 'cancels both jobs' do
1382 1383 1384 1385
        expect(latest_status).to contain_exactly('canceled', 'canceled')
      end
    end

1386
    context 'when jobs are in different stages' do
1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397
      before do
        create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
        create(:ci_build, :running, stage_idx: 1, pipeline: pipeline)

        pipeline.cancel_running
      end

      it 'cancels both jobs' do
        expect(latest_status).to contain_exactly('canceled', 'canceled')
      end
    end
1398 1399 1400 1401 1402 1403 1404 1405 1406

    context 'when there are created builds present in the pipeline' do
      before do
        create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
        create(:ci_build, :created, stage_idx: 1, pipeline: pipeline)

        pipeline.cancel_running
      end

1407
      it 'cancels created builds' do
D
Douwe Maan 已提交
1408
        expect(latest_status).to eq %w(canceled canceled)
1409 1410
      end
    end
1411 1412 1413 1414 1415
  end

  describe '#retry_failed' do
    let(:latest_status) { pipeline.statuses.latest.pluck(:status) }

1416
    before do
S
Shinya Maeda 已提交
1417 1418
      stub_not_protect_default_branch

1419
      project.add_developer(user)
1420 1421
    end

1422 1423 1424 1425 1426
    context 'when there is a failed build and failed external status' do
      before do
        create(:ci_build, :failed, name: 'build', pipeline: pipeline)
        create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline)

1427
        pipeline.retry_failed(user)
1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439
      end

      it 'retries only build' do
        expect(latest_status).to contain_exactly('pending', 'failed')
      end
    end

    context 'when builds are in different stages' do
      before do
        create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
        create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline)

1440
        pipeline.retry_failed(user)
1441 1442 1443
      end

      it 'retries both builds' do
1444
        expect(latest_status).to contain_exactly('pending', 'created')
1445 1446 1447 1448 1449 1450 1451 1452
      end
    end

    context 'when there are canceled and failed' do
      before do
        create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
        create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline)

1453
        pipeline.retry_failed(user)
1454 1455 1456
      end

      it 'retries both builds' do
1457
        expect(latest_status).to contain_exactly('pending', 'created')
1458 1459 1460 1461
      end
    end
  end

1462
  describe '#execute_hooks' do
L
Lin Jen-Shin 已提交
1463
    let!(:build_a) { create_build('a', 0) }
S
Shinya Maeda 已提交
1464
    let!(:build_b) { create_build('b', 0) }
K
Kamil Trzcinski 已提交
1465

1466 1467 1468 1469 1470
    let!(:hook) do
      create(:project_hook, project: project, pipeline_events: enabled)
    end

    before do
A
Alexander Randa 已提交
1471
      WebHookWorker.drain
1472 1473 1474 1475 1476
    end

    context 'with pipeline hooks enabled' do
      let(:enabled) { true }

K
Kamil Trzcinski 已提交
1477 1478 1479 1480
      before do
        WebMock.stub_request(:post, hook.url)
      end

1481
      context 'with multiple builds' do
K
Kamil Trzcinski 已提交
1482 1483
        context 'when build is queued' do
          before do
1484 1485
            build_a.enqueue
            build_b.enqueue
K
Kamil Trzcinski 已提交
1486
          end
1487

L
Lin Jen-Shin 已提交
1488
          it 'receives a pending event once' do
1489
            expect(WebMock).to have_requested_pipeline_hook('pending').once
K
Kamil Trzcinski 已提交
1490 1491
          end
        end
1492

K
Kamil Trzcinski 已提交
1493 1494
        context 'when build is run' do
          before do
1495
            build_a.enqueue
K
Kamil Trzcinski 已提交
1496
            build_a.run
1497
            build_b.enqueue
K
Kamil Trzcinski 已提交
1498 1499
            build_b.run
          end
1500

L
Lin Jen-Shin 已提交
1501
          it 'receives a running event once' do
1502
            expect(WebMock).to have_requested_pipeline_hook('running').once
K
Kamil Trzcinski 已提交
1503
          end
1504 1505
        end

K
Kamil Trzcinski 已提交
1506 1507 1508
        context 'when all builds succeed' do
          before do
            build_a.success
K
Kamil Trzcinski 已提交
1509 1510 1511

            # We have to reload build_b as this is in next stage and it gets triggered by PipelineProcessWorker
            build_b.reload.success
K
Kamil Trzcinski 已提交
1512 1513
          end

L
Lin Jen-Shin 已提交
1514
          it 'receives a success event once' do
1515
            expect(WebMock).to have_requested_pipeline_hook('success').once
K
Kamil Trzcinski 已提交
1516
          end
1517 1518
        end

L
Lin Jen-Shin 已提交
1519
        context 'when stage one failed' do
S
Shinya Maeda 已提交
1520 1521
          let!(:build_b) { create_build('b', 1) }

L
Lin Jen-Shin 已提交
1522 1523 1524 1525
          before do
            build_a.drop
          end

L
Lin Jen-Shin 已提交
1526
          it 'receives a failed event once' do
L
Lin Jen-Shin 已提交
1527 1528 1529 1530
            expect(WebMock).to have_requested_pipeline_hook('failed').once
          end
        end

1531
        def have_requested_pipeline_hook(status)
K
Kamil Trzcinski 已提交
1532
          have_requested(:post, hook.url).with do |req|
1533 1534 1535 1536
            json_body = JSON.parse(req.body)
            json_body['object_attributes']['status'] == status &&
              json_body['builds'].length == 2
          end
1537
        end
1538
      end
1539 1540 1541 1542 1543
    end

    context 'with pipeline hooks disabled' do
      let(:enabled) { false }

K
Kamil Trzcinski 已提交
1544
      before do
1545 1546
        build_a.enqueue
        build_b.enqueue
K
Kamil Trzcinski 已提交
1547 1548
      end

1549 1550 1551 1552
      it 'did not execute pipeline_hook after touched' do
        expect(WebMock).not_to have_requested(:post, hook.url)
      end
    end
K
Kamil Trzcinski 已提交
1553

L
Lin Jen-Shin 已提交
1554 1555 1556 1557 1558 1559
    def create_build(name, stage_idx)
      create(:ci_build,
             :created,
             pipeline: pipeline,
             name: name,
             stage_idx: stage_idx)
K
Kamil Trzcinski 已提交
1560
    end
1561
  end
1562 1563

  describe "#merge_requests" do
1564
    let(:project) { create(:project) }
1565
    let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') }
1566 1567

    it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do
1568
      allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { 'a288a022a53a5a944fae87bcec6efc87b7061808' }
F
Felipe Artur 已提交
1569
      merge_request = create(:merge_request, source_project: project, head_pipeline: pipeline, source_branch: pipeline.ref)
1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586

      expect(pipeline.merge_requests).to eq([merge_request])
    end

    it "doesn't return merge requests whose source branch doesn't match the pipeline's ref" do
      create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master')

      expect(pipeline.merge_requests).to be_empty
    end

    it "doesn't return merge requests whose `diff_head_sha` doesn't match the pipeline's SHA" do
      create(:merge_request, source_project: project, source_branch: pipeline.ref)
      allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { '97de212e80737a608d939f648d959671fb0a0142b' }

      expect(pipeline.merge_requests).to be_empty
    end
  end
1587

1588
  describe "#all_merge_requests" do
1589
    let(:project) { create(:project) }
1590
    let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master') }
1591

1592
    it "returns all merge requests having the same source branch" do
1593 1594 1595 1596 1597
      merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)

      expect(pipeline.all_merge_requests).to eq([merge_request])
    end

1598
    it "doesn't return merge requests having a different source branch" do
1599 1600 1601 1602 1603 1604
      create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master')

      expect(pipeline.all_merge_requests).to be_empty
    end
  end

1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616
  describe '#stuck?' do
    before do
      create(:ci_build, :pending, pipeline: pipeline)
    end

    context 'when pipeline is stuck' do
      it 'is stuck' do
        expect(pipeline).to be_stuck
      end
    end

    context 'when pipeline is not stuck' do
1617 1618 1619
      before do
        create(:ci_runner, :shared, :online)
      end
1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648

      it 'is not stuck' do
        expect(pipeline).not_to be_stuck
      end
    end
  end

  describe '#has_yaml_errors?' do
    context 'when pipeline has errors' do
      let(:pipeline) do
        create(:ci_pipeline, config: { rspec: nil })
      end

      it 'contains yaml errors' do
        expect(pipeline).to have_yaml_errors
      end
    end

    context 'when pipeline does not have errors' do
      let(:pipeline) do
        create(:ci_pipeline, config: { rspec: { script: 'rake test' } })
      end

      it 'does not containyaml errors' do
        expect(pipeline).not_to have_yaml_errors
      end
    end
  end

1649
  describe 'notifications when pipeline success or failed' do
1650
    let(:project) { create(:project, :repository) }
L
Lin Jen-Shin 已提交
1651 1652 1653 1654 1655 1656 1657 1658

    let(:pipeline) do
      create(:ci_pipeline,
             project: project,
             sha: project.commit('master').sha,
             user: create(:user))
    end

1659
    before do
1660
      project.add_developer(pipeline.user)
1661

1662 1663
      pipeline.user.global_notification_setting
        .update(level: 'custom', failed_pipeline: true, success_pipeline: true)
S
Sean McGivern 已提交
1664

1665 1666 1667 1668
      perform_enqueued_jobs do
        pipeline.enqueue
        pipeline.run
      end
1669 1670 1671 1672
    end

    shared_examples 'sending a notification' do
      it 'sends an email' do
1673
        should_only_email(pipeline.user, kind: :bcc)
1674 1675 1676 1677 1678
      end
    end

    shared_examples 'not sending any notification' do
      it 'does not send any email' do
1679
        should_not_email_anyone
1680 1681 1682 1683 1684 1685
      end
    end

    context 'with success pipeline' do
      before do
        perform_enqueued_jobs do
L
Lin Jen-Shin 已提交
1686
          pipeline.succeed
1687 1688
        end
      end
L
Lin Jen-Shin 已提交
1689 1690

      it_behaves_like 'sending a notification'
1691 1692 1693 1694 1695
    end

    context 'with failed pipeline' do
      before do
        perform_enqueued_jobs do
1696 1697
          create(:ci_build, :failed, pipeline: pipeline)
          create(:generic_commit_status, :failed, pipeline: pipeline)
1698

1699
          pipeline.drop
1700 1701
        end
      end
L
Lin Jen-Shin 已提交
1702 1703

      it_behaves_like 'sending a notification'
1704 1705 1706 1707 1708 1709 1710 1711
    end

    context 'with skipped pipeline' do
      before do
        perform_enqueued_jobs do
          pipeline.skip
        end
      end
L
Lin Jen-Shin 已提交
1712 1713

      it_behaves_like 'not sending any notification'
1714 1715 1716 1717 1718 1719 1720 1721
    end

    context 'with cancelled pipeline' do
      before do
        perform_enqueued_jobs do
          pipeline.cancel
        end
      end
L
Lin Jen-Shin 已提交
1722 1723

      it_behaves_like 'not sending any notification'
1724 1725
    end
  end
1726 1727 1728 1729 1730 1731 1732 1733

  describe '#latest_builds_with_artifacts' do
    let!(:pipeline) { create(:ci_pipeline, :success) }

    let!(:build) do
      create(:ci_build, :success, :artifacts, pipeline: pipeline)
    end

1734 1735 1736 1737
    it 'returns an Array' do
      expect(pipeline.latest_builds_with_artifacts).to be_an_instance_of(Array)
    end

1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749
    it 'returns the latest builds' do
      expect(pipeline.latest_builds_with_artifacts).to eq([build])
    end

    it 'memoizes the returned relation' do
      query_count = ActiveRecord::QueryRecorder
        .new { 2.times { pipeline.latest_builds_with_artifacts.to_a } }
        .count

      expect(query_count).to eq(1)
    end
  end
1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761

  describe '#total_size' do
    let!(:build_job1) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
    let!(:build_job2) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
    let!(:test_job_failed_and_retried) { create(:ci_build, :failed, :retried, pipeline: pipeline, stage_idx: 1) }
    let!(:second_test_job) { create(:ci_build, pipeline: pipeline, stage_idx: 1) }
    let!(:deploy_job) { create(:ci_build, pipeline: pipeline, stage_idx: 2) }

    it 'returns all jobs (including failed and retried)' do
      expect(pipeline.total_size).to eq(5)
    end
  end
D
Douwe Maan 已提交
1762
end