pipeline_spec.rb 25.7 KB
Newer Older
Douwe Maan's avatar
Douwe Maan committed
1
2
require 'spec_helper'

3
describe Ci::Pipeline, models: true do
4
5
  include EmailHelpers

6
  let(:project) { FactoryGirl.create :empty_project }
7
  let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, status: 'created', project: project }
Douwe Maan's avatar
Douwe Maan committed
8

9
  it { is_expected.to belong_to(:project) }
10
11
  it { is_expected.to belong_to(:user) }

12
  it { is_expected.to have_many(:statuses) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
13
  it { is_expected.to have_many(:trigger_requests) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
14
  it { is_expected.to have_many(:builds) }
15

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
16
  it { is_expected.to validate_presence_of :sha }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
17
  it { is_expected.to validate_presence_of :status }
Douwe Maan's avatar
Douwe Maan committed
18

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
19
20
21
  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 }
Douwe Maan's avatar
Douwe Maan committed
22

23
  describe '#valid_commit_sha' do
Douwe Maan's avatar
Douwe Maan committed
24
25
    context 'commit.sha can not start with 00000000' do
      before do
26
27
        pipeline.sha = '0' * 40
        pipeline.valid_commit_sha
Douwe Maan's avatar
Douwe Maan committed
28
29
      end

30
      it('commit errors should not be empty') { expect(pipeline.errors).not_to be_empty }
Douwe Maan's avatar
Douwe Maan committed
31
32
33
    end
  end

34
  describe '#short_sha' do
35
    subject { pipeline.short_sha }
Douwe Maan's avatar
Douwe Maan committed
36

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
37
38
39
    it 'has 8 items' do
      expect(subject.size).to eq(8)
    end
40
    it { expect(pipeline.sha).to start_with(subject) }
Douwe Maan's avatar
Douwe Maan committed
41
42
  end

43
  describe '#retried' do
44
    subject { pipeline.retried }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
45
46

    before do
47
48
      @build1 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy'
      @build2 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy'
Kamil Trzcinski's avatar
Kamil Trzcinski committed
49
50
51
    end

    it 'returns old builds' do
52
      is_expected.to contain_exactly(@build1)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
53
54
55
    end
  end

Douwe Maan's avatar
Douwe Maan committed
56
  describe "coverage" do
57
    let(:project) { FactoryGirl.create :empty_project, build_coverage_regex: "/.*/" }
58
    let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, project: project }
Douwe Maan's avatar
Douwe Maan committed
59
60

    it "calculates average when there are two builds with coverage" do
61
62
63
      FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
      expect(pipeline.coverage).to eq("35.00")
Douwe Maan's avatar
Douwe Maan committed
64
65
66
    end

    it "calculates average when there are two builds with coverage and one with nil" do
67
68
69
70
      FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
      FactoryGirl.create :ci_build, pipeline: pipeline
      expect(pipeline.coverage).to eq("35.00")
Douwe Maan's avatar
Douwe Maan committed
71
72
73
    end

    it "calculates average when there are two builds with coverage and one is retried" do
74
75
76
77
      FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
      expect(pipeline.coverage).to eq("35.00")
Douwe Maan's avatar
Douwe Maan committed
78
79
80
    end

    it "calculates average when there is one build without coverage" do
81
82
      FactoryGirl.create :ci_build, pipeline: pipeline
      expect(pipeline.coverage).to be_nil
Douwe Maan's avatar
Douwe Maan committed
83
84
    end
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
85
86

  describe '#retryable?' do
87
    subject { pipeline.retryable? }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
88
89
90

    context 'no failed builds' do
      before do
91
        create_build('rspec', 'success')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
92
93
      end

94
      it 'is not retryable' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
95
96
        is_expected.to be_falsey
      end
97
98
99
100
101
102
103
104
105
106

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

        it 'is retryable' do
          is_expected.to be_truthy
        end
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
107
108
109
110
    end

    context 'with failed builds' do
      before do
111
112
        create_build('rspec', 'running')
        create_build('rubocop', 'failed')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
113
114
      end

115
      it 'is retryable' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
116
117
118
        is_expected.to be_truthy
      end
    end
119
120
121
122

    def create_build(name, status)
      create(:ci_build, name: name, status: status, pipeline: pipeline)
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
123
124
125
126
  end

  describe '#stages' do
    before do
127
128
129
130
      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')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
131
132
    end

133
134
135
136
137
138
139
140
141
142
143
144
    subject { pipeline.stages }

    context 'stages list' do
      it 'returns ordered list of stages' do
        expect(subject.map(&:name)).to eq(%w[build test deploy])
      end
    end

    it 'returns a valid number of stages' do
      expect(pipeline.stages_count).to eq(3)
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
145
146
147
148
    it 'returns a valid names of stages' do
      expect(pipeline.stages_name).to eq(['build', 'test', 'deploy'])
    end

149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
    context 'stages with statuses' do
      let(:statuses) do
        subject.map do |stage|
          [stage.name, stage.status]
        end
      end

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

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

        it 'ignores the previous state' do
          expect(statuses).to eq([['build', 'success'],
                                  ['test', 'success'],
                                  ['deploy', 'running']
                                 ])
        end
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
175
176
177
    end
  end

178
179
180
181
  describe '#stage' do
    subject { pipeline.stage('test') }

    context 'with status in stage' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
182
183
      before do
        create(:commit_status, pipeline: pipeline, stage: 'test')
184
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
185

186
187
188
      it { expect(subject).to be_a Ci::Stage }
      it { expect(subject.name).to eq 'test' }
      it { expect(subject.statuses).not_to be_empty }
189
190
191
    end

    context 'without status in stage' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
192
193
194
      before do
        create(:commit_status, pipeline: pipeline, stage: 'build')
      end
195
196
197
198
199
200
201

      it 'return stage object' do
        is_expected.to be_nil
      end
    end
  end

202
  describe 'state machine' do
203
    let(:current) { Time.now.change(usec: 0) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
204
205
206
    let(:build) { create_build('build1', 0) }
    let(:build_b) { create_build('build2', 0) }
    let(:build_c) { create_build('build3', 0) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
207

208
209
    describe '#duration' do
      before do
210
        travel_to(current + 30) do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
211
212
213
214
          build.run!
          build.success!
          build_b.run!
          build_c.run!
Lin Jen-Shin's avatar
Lin Jen-Shin committed
215
216
        end

217
        travel_to(current + 40) do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
218
          build_b.drop!
219
        end
220

221
        travel_to(current + 70) do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
222
          build_c.success!
223
        end
224
225
226
      end

      it 'matches sum of builds duration' do
227
228
        pipeline.reload

229
        expect(pipeline.duration).to eq(40)
230
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
231
232
    end

233
234
235
    describe '#started_at' do
      it 'updates on transitioning to running' do
        build.run
Kamil Trzcinski's avatar
Kamil Trzcinski committed
236

237
238
239
        expect(pipeline.reload.started_at).not_to be_nil
      end

240
      it 'does not update on transitioning to success' do
241
242
243
        build.success

        expect(pipeline.reload.started_at).to be_nil
Kamil Trzcinski's avatar
Kamil Trzcinski committed
244
245
246
      end
    end

247
248
249
    describe '#finished_at' do
      it 'updates on transitioning to success' do
        build.success
Kamil Trzcinski's avatar
Kamil Trzcinski committed
250

251
        expect(pipeline.reload.finished_at).not_to be_nil
Kamil Trzcinski's avatar
Kamil Trzcinski committed
252
253
      end

254
      it 'does not update on transitioning to running' do
255
256
257
        build.run

        expect(pipeline.reload.finished_at).to be_nil
Kamil Trzcinski's avatar
Kamil Trzcinski committed
258
259
      end
    end
260

261
    describe 'merge request metrics' do
262
263
264
265
      let(:project) { FactoryGirl.create :project }
      let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }

266
267
268
      before do
        expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id)
      end
269

270
271
272
      context 'when transitioning to running' do
        it 'schedules metrics workers' do
          pipeline.run
273
274
275
276
        end
      end

      context 'when transitioning to success' do
277
278
        it 'schedules metrics workers' do
          pipeline.succeed
279
280
281
        end
      end
    end
282

283
    def create_build(name, queued_at = current, started_from = 0)
Lin Jen-Shin's avatar
Lin Jen-Shin committed
284
285
286
      create(:ci_build,
             name: name,
             pipeline: pipeline,
287
288
             queued_at: queued_at,
             started_at: queued_at + started_from)
Lin Jen-Shin's avatar
Lin Jen-Shin committed
289
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
290
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
291
292

  describe '#branch?' do
293
    subject { pipeline.branch? }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
294
295
296

    context 'is not a tag' do
      before do
297
        pipeline.tag = false
Kamil Trzcinski's avatar
Kamil Trzcinski committed
298
299
300
301
302
303
304
305
306
      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
307
        pipeline.tag = true
Kamil Trzcinski's avatar
Kamil Trzcinski committed
308
309
310
311
312
313
314
      end

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

316
317
  context 'with non-empty project' do
    let(:project) { create(:project) }
318
319
320
321
322
323
324

    let(:pipeline) do
      create(:ci_pipeline,
             project: project,
             ref: project.default_branch,
             sha: project.commit.sha)
    end
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345

    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

346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
  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') }

        it 'returns latest one' do
          is_expected.to contain_exactly(manual2)
        end
      end
    end
  end
369

Connor Shea's avatar
Connor Shea committed
370
371
  describe '#has_warnings?' do
    subject { pipeline.has_warnings? }
372
373
374

    context 'build which is allowed to fail fails' do
      before do
Connor Shea's avatar
Connor Shea committed
375
376
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop'
377
      end
378

379
380
381
382
383
384
385
      it 'returns true' do
        is_expected.to be_truthy
      end
    end

    context 'build which is allowed to fail succeeds' do
      before do
Connor Shea's avatar
Connor Shea committed
386
387
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :success, pipeline: pipeline, name: 'rubocop'
388
      end
389

390
391
392
393
      it 'returns false' do
        is_expected.to be_falsey
      end
    end
Connor Shea's avatar
Connor Shea committed
394
395
396
397
398
399
400
401
402
403
404
405

    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
406
  end
407

408
  shared_context 'with some outdated pipelines' do
409
410
411
412
413
414
415
416
417
418
419
420
421
    before do
      create_pipeline(:canceled, 'ref', 'A')
      create_pipeline(:success, 'ref', 'A')
      create_pipeline(:failed, 'ref', 'B')
      create_pipeline(:skipped, 'feature', 'C')
    end

    def create_pipeline(status, ref, sha)
      create(:ci_empty_pipeline, status: status, ref: ref, sha: sha)
    end
  end

  describe '.latest' do
422
    include_context 'with some outdated pipelines'
423
424

    context 'when no ref is specified' do
425
      let(:pipelines) { described_class.latest.all }
426

427
428
429
      it 'returns the latest pipelines for the same ref and different sha' do
        expect(pipelines.map(&:sha)).to eq(%w[C B A])
        expect(pipelines.map(&:status)).to eq(%w[skipped failed success])
430
431
432
433
      end
    end

    context 'when ref is specified' do
434
      let(:pipelines) { described_class.latest('ref').all }
435

436
437
438
      it 'returns the latest pipelines for ref and different sha' do
        expect(pipelines.map(&:sha)).to eq(%w[B A])
        expect(pipelines.map(&:status)).to eq(%w[failed success])
439
440
441
442
443
      end
    end
  end

  describe '.latest_status' do
444
    include_context 'with some outdated pipelines'
445
446

    context 'when no ref is specified' do
447
      let(:latest_status) { described_class.latest_status }
448
449

      it 'returns the latest status for the same ref and different sha' do
450
        expect(latest_status).to eq(described_class.latest.status)
451
        expect(latest_status).to eq('failed')
452
453
454
455
      end
    end

    context 'when ref is specified' do
456
      let(:latest_status) { described_class.latest_status('ref') }
457
458

      it 'returns the latest status for ref and different sha' do
459
        expect(latest_status).to eq(described_class.latest_status('ref'))
460
        expect(latest_status).to eq('failed')
461
462
463
464
      end
    end
  end

465
466
467
468
469
470
  describe '#status' do
    let!(:build) { create(:ci_build, :created, pipeline: pipeline, name: 'test') }

    subject { pipeline.reload.status }

    context 'on queuing' do
471
472
473
      before do
        build.enqueue
      end
474
475
476
477
478
479

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

    context 'on run' do
      before do
480
        build.enqueue
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
        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

      it { is_expected.to eq('canceled') }
    end
510
511
512
513
514
515
516
517
518
519
520
521
522

    context 'on failure and build retry' do
      before do
        build.drop
        Ci::Build.retry(build)
      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
523
  end
524

525
  describe '#detailed_status' do
526
527
528
529
    let(:user) { create(:user) }

    subject { pipeline.detailed_status(user) }

530
531
532
533
    context 'when pipeline is created' do
      let(:pipeline) { create(:ci_pipeline, status: :created) }

      it 'returns detailed status for created pipeline' do
534
        expect(subject.text).to eq 'created'
535
536
537
538
539
540
541
      end
    end

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

      it 'returns detailed status for pending pipeline' do
542
        expect(subject.text).to eq 'pending'
543
544
545
546
547
548
549
      end
    end

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

      it 'returns detailed status for running pipeline' do
550
        expect(subject.text).to eq 'running'
551
552
553
554
555
556
557
      end
    end

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

      it 'returns detailed status for successful pipeline' do
558
        expect(subject.text).to eq 'passed'
559
560
561
562
563
564
565
      end
    end

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

      it 'returns detailed status for failed pipeline' do
566
        expect(subject.text).to eq 'failed'
567
568
569
570
571
572
573
      end
    end

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

      it 'returns detailed status for canceled pipeline' do
574
        expect(subject.text).to eq 'canceled'
575
576
577
578
579
580
581
      end
    end

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

      it 'returns detailed status for skipped pipeline' do
582
        expect(subject.text).to eq 'skipped'
583
584
585
586
587
588
589
590
591
592
593
      end
    end

    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
594
        expect(subject.label).to eq 'passed with warnings'
595
596
597
598
      end
    end
  end

599
  describe '#cancelable?' do
600
601
    %i[created running pending].each do |status0|
      context "when there is a build #{status0}" do
602
        before do
603
          create(:ci_build, status0, pipeline: pipeline)
604
605
        end

606
607
608
        it 'is cancelable' do
          expect(pipeline.cancelable?).to be_truthy
        end
609
610
      end

611
      context "when there is an external job #{status0}" do
612
        before do
613
          create(:generic_commit_status, status0, pipeline: pipeline)
614
615
        end

616
617
618
        it 'is cancelable' do
          expect(pipeline.cancelable?).to be_truthy
        end
619
      end
620

621
      %i[success failed canceled].each do |status1|
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
        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
652
          end
653
654
        end
      end
655
656
657
658
659
660
661
662
    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

663
664
665
        it 'is not cancelable' do
          expect(pipeline.cancelable?).to be_falsey
        end
666
667
668
669
670
671
672
      end

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

673
674
675
        it 'is not cancelable' do
          expect(pipeline.cancelable?).to be_falsey
        end
676
677
678
679
      end
    end
  end

680
  describe '#cancel_running' do
681
682
    let(:latest_status) { pipeline.statuses.pluck(:status) }

683
684
    context 'when there is a running external job and created build' do
      before do
685
        create(:ci_build, :running, pipeline: pipeline)
686
687
688
689
690
691
        create(:generic_commit_status, :running, pipeline: pipeline)

        pipeline.cancel_running
      end

      it 'cancels both jobs' do
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
        expect(latest_status).to contain_exactly('canceled', 'canceled')
      end
    end

    context 'when builds are in different stages' do
      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
  end

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

    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)

718
        pipeline.retry_failed(create(:user))
719
720
721
722
723
724
725
726
727
728
729
730
      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)

731
        pipeline.retry_failed(create(:user))
732
733
734
735
736
737
738
739
740
741
742
743
      end

      it 'retries both builds' do
        expect(latest_status).to contain_exactly('pending', 'pending')
      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)

744
        pipeline.retry_failed(create(:user))
745
746
747
748
      end

      it 'retries both builds' do
        expect(latest_status).to contain_exactly('pending', 'pending')
749
750
751
752
      end
    end
  end

753
  describe '#execute_hooks' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
754
755
    let!(:build_a) { create_build('a', 0) }
    let!(:build_b) { create_build('b', 1) }
756

757
758
759
760
761
762
763
764
765
766
767
    let!(:hook) do
      create(:project_hook, project: project, pipeline_events: enabled)
    end

    before do
      ProjectWebHookWorker.drain
    end

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

768
769
770
771
      before do
        WebMock.stub_request(:post, hook.url)
      end

772
      context 'with multiple builds' do
773
774
        context 'when build is queued' do
          before do
775
776
            build_a.enqueue
            build_b.enqueue
777
          end
778

Lin Jen-Shin's avatar
Lin Jen-Shin committed
779
          it 'receives a pending event once' do
780
            expect(WebMock).to have_requested_pipeline_hook('pending').once
781
782
          end
        end
783

784
785
        context 'when build is run' do
          before do
786
            build_a.enqueue
787
            build_a.run
788
            build_b.enqueue
789
790
            build_b.run
          end
791

Lin Jen-Shin's avatar
Lin Jen-Shin committed
792
          it 'receives a running event once' do
793
            expect(WebMock).to have_requested_pipeline_hook('running').once
794
          end
795
796
        end

797
798
799
        context 'when all builds succeed' do
          before do
            build_a.success
Kamil Trzcinski's avatar
Kamil Trzcinski committed
800
801
802

            # We have to reload build_b as this is in next stage and it gets triggered by PipelineProcessWorker
            build_b.reload.success
803
804
          end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
805
          it 'receives a success event once' do
806
            expect(WebMock).to have_requested_pipeline_hook('success').once
807
          end
808
809
        end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
810
811
812
813
814
        context 'when stage one failed' do
          before do
            build_a.drop
          end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
815
          it 'receives a failed event once' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
816
817
818
819
            expect(WebMock).to have_requested_pipeline_hook('failed').once
          end
        end

820
        def have_requested_pipeline_hook(status)
821
          have_requested(:post, hook.url).with do |req|
822
823
824
825
            json_body = JSON.parse(req.body)
            json_body['object_attributes']['status'] == status &&
              json_body['builds'].length == 2
          end
826
        end
827
      end
828
829
830
831
832
    end

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

833
      before do
834
835
        build_a.enqueue
        build_b.enqueue
836
837
      end

838
839
840
841
      it 'did not execute pipeline_hook after touched' do
        expect(WebMock).not_to have_requested(:post, hook.url)
      end
    end
842

Lin Jen-Shin's avatar
Lin Jen-Shin committed
843
844
845
846
847
848
    def create_build(name, stage_idx)
      create(:ci_build,
             :created,
             pipeline: pipeline,
             name: name,
             stage_idx: stage_idx)
849
    end
850
  end
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874

  describe "#merge_requests" do
    let(:project) { FactoryGirl.create :project }
    let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }

    it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do
      merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)

      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
875
876

  describe 'notifications when pipeline success or failed' do
877
878
879
880
881
882
883
884
885
    let(:project) { create(:project) }

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

886
    before do
887
      reset_delivered_emails!
888

889
890
      project.team << [pipeline.user, Gitlab::Access::DEVELOPER]

891
892
893
894
      perform_enqueued_jobs do
        pipeline.enqueue
        pipeline.run
      end
895
896
897
898
    end

    shared_examples 'sending a notification' do
      it 'sends an email' do
899
        should_only_email(pipeline.user, kind: :bcc)
900
901
902
903
904
      end
    end

    shared_examples 'not sending any notification' do
      it 'does not send any email' do
905
        should_not_email_anyone
906
907
908
909
910
911
      end
    end

    context 'with success pipeline' do
      before do
        perform_enqueued_jobs do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
912
          pipeline.succeed
913
914
        end
      end
Lin Jen-Shin's avatar
Lin Jen-Shin committed
915
916

      it_behaves_like 'sending a notification'
917
918
919
920
921
    end

    context 'with failed pipeline' do
      before do
        perform_enqueued_jobs do
922
923
          create(:ci_build, :failed, pipeline: pipeline)
          create(:generic_commit_status, :failed, pipeline: pipeline)
924

925
          pipeline.drop
926
927
        end
      end
Lin Jen-Shin's avatar
Lin Jen-Shin committed
928
929

      it_behaves_like 'sending a notification'
930
931
932
933
934
935
936
937
    end

    context 'with skipped pipeline' do
      before do
        perform_enqueued_jobs do
          pipeline.skip
        end
      end
Lin Jen-Shin's avatar
Lin Jen-Shin committed
938
939

      it_behaves_like 'not sending any notification'
940
941
942
943
944
945
946
947
    end

    context 'with cancelled pipeline' do
      before do
        perform_enqueued_jobs do
          pipeline.cancel
        end
      end
Lin Jen-Shin's avatar
Lin Jen-Shin committed
948
949

      it_behaves_like 'not sending any notification'
950
951
    end
  end
Douwe Maan's avatar
Douwe Maan committed
952
end