1# frozen_string_literal: true
2
3require 'spec_helper'
4
5RSpec.describe Ci::Build do
6  let_it_be(:user) { create(:user) }
7  let_it_be(:group, reload: true) { create(:group) }
8  let_it_be(:project, reload: true) { create(:project, :repository, group: group) }
9
10  let_it_be(:pipeline, reload: true) do
11    create(:ci_pipeline, project: project,
12                         sha: project.commit.id,
13                         ref: project.default_branch,
14                         status: 'success')
15  end
16
17  let_it_be(:build, refind: true) { create(:ci_build, pipeline: pipeline) }
18
19  it { is_expected.to belong_to(:runner) }
20  it { is_expected.to belong_to(:trigger_request) }
21  it { is_expected.to belong_to(:erased_by) }
22
23  it { is_expected.to have_many(:needs) }
24  it { is_expected.to have_many(:sourced_pipelines) }
25  it { is_expected.to have_many(:job_variables) }
26  it { is_expected.to have_many(:report_results) }
27  it { is_expected.to have_many(:pages_deployments) }
28
29  it { is_expected.to have_one(:deployment) }
30  it { is_expected.to have_one(:runner_session) }
31  it { is_expected.to have_one(:trace_metadata) }
32  it { is_expected.to have_many(:terraform_state_versions).inverse_of(:build) }
33
34  it { is_expected.to validate_presence_of(:ref) }
35
36  it { is_expected.to respond_to(:has_trace?) }
37  it { is_expected.to respond_to(:trace) }
38  it { is_expected.to respond_to(:set_cancel_gracefully) }
39  it { is_expected.to respond_to(:cancel_gracefully?) }
40
41  it { is_expected.to delegate_method(:merge_request?).to(:pipeline) }
42  it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
43  it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) }
44
45  shared_examples 'calling proper BuildFinishedWorker' do
46    context 'when ci_build_finished_worker_namespace_changed feature flag enabled' do
47      before do
48        stub_feature_flags(ci_build_finished_worker_namespace_changed: build.project)
49      end
50
51      it 'calls Ci::BuildFinishedWorker' do
52        expect(Ci::BuildFinishedWorker).to receive(:perform_async)
53        expect(::BuildFinishedWorker).not_to receive(:perform_async)
54
55        subject
56      end
57    end
58
59    context 'when ci_build_finished_worker_namespace_changed feature flag disabled' do
60      before do
61        stub_feature_flags(ci_build_finished_worker_namespace_changed: false)
62      end
63
64      it 'calls ::BuildFinishedWorker' do
65        expect(::BuildFinishedWorker).to receive(:perform_async)
66        expect(Ci::BuildFinishedWorker).not_to receive(:perform_async)
67
68        subject
69      end
70    end
71  end
72
73  describe 'associations' do
74    it 'has a bidirectional relationship with projects' do
75      expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds)
76      expect(Project.reflect_on_association(:builds).has_inverse?).to eq(:project)
77    end
78  end
79
80  describe 'callbacks' do
81    context 'when running after_create callback' do
82      it 'triggers asynchronous build hooks worker' do
83        expect(BuildHooksWorker).to receive(:perform_async)
84
85        create(:ci_build)
86      end
87    end
88  end
89
90  describe 'status' do
91    context 'when transitioning to any state from running' do
92      it 'removes runner_session' do
93        %w(success drop cancel).each do |event|
94          build = FactoryBot.create(:ci_build, :running, :with_runner_session, pipeline: pipeline)
95
96          build.fire_events!(event)
97
98          expect(build.reload.runner_session).to be_nil
99        end
100      end
101    end
102  end
103
104  describe '.manual_actions' do
105    let!(:manual_but_created) { create(:ci_build, :manual, status: :created, pipeline: pipeline) }
106    let!(:manual_but_succeeded) { create(:ci_build, :manual, status: :success, pipeline: pipeline) }
107    let!(:manual_action) { create(:ci_build, :manual, pipeline: pipeline) }
108
109    subject { described_class.manual_actions }
110
111    it { is_expected.to include(manual_action) }
112    it { is_expected.to include(manual_but_succeeded) }
113    it { is_expected.not_to include(manual_but_created) }
114  end
115
116  describe '.ref_protected' do
117    subject { described_class.ref_protected }
118
119    context 'when protected is true' do
120      let!(:job) { create(:ci_build, :protected) }
121
122      it { is_expected.to include(job) }
123    end
124
125    context 'when protected is false' do
126      let!(:job) { create(:ci_build) }
127
128      it { is_expected.not_to include(job) }
129    end
130
131    context 'when protected is nil' do
132      let!(:job) { create(:ci_build) }
133
134      before do
135        job.update_attribute(:protected, nil)
136      end
137
138      it { is_expected.not_to include(job) }
139    end
140  end
141
142  describe '.with_downloadable_artifacts' do
143    subject { described_class.with_downloadable_artifacts }
144
145    context 'when job does not have a downloadable artifact' do
146      let!(:job) { create(:ci_build) }
147
148      it 'does not return the job' do
149        is_expected.not_to include(job)
150      end
151    end
152
153    ::Ci::JobArtifact::DOWNLOADABLE_TYPES.each do |type|
154      context "when job has a #{type} artifact" do
155        it 'returns the job' do
156          job = create(:ci_build)
157          create(
158            :ci_job_artifact,
159            file_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[type.to_sym],
160            file_type: type,
161            job: job
162          )
163
164          is_expected.to include(job)
165        end
166      end
167    end
168
169    context 'when job has a non-downloadable artifact' do
170      let!(:job) { create(:ci_build, :trace_artifact) }
171
172      it 'does not return the job' do
173        is_expected.not_to include(job)
174      end
175    end
176  end
177
178  describe '.with_live_trace' do
179    subject { described_class.with_live_trace }
180
181    context 'when build has live trace' do
182      let!(:build) { create(:ci_build, :success, :trace_live) }
183
184      it 'selects the build' do
185        is_expected.to eq([build])
186      end
187    end
188
189    context 'when build does not have live trace' do
190      let!(:build) { create(:ci_build, :success, :trace_artifact) }
191
192      it 'does not select the build' do
193        is_expected.to be_empty
194      end
195    end
196  end
197
198  describe '.with_stale_live_trace' do
199    subject { described_class.with_stale_live_trace }
200
201    context 'when build has a stale live trace' do
202      let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.day.ago) }
203
204      it 'selects the build' do
205        is_expected.to eq([build])
206      end
207    end
208
209    context 'when build does not have a stale live trace' do
210      let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.hour.ago) }
211
212      it 'does not select the build' do
213        is_expected.to be_empty
214      end
215    end
216  end
217
218  describe '.license_management_jobs' do
219    subject { described_class.license_management_jobs }
220
221    let!(:management_build) { create(:ci_build, :success, name: :license_management) }
222    let!(:scanning_build) { create(:ci_build, :success, name: :license_scanning) }
223    let!(:another_build) { create(:ci_build, :success, name: :another_type) }
224
225    it 'returns license_scanning jobs' do
226      is_expected.to include(scanning_build)
227    end
228
229    it 'returns license_management jobs' do
230      is_expected.to include(management_build)
231    end
232
233    it 'doesnt return filtered out jobs' do
234      is_expected.not_to include(another_build)
235    end
236  end
237
238  describe '.finished_before' do
239    subject { described_class.finished_before(date) }
240
241    let(:date) { 1.hour.ago }
242
243    context 'when build has finished one day ago' do
244      let!(:build) { create(:ci_build, :success, finished_at: 1.day.ago) }
245
246      it 'selects the build' do
247        is_expected.to eq([build])
248      end
249    end
250
251    context 'when build has finished 30 minutes ago' do
252      let!(:build) { create(:ci_build, :success, finished_at: 30.minutes.ago) }
253
254      it 'returns an empty array' do
255        is_expected.to be_empty
256      end
257    end
258
259    context 'when build is still running' do
260      let!(:build) { create(:ci_build, :running) }
261
262      it 'returns an empty array' do
263        is_expected.to be_empty
264      end
265    end
266  end
267
268  describe '.with_exposed_artifacts' do
269    subject { described_class.with_exposed_artifacts }
270
271    let!(:job1) { create(:ci_build) }
272    let!(:job2) { create(:ci_build, options: options) }
273    let!(:job3) { create(:ci_build) }
274
275    context 'when some jobs have exposed artifacs and some not' do
276      let(:options) { { artifacts: { expose_as: 'test', paths: ['test'] } } }
277
278      before do
279        job1.ensure_metadata.update!(has_exposed_artifacts: nil)
280        job3.ensure_metadata.update!(has_exposed_artifacts: false)
281      end
282
283      it 'selects only the jobs with exposed artifacts' do
284        is_expected.to eq([job2])
285      end
286    end
287
288    context 'when job does not expose artifacts' do
289      let(:options) { nil }
290
291      it 'returns an empty array' do
292        is_expected.to be_empty
293      end
294    end
295  end
296
297  describe '.with_reports' do
298    subject { described_class.with_reports(Ci::JobArtifact.test_reports) }
299
300    context 'when build has a test report' do
301      let!(:build) { create(:ci_build, :success, :test_reports) }
302
303      it 'selects the build' do
304        is_expected.to eq([build])
305      end
306    end
307
308    context 'when build does not have test reports' do
309      let!(:build) { create(:ci_build, :success, :trace_artifact) }
310
311      it 'does not select the build' do
312        is_expected.to be_empty
313      end
314    end
315
316    context 'when there are multiple builds with test reports' do
317      let!(:builds) { create_list(:ci_build, 5, :success, :test_reports) }
318
319      it 'does not execute a query for selecting job artifact one by one' do
320        recorded = ActiveRecord::QueryRecorder.new do
321          subject.each do |build|
322            build.job_artifacts.map { |a| a.file.exists? }
323          end
324        end
325
326        expect(recorded.count).to eq(2)
327      end
328    end
329  end
330
331  describe '.with_needs' do
332    let!(:build) { create(:ci_build) }
333    let!(:build_b) { create(:ci_build) }
334    let!(:build_need_a) { create(:ci_build_need, build: build) }
335    let!(:build_need_b) { create(:ci_build_need, build: build_b) }
336
337    context 'when passing build name' do
338      subject { described_class.with_needs(build_need_a.name) }
339
340      it { is_expected.to contain_exactly(build) }
341    end
342
343    context 'when not passing any build name' do
344      subject { described_class.with_needs }
345
346      it { is_expected.to contain_exactly(build, build_b) }
347    end
348
349    context 'when not matching build name' do
350      subject { described_class.with_needs('undefined') }
351
352      it { is_expected.to be_empty }
353    end
354  end
355
356  describe '.without_needs' do
357    subject { described_class.without_needs }
358
359    context 'when no build_need is created' do
360      it { is_expected.to contain_exactly(build) }
361    end
362
363    context 'when a build_need is created' do
364      let!(:need_a) { create(:ci_build_need, build: build) }
365
366      it { is_expected.to be_empty }
367    end
368  end
369
370  describe '#stick_build_if_status_changed' do
371    it 'sticks the build if the status changed' do
372      job = create(:ci_build, :pending)
373
374      expect(described_class.sticking).to receive(:stick)
375        .with(:build, job.id)
376
377      job.update!(status: :running)
378    end
379  end
380
381  describe '#enqueue' do
382    let(:build) { create(:ci_build, :created) }
383
384    before do
385      allow(build).to receive(:any_unmet_prerequisites?).and_return(has_prerequisites)
386      allow(Ci::PrepareBuildService).to receive(:perform_async)
387    end
388
389    context 'build has unmet prerequisites' do
390      let(:has_prerequisites) { true }
391
392      it 'transitions to preparing' do
393        build.enqueue
394
395        expect(build).to be_preparing
396      end
397
398      it 'does not push build to the queue' do
399        build.enqueue
400
401        expect(build.queuing_entry).not_to be_present
402      end
403    end
404
405    context 'build has no prerequisites' do
406      let(:has_prerequisites) { false }
407
408      it 'transitions to pending' do
409        build.enqueue
410
411        expect(build).to be_pending
412      end
413
414      it 'pushes build to a queue' do
415        build.enqueue
416
417        expect(build.queuing_entry).to be_present
418      end
419
420      context 'when build status transition fails' do
421        before do
422          ::Ci::Build.find(build.id).update_column(:lock_version, 100)
423        end
424
425        it 'does not push build to a queue' do
426          expect { build.enqueue! }
427            .to raise_error(ActiveRecord::StaleObjectError)
428
429          expect(build.queuing_entry).not_to be_present
430        end
431      end
432
433      context 'when there is a queuing entry already present' do
434        before do
435          create(:ci_pending_build, build: build, project: build.project)
436        end
437
438        it 'does not raise an error' do
439          expect { build.enqueue! }.not_to raise_error
440          expect(build.reload.queuing_entry).to be_present
441        end
442      end
443
444      context 'when both failure scenario happen at the same time' do
445        before do
446          ::Ci::Build.find(build.id).update_column(:lock_version, 100)
447          create(:ci_pending_build, build: build, project: build.project)
448        end
449
450        it 'raises stale object error exception' do
451          expect { build.enqueue! }
452            .to raise_error(ActiveRecord::StaleObjectError)
453        end
454      end
455    end
456  end
457
458  describe '#enqueue_preparing' do
459    let(:build) { create(:ci_build, :preparing) }
460
461    before do
462      allow(build).to receive(:any_unmet_prerequisites?).and_return(has_unmet_prerequisites)
463    end
464
465    context 'build completed prerequisites' do
466      let(:has_unmet_prerequisites) { false }
467
468      it 'transitions to pending' do
469        build.enqueue_preparing
470
471        expect(build).to be_pending
472        expect(build.queuing_entry).to be_present
473      end
474    end
475
476    context 'build did not complete prerequisites' do
477      let(:has_unmet_prerequisites) { true }
478
479      it 'remains in preparing' do
480        build.enqueue_preparing
481
482        expect(build).to be_preparing
483        expect(build.queuing_entry).not_to be_present
484      end
485    end
486  end
487
488  describe '#actionize' do
489    context 'when build is a created' do
490      before do
491        build.update_column(:status, :created)
492      end
493
494      it 'makes build a manual action' do
495        expect(build.actionize).to be true
496        expect(build.reload).to be_manual
497      end
498    end
499
500    context 'when build is not created' do
501      before do
502        build.update_column(:status, :pending)
503      end
504
505      it 'does not change build status' do
506        expect(build.actionize).to be false
507        expect(build.reload).to be_pending
508      end
509    end
510  end
511
512  describe '#run' do
513    context 'when build has been just created' do
514      let(:build) { create(:ci_build, :created) }
515
516      it 'creates queuing entry and then removes it' do
517        build.enqueue!
518        expect(build.queuing_entry).to be_present
519
520        build.run!
521        expect(build.reload.queuing_entry).not_to be_present
522      end
523    end
524
525    context 'when build status transition fails' do
526      let(:build) { create(:ci_build, :pending) }
527
528      before do
529        create(:ci_pending_build, build: build, project: build.project)
530        ::Ci::Build.find(build.id).update_column(:lock_version, 100)
531      end
532
533      it 'does not remove build from a queue' do
534        expect { build.run! }
535          .to raise_error(ActiveRecord::StaleObjectError)
536
537        expect(build.queuing_entry).to be_present
538      end
539    end
540
541    context 'when build has been picked by a shared runner' do
542      let(:build) { create(:ci_build, :pending) }
543
544      it 'creates runtime metadata entry' do
545        build.runner = create(:ci_runner, :instance_type)
546
547        build.run!
548
549        expect(build.reload.runtime_metadata).to be_present
550      end
551    end
552  end
553
554  describe '#drop' do
555    context 'when has a runtime tracking entry' do
556      let(:build) { create(:ci_build, :pending) }
557
558      it 'removes runtime tracking entry' do
559        build.runner = create(:ci_runner, :instance_type)
560
561        build.run!
562        expect(build.reload.runtime_metadata).to be_present
563
564        build.drop!
565        expect(build.reload.runtime_metadata).not_to be_present
566      end
567    end
568  end
569
570  describe '#schedulable?' do
571    subject { build.schedulable? }
572
573    context 'when build is schedulable' do
574      let(:build) { create(:ci_build, :created, :schedulable, project: project) }
575
576      it { expect(subject).to be_truthy }
577    end
578
579    context 'when build is not schedulable' do
580      let(:build) { create(:ci_build, :created, project: project) }
581
582      it { expect(subject).to be_falsy }
583    end
584  end
585
586  describe '#schedule' do
587    subject { build.schedule }
588
589    before do
590      project.add_developer(user)
591    end
592
593    let(:build) { create(:ci_build, :created, :schedulable, user: user, project: project) }
594
595    it 'transits to scheduled' do
596      allow(Ci::BuildScheduleWorker).to receive(:perform_at)
597
598      subject
599
600      expect(build).to be_scheduled
601    end
602
603    it 'updates scheduled_at column' do
604      allow(Ci::BuildScheduleWorker).to receive(:perform_at)
605
606      subject
607
608      expect(build.scheduled_at).not_to be_nil
609    end
610
611    it 'schedules BuildScheduleWorker at the right time' do
612      freeze_time do
613        expect(Ci::BuildScheduleWorker)
614          .to receive(:perform_at).with(be_like_time(1.minute.since), build.id)
615
616        subject
617      end
618    end
619  end
620
621  describe '#unschedule' do
622    subject { build.unschedule }
623
624    context 'when build is scheduled' do
625      let(:build) { create(:ci_build, :scheduled, pipeline: pipeline) }
626
627      it 'cleans scheduled_at column' do
628        subject
629
630        expect(build.scheduled_at).to be_nil
631      end
632
633      it 'transits to manual' do
634        subject
635
636        expect(build).to be_manual
637      end
638    end
639
640    context 'when build is not scheduled' do
641      let(:build) { create(:ci_build, :created, pipeline: pipeline) }
642
643      it 'does not transit status' do
644        subject
645
646        expect(build).to be_created
647      end
648    end
649  end
650
651  describe '#options_scheduled_at' do
652    subject { build.options_scheduled_at }
653
654    let(:build) { build_stubbed(:ci_build, options: option) }
655
656    context 'when start_in is 1 day' do
657      let(:option) { { start_in: '1 day' } }
658
659      it 'returns date after 1 day' do
660        freeze_time do
661          is_expected.to eq(1.day.since)
662        end
663      end
664    end
665
666    context 'when start_in is 1 week' do
667      let(:option) { { start_in: '1 week' } }
668
669      it 'returns date after 1 week' do
670        freeze_time do
671          is_expected.to eq(1.week.since)
672        end
673      end
674    end
675  end
676
677  describe '#enqueue_scheduled' do
678    subject { build.enqueue_scheduled }
679
680    context 'when build is scheduled and the right time has not come yet' do
681      let(:build) { create(:ci_build, :scheduled, pipeline: pipeline) }
682
683      it 'does not transits the status' do
684        subject
685
686        expect(build).to be_scheduled
687      end
688    end
689
690    context 'when build is scheduled and the right time has already come' do
691      let(:build) { create(:ci_build, :expired_scheduled, pipeline: pipeline) }
692
693      it 'cleans scheduled_at column' do
694        subject
695
696        expect(build.scheduled_at).to be_nil
697      end
698
699      it 'transits to pending' do
700        subject
701
702        expect(build).to be_pending
703      end
704
705      context 'build has unmet prerequisites' do
706        before do
707          allow(build).to receive(:prerequisites).and_return([double])
708        end
709
710        it 'transits to preparing' do
711          subject
712
713          expect(build).to be_preparing
714        end
715      end
716    end
717  end
718
719  describe '#any_runners_online?' do
720    subject { build.any_runners_online? }
721
722    context 'when no runners' do
723      it { is_expected.to be_falsey }
724    end
725
726    context 'when there are runners' do
727      let(:runner) { create(:ci_runner, :project, projects: [build.project]) }
728
729      before do
730        runner.update!(contacted_at: 1.second.ago)
731      end
732
733      it { is_expected.to be_truthy }
734
735      it 'that is inactive' do
736        runner.update!(active: false)
737        is_expected.to be_falsey
738      end
739
740      it 'that is not online' do
741        runner.update!(contacted_at: nil)
742        is_expected.to be_falsey
743      end
744
745      it 'that cannot handle build' do
746        expect_any_instance_of(Ci::Runner).to receive(:matches_build?).with(build).and_return(false)
747        is_expected.to be_falsey
748      end
749    end
750
751    it 'caches the result in Redis' do
752      expect(Rails.cache).to receive(:fetch).with(['has-online-runners', build.id], expires_in: 1.minute)
753
754      build.any_runners_online?
755    end
756  end
757
758  describe '#any_runners_available?' do
759    subject { build.any_runners_available? }
760
761    context 'when no runners' do
762      it { is_expected.to be_falsey }
763    end
764
765    context 'when there are runners' do
766      let!(:runner) { create(:ci_runner, :project, projects: [build.project]) }
767
768      it { is_expected.to be_truthy }
769    end
770
771    it 'caches the result in Redis' do
772      expect(Rails.cache).to receive(:fetch).with(['has-available-runners', build.project.id], expires_in: 1.minute)
773
774      build.any_runners_available?
775    end
776  end
777
778  describe '#artifacts?' do
779    subject { build.artifacts? }
780
781    context 'when new artifacts are used' do
782      context 'artifacts archive does not exist' do
783        let(:build) { create(:ci_build) }
784
785        it { is_expected.to be_falsy }
786      end
787
788      context 'artifacts archive exists' do
789        let(:build) { create(:ci_build, :artifacts) }
790
791        it { is_expected.to be_truthy }
792
793        context 'is expired' do
794          let(:build) { create(:ci_build, :artifacts, :expired) }
795
796          it { is_expected.to be_falsy }
797        end
798      end
799    end
800  end
801
802  describe '#locked_artifacts?' do
803    subject(:locked_artifacts) { build.locked_artifacts? }
804
805    context 'when pipeline is artifacts_locked' do
806      before do
807        build.pipeline.artifacts_locked!
808      end
809
810      context 'artifacts archive does not exist' do
811        let(:build) { create(:ci_build) }
812
813        it { is_expected.to be_falsy }
814      end
815
816      context 'artifacts archive exists' do
817        let(:build) { create(:ci_build, :artifacts) }
818
819        it { is_expected.to be_truthy }
820      end
821    end
822
823    context 'when pipeline is unlocked' do
824      before do
825        build.pipeline.unlocked!
826      end
827
828      context 'artifacts archive does not exist' do
829        let(:build) { create(:ci_build) }
830
831        it { is_expected.to be_falsy }
832      end
833
834      context 'artifacts archive exists' do
835        let(:build) { create(:ci_build, :artifacts) }
836
837        it { is_expected.to be_falsy }
838      end
839    end
840  end
841
842  describe '#available_artifacts?' do
843    let(:build) { create(:ci_build) }
844
845    subject { build.available_artifacts? }
846
847    context 'when artifacts are not expired' do
848      before do
849        build.artifacts_expire_at = Date.tomorrow
850      end
851
852      context 'when artifacts exist' do
853        before do
854          create(:ci_job_artifact, :archive, job: build)
855        end
856
857        it { is_expected.to be_truthy }
858      end
859
860      context 'when artifacts do not exist' do
861        it { is_expected.to be_falsey }
862      end
863    end
864
865    context 'when artifacts are expired' do
866      before do
867        build.artifacts_expire_at = Date.yesterday
868      end
869
870      context 'when artifacts are not locked' do
871        before do
872          build.pipeline.locked = :unlocked
873        end
874
875        it { is_expected.to be_falsey }
876      end
877
878      context 'when artifacts are locked' do
879        before do
880          build.pipeline.locked = :artifacts_locked
881        end
882
883        context 'when artifacts exist' do
884          before do
885            create(:ci_job_artifact, :archive, job: build)
886          end
887
888          it { is_expected.to be_truthy }
889        end
890
891        context 'when artifacts do not exist' do
892          it { is_expected.to be_falsey }
893        end
894      end
895    end
896  end
897
898  describe '#browsable_artifacts?' do
899    subject { build.browsable_artifacts? }
900
901    context 'artifacts metadata does exists' do
902      let(:build) { create(:ci_build, :artifacts) }
903
904      it { is_expected.to be_truthy }
905    end
906  end
907
908  describe '#artifacts_public?' do
909    subject { build.artifacts_public? }
910
911    context 'artifacts with defaults' do
912      let(:build) { create(:ci_build, :artifacts) }
913
914      it { is_expected.to be_truthy }
915    end
916
917    context 'non public artifacts' do
918      let(:build) { create(:ci_build, :artifacts, :non_public_artifacts) }
919
920      it { is_expected.to be_falsey }
921    end
922  end
923
924  describe '#artifacts_expired?' do
925    subject { build.artifacts_expired? }
926
927    context 'is expired' do
928      before do
929        build.update!(artifacts_expire_at: Time.current - 7.days)
930      end
931
932      it { is_expected.to be_truthy }
933    end
934
935    context 'is not expired' do
936      before do
937        build.update!(artifacts_expire_at: Time.current + 7.days)
938      end
939
940      it { is_expected.to be_falsey }
941    end
942  end
943
944  describe '#artifacts_metadata?' do
945    subject { build.artifacts_metadata? }
946
947    context 'artifacts metadata does not exist' do
948      it { is_expected.to be_falsy }
949    end
950
951    context 'artifacts archive is a zip file and metadata exists' do
952      let(:build) { create(:ci_build, :artifacts) }
953
954      it { is_expected.to be_truthy }
955    end
956  end
957
958  describe '#artifacts_expire_in' do
959    subject { build.artifacts_expire_in }
960
961    it { is_expected.to be_nil }
962
963    context 'when artifacts_expire_at is specified' do
964      let(:expire_at) { Time.current + 7.days }
965
966      before do
967        build.artifacts_expire_at = expire_at
968      end
969
970      it { is_expected.to be_within(5).of(expire_at - Time.current) }
971    end
972  end
973
974  describe '#artifacts_expire_in=' do
975    subject { build.artifacts_expire_in }
976
977    it 'when assigning valid duration' do
978      build.artifacts_expire_in = '7 days'
979
980      is_expected.to be_within(10).of(7.days.to_i)
981    end
982
983    it 'when assigning invalid duration' do
984      expect { build.artifacts_expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError)
985      is_expected.to be_nil
986    end
987
988    it 'when resetting value' do
989      build.artifacts_expire_in = nil
990
991      is_expected.to be_nil
992    end
993
994    it 'when setting to 0' do
995      build.artifacts_expire_in = '0'
996
997      is_expected.to be_nil
998    end
999  end
1000
1001  describe '#commit' do
1002    it 'returns commit pipeline has been created for' do
1003      expect(build.commit).to eq project.commit
1004    end
1005  end
1006
1007  describe '#cache' do
1008    let(:options) do
1009      { cache: [{ key: "key", paths: ["public"], policy: "pull-push" }] }
1010    end
1011
1012    subject { build.cache }
1013
1014    context 'when build has cache' do
1015      before do
1016        allow(build).to receive(:options).and_return(options)
1017      end
1018
1019      context 'when build has multiple caches' do
1020        let(:options) do
1021          { cache: [
1022            { key: "key", paths: ["public"], policy: "pull-push" },
1023            { key: "key2", paths: ["public"], policy: "pull-push" }
1024          ] }
1025        end
1026
1027        before do
1028          allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
1029        end
1030
1031        it { is_expected.to match([a_hash_including(key: "key-1"), a_hash_including(key: "key2-1")]) }
1032      end
1033
1034      context 'when project has jobs_cache_index' do
1035        before do
1036          allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
1037        end
1038
1039        it { is_expected.to be_an(Array).and all(include(key: "key-1")) }
1040      end
1041
1042      context 'when project does not have jobs_cache_index' do
1043        before do
1044          allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(nil)
1045        end
1046
1047        it { is_expected.to eq(options[:cache]) }
1048      end
1049    end
1050
1051    context 'when build does not have cache' do
1052      before do
1053        allow(build).to receive(:options).and_return({})
1054      end
1055
1056      it { is_expected.to be_empty }
1057    end
1058  end
1059
1060  describe '#triggered_by?' do
1061    subject { build.triggered_by?(user) }
1062
1063    context 'when user is owner' do
1064      let(:build) { create(:ci_build, pipeline: pipeline, user: user) }
1065
1066      it { is_expected.to be_truthy }
1067    end
1068
1069    context 'when user is not owner' do
1070      let(:another_user) { create(:user) }
1071      let(:build) { create(:ci_build, pipeline: pipeline, user: another_user) }
1072
1073      it { is_expected.to be_falsy }
1074    end
1075  end
1076
1077  describe '#detailed_status' do
1078    it 'returns a detailed status' do
1079      expect(build.detailed_status(user))
1080        .to be_a Gitlab::Ci::Status::Build::Cancelable
1081    end
1082  end
1083
1084  describe '#coverage_regex' do
1085    subject { build.coverage_regex }
1086
1087    context 'when project has build_coverage_regex set' do
1088      let(:project_regex) { '\(\d+\.\d+\) covered' }
1089
1090      before do
1091        project.update_column(:build_coverage_regex, project_regex)
1092      end
1093
1094      context 'and coverage_regex attribute is not set' do
1095        it { is_expected.to eq(project_regex) }
1096      end
1097
1098      context 'but coverage_regex attribute is also set' do
1099        let(:build_regex) { 'Code coverage: \d+\.\d+' }
1100
1101        before do
1102          build.coverage_regex = build_regex
1103        end
1104
1105        it { is_expected.to eq(build_regex) }
1106      end
1107    end
1108
1109    context 'when neither project nor build has coverage regex set' do
1110      it { is_expected.to be_nil }
1111    end
1112  end
1113
1114  describe '#update_coverage' do
1115    context "regarding coverage_regex's value," do
1116      before do
1117        build.coverage_regex = '\(\d+.\d+\%\) covered'
1118        build.trace.set('Coverage 1033 / 1051 LOC (98.29%) covered')
1119      end
1120
1121      it "saves the correct extracted coverage value" do
1122        expect(build.update_coverage).to be(true)
1123        expect(build.coverage).to eq(98.29)
1124      end
1125    end
1126  end
1127
1128  describe '#trace' do
1129    subject { build.trace }
1130
1131    it { is_expected.to be_a(Gitlab::Ci::Trace) }
1132  end
1133
1134  describe '#has_trace?' do
1135    subject { build.has_trace? }
1136
1137    it "expect to call exist? method" do
1138      expect_any_instance_of(Gitlab::Ci::Trace).to receive(:exist?)
1139        .and_return(true)
1140
1141      is_expected.to be(true)
1142    end
1143  end
1144
1145  describe '#has_live_trace?' do
1146    subject { build.has_live_trace? }
1147
1148    let(:build) { create(:ci_build, :trace_live) }
1149
1150    it { is_expected.to be_truthy }
1151
1152    context 'when build does not have live trace' do
1153      let(:build) { create(:ci_build) }
1154
1155      it { is_expected.to be_falsy }
1156    end
1157  end
1158
1159  describe '#has_archived_trace?' do
1160    subject { build.has_archived_trace? }
1161
1162    let(:build) { create(:ci_build, :trace_artifact) }
1163
1164    it { is_expected.to be_truthy }
1165
1166    context 'when build does not have archived trace' do
1167      let(:build) { create(:ci_build) }
1168
1169      it { is_expected.to be_falsy }
1170    end
1171  end
1172
1173  describe '#has_job_artifacts?' do
1174    subject { build.has_job_artifacts? }
1175
1176    context 'when build has a job artifact' do
1177      let(:build) { create(:ci_build, :artifacts) }
1178
1179      it { is_expected.to be_truthy }
1180    end
1181  end
1182
1183  describe '#has_test_reports?' do
1184    subject { build.has_test_reports? }
1185
1186    context 'when build has a test report' do
1187      let(:build) { create(:ci_build, :test_reports) }
1188
1189      it { is_expected.to be_truthy }
1190    end
1191
1192    context 'when build does not have a test report' do
1193      let(:build) { create(:ci_build) }
1194
1195      it { is_expected.to be_falsey }
1196    end
1197  end
1198
1199  describe '#has_old_trace?' do
1200    subject { build.has_old_trace? }
1201
1202    context 'when old trace exists' do
1203      before do
1204        build.update_column(:trace, 'old trace')
1205      end
1206
1207      it { is_expected.to be_truthy }
1208    end
1209
1210    context 'when old trace does not exist' do
1211      it { is_expected.to be_falsy }
1212    end
1213  end
1214
1215  describe '#trace=' do
1216    it "expect to fail trace=" do
1217      expect { build.trace = "new" }.to raise_error(NotImplementedError)
1218    end
1219  end
1220
1221  describe '#old_trace' do
1222    subject { build.old_trace }
1223
1224    before do
1225      build.update_column(:trace, 'old trace')
1226    end
1227
1228    it "expect to receive data from database" do
1229      is_expected.to eq('old trace')
1230    end
1231  end
1232
1233  describe '#erase_old_trace!' do
1234    subject { build.erase_old_trace! }
1235
1236    context 'when old trace exists' do
1237      before do
1238        build.update_column(:trace, 'old trace')
1239      end
1240
1241      it "erases old trace" do
1242        subject
1243
1244        expect(build.old_trace).to be_nil
1245      end
1246
1247      it "executes UPDATE query" do
1248        recorded = ActiveRecord::QueryRecorder.new { subject }
1249
1250        expect(recorded.log.count { |l| l.match?(/UPDATE.*ci_builds/) }).to eq(1)
1251      end
1252    end
1253
1254    context 'when old trace does not exist' do
1255      it 'does not execute UPDATE query' do
1256        recorded = ActiveRecord::QueryRecorder.new { subject }
1257
1258        expect(recorded.log.count { |l| l.match?(/UPDATE.*ci_builds/) }).to eq(0)
1259      end
1260    end
1261  end
1262
1263  describe '#hide_secrets' do
1264    let(:metrics) { spy('metrics') }
1265    let(:subject) { build.hide_secrets(data) }
1266
1267    context 'hide runners token' do
1268      let(:data) { "new #{project.runners_token} data"}
1269
1270      it { is_expected.to match(/^new x+ data$/) }
1271
1272      it 'increments trace mutation metric' do
1273        build.hide_secrets(data, metrics)
1274
1275        expect(metrics)
1276          .to have_received(:increment_trace_operation)
1277          .with(operation: :mutated)
1278      end
1279    end
1280
1281    context 'hide build token' do
1282      let(:data) { "new #{build.token} data"}
1283
1284      it { is_expected.to match(/^new x+ data$/) }
1285
1286      it 'increments trace mutation metric' do
1287        build.hide_secrets(data, metrics)
1288
1289        expect(metrics)
1290          .to have_received(:increment_trace_operation)
1291          .with(operation: :mutated)
1292      end
1293    end
1294
1295    context 'when build does not include secrets' do
1296      let(:data) { 'my build log' }
1297
1298      it 'does not mutate trace' do
1299        trace = build.hide_secrets(data)
1300
1301        expect(trace).to eq data
1302      end
1303
1304      it 'does not increment trace mutation metric' do
1305        build.hide_secrets(data, metrics)
1306
1307        expect(metrics)
1308          .not_to have_received(:increment_trace_operation)
1309          .with(operation: :mutated)
1310      end
1311    end
1312  end
1313
1314  describe 'state transition as a deployable' do
1315    subject { build.send(event) }
1316
1317    let!(:build) { create(:ci_build, :with_deployment, :start_review_app, project: project, pipeline: pipeline) }
1318    let(:deployment) { build.deployment }
1319    let(:environment) { deployment.environment }
1320
1321    before do
1322      allow(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
1323      allow(Deployments::HooksWorker).to receive(:perform_async)
1324    end
1325
1326    it 'has deployments record with created status' do
1327      expect(deployment).to be_created
1328      expect(environment.name).to eq('review/master')
1329    end
1330
1331    shared_examples_for 'avoid deadlock' do
1332      it 'executes UPDATE in the right order' do
1333        recorded = with_cross_database_modification_prevented do
1334          ActiveRecord::QueryRecorder.new { subject }
1335        end
1336
1337        index_for_build = recorded.log.index { |l| l.include?("UPDATE \"ci_builds\"") }
1338        index_for_deployment = recorded.log.index { |l| l.include?("UPDATE \"deployments\"") }
1339
1340        expect(index_for_build).to be < index_for_deployment
1341      end
1342    end
1343
1344    context 'when transits to running' do
1345      let(:event) { :run! }
1346
1347      it_behaves_like 'avoid deadlock'
1348
1349      it 'transits deployment status to running' do
1350        with_cross_database_modification_prevented do
1351          subject
1352        end
1353
1354        expect(deployment).to be_running
1355      end
1356
1357      context 'when deployment is already running state' do
1358        before do
1359          build.deployment.success!
1360        end
1361
1362        it 'does not change deployment status and tracks an error' do
1363          expect(Gitlab::ErrorTracking)
1364            .to receive(:track_exception).with(
1365              instance_of(Deployment::StatusSyncError), deployment_id: deployment.id, build_id: build.id)
1366
1367          with_cross_database_modification_prevented do
1368            expect { subject }.not_to change { deployment.reload.status }
1369          end
1370        end
1371      end
1372    end
1373
1374    context 'when transits to success' do
1375      let(:event) { :success! }
1376
1377      before do
1378        allow(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
1379        allow(Deployments::HooksWorker).to receive(:perform_async)
1380      end
1381
1382      it_behaves_like 'avoid deadlock'
1383      it_behaves_like 'calling proper BuildFinishedWorker'
1384
1385      it 'transits deployment status to success' do
1386        with_cross_database_modification_prevented do
1387          subject
1388        end
1389
1390        expect(deployment).to be_success
1391      end
1392    end
1393
1394    context 'when transits to failed' do
1395      let(:event) { :drop! }
1396
1397      it_behaves_like 'avoid deadlock'
1398      it_behaves_like 'calling proper BuildFinishedWorker'
1399
1400      it 'transits deployment status to failed' do
1401        with_cross_database_modification_prevented do
1402          subject
1403        end
1404
1405        expect(deployment).to be_failed
1406      end
1407    end
1408
1409    context 'when transits to skipped' do
1410      let(:event) { :skip! }
1411
1412      it_behaves_like 'avoid deadlock'
1413
1414      it 'transits deployment status to skipped' do
1415        with_cross_database_modification_prevented do
1416          subject
1417        end
1418
1419        expect(deployment).to be_skipped
1420      end
1421    end
1422
1423    context 'when transits to canceled' do
1424      let(:event) { :cancel! }
1425
1426      it_behaves_like 'avoid deadlock'
1427      it_behaves_like 'calling proper BuildFinishedWorker'
1428
1429      it 'transits deployment status to canceled' do
1430        with_cross_database_modification_prevented do
1431          subject
1432        end
1433
1434        expect(deployment).to be_canceled
1435      end
1436    end
1437  end
1438
1439  describe '#on_stop' do
1440    subject { build.on_stop }
1441
1442    context 'when a job has a specification that it can be stopped from the other job' do
1443      let(:build) { create(:ci_build, :start_review_app) }
1444
1445      it 'returns the other job name' do
1446        is_expected.to eq('stop_review_app')
1447      end
1448    end
1449
1450    context 'when a job does not have environment information' do
1451      let(:build) { create(:ci_build) }
1452
1453      it 'returns nil' do
1454        is_expected.to be_nil
1455      end
1456    end
1457  end
1458
1459  describe '#environment_deployment_tier' do
1460    subject { build.environment_deployment_tier }
1461
1462    let(:build) { described_class.new(options: options) }
1463    let(:options) { { environment: { deployment_tier: 'production' } } }
1464
1465    it { is_expected.to eq('production') }
1466
1467    context 'when options does not include deployment_tier' do
1468      let(:options) { { environment: { name: 'production' } } }
1469
1470      it { is_expected.to be_nil }
1471    end
1472  end
1473
1474  describe 'deployment' do
1475    describe '#outdated_deployment?' do
1476      subject { build.outdated_deployment? }
1477
1478      context 'when build succeeded' do
1479        let(:build) { create(:ci_build, :success) }
1480        let!(:deployment) { create(:deployment, :success, deployable: build) }
1481
1482        context 'current deployment is latest' do
1483          it { is_expected.to be_falsey }
1484        end
1485
1486        context 'current deployment is not latest on environment' do
1487          let!(:deployment2) { create(:deployment, :success, environment: deployment.environment) }
1488
1489          it { is_expected.to be_truthy }
1490        end
1491      end
1492
1493      context 'when build failed' do
1494        let(:build) { create(:ci_build, :failed) }
1495
1496        it { is_expected.to be_falsey }
1497      end
1498    end
1499  end
1500
1501  describe 'environment' do
1502    describe '#has_environment?' do
1503      subject { build.has_environment? }
1504
1505      context 'when environment is defined' do
1506        before do
1507          build.update!(environment: 'review')
1508        end
1509
1510        it { is_expected.to be_truthy }
1511      end
1512
1513      context 'when environment is not defined' do
1514        before do
1515          build.update!(environment: nil)
1516        end
1517
1518        it { is_expected.to be_falsey }
1519      end
1520    end
1521
1522    describe '#expanded_environment_name' do
1523      subject { build.expanded_environment_name }
1524
1525      context 'when environment uses $CI_COMMIT_REF_NAME' do
1526        let(:build) do
1527          create(:ci_build,
1528                 ref: 'master',
1529                 environment: 'review/$CI_COMMIT_REF_NAME')
1530        end
1531
1532        it { is_expected.to eq('review/master') }
1533      end
1534
1535      context 'when environment uses yaml_variables containing symbol keys' do
1536        let(:build) do
1537          create(:ci_build,
1538                 yaml_variables: [{ key: :APP_HOST, value: 'host' }],
1539                 environment: 'review/$APP_HOST')
1540        end
1541
1542        it 'returns an expanded environment name with a list of variables' do
1543          expect(build).to receive(:simple_variables).once.and_call_original
1544
1545          is_expected.to eq('review/host')
1546        end
1547
1548        context 'when build metadata has already persisted the expanded environment name' do
1549          before do
1550            build.metadata.expanded_environment_name = 'review/host'
1551          end
1552
1553          it 'returns a persisted expanded environment name without a list of variables' do
1554            expect(build).not_to receive(:simple_variables)
1555
1556            is_expected.to eq('review/host')
1557          end
1558        end
1559      end
1560
1561      context 'when using persisted variables' do
1562        let(:build) do
1563          create(:ci_build, environment: 'review/x$CI_BUILD_ID')
1564        end
1565
1566        it { is_expected.to eq('review/x') }
1567      end
1568    end
1569
1570    describe '#expanded_kubernetes_namespace' do
1571      let(:build) { create(:ci_build, environment: environment, options: options) }
1572
1573      subject { build.expanded_kubernetes_namespace }
1574
1575      context 'environment and namespace are not set' do
1576        let(:environment) { nil }
1577        let(:options) { nil }
1578
1579        it { is_expected.to be_nil }
1580      end
1581
1582      context 'environment is specified' do
1583        let(:environment) { 'production' }
1584
1585        context 'namespace is not set' do
1586          let(:options) { nil }
1587
1588          it { is_expected.to be_nil }
1589        end
1590
1591        context 'namespace is provided' do
1592          let(:options) do
1593            {
1594              environment: {
1595                name: environment,
1596                kubernetes: {
1597                  namespace: namespace
1598                }
1599              }
1600            }
1601          end
1602
1603          context 'with a static value' do
1604            let(:namespace) { 'production' }
1605
1606            it { is_expected.to eq namespace }
1607          end
1608
1609          context 'with a dynamic value' do
1610            let(:namespace) { 'deploy-$CI_COMMIT_REF_NAME'}
1611
1612            it { is_expected.to eq 'deploy-master' }
1613          end
1614        end
1615      end
1616    end
1617
1618    describe '#starts_environment?' do
1619      subject { build.starts_environment? }
1620
1621      context 'when environment is defined' do
1622        before do
1623          build.update!(environment: 'review')
1624        end
1625
1626        context 'no action is defined' do
1627          it { is_expected.to be_truthy }
1628        end
1629
1630        context 'and start action is defined' do
1631          before do
1632            build.update!(options: { environment: { action: 'start' } } )
1633          end
1634
1635          it { is_expected.to be_truthy }
1636        end
1637      end
1638
1639      context 'when environment is not defined' do
1640        before do
1641          build.update!(environment: nil)
1642        end
1643
1644        it { is_expected.to be_falsey }
1645      end
1646    end
1647
1648    describe '#stops_environment?' do
1649      subject { build.stops_environment? }
1650
1651      context 'when environment is defined' do
1652        before do
1653          build.update!(environment: 'review')
1654        end
1655
1656        context 'no action is defined' do
1657          it { is_expected.to be_falsey }
1658        end
1659
1660        context 'and stop action is defined' do
1661          before do
1662            build.update!(options: { environment: { action: 'stop' } } )
1663          end
1664
1665          it { is_expected.to be_truthy }
1666        end
1667      end
1668
1669      context 'when environment is not defined' do
1670        before do
1671          build.update!(environment: nil)
1672        end
1673
1674        it { is_expected.to be_falsey }
1675      end
1676    end
1677  end
1678
1679  describe 'erasable build' do
1680    shared_examples 'erasable' do
1681      it 'removes artifact file' do
1682        expect(build.artifacts_file.present?).to be_falsy
1683      end
1684
1685      it 'removes artifact metadata file' do
1686        expect(build.artifacts_metadata.present?).to be_falsy
1687      end
1688
1689      it 'removes all job_artifacts' do
1690        expect(build.job_artifacts.count).to eq(0)
1691      end
1692
1693      it 'erases build trace in trace file' do
1694        expect(build).not_to have_trace
1695      end
1696
1697      it 'sets erased to true' do
1698        expect(build.erased?).to be true
1699      end
1700
1701      it 'sets erase date' do
1702        expect(build.erased_at).not_to be_falsy
1703      end
1704    end
1705
1706    context 'build is not erasable' do
1707      let!(:build) { create(:ci_build) }
1708
1709      describe '#erase' do
1710        subject { build.erase }
1711
1712        it { is_expected.to be false }
1713      end
1714
1715      describe '#erasable?' do
1716        subject { build.erasable? }
1717
1718        it { is_expected.to eq false }
1719      end
1720    end
1721
1722    context 'build is erasable' do
1723      context 'new artifacts' do
1724        let!(:build) { create(:ci_build, :test_reports, :trace_artifact, :success, :artifacts) }
1725
1726        describe '#erase' do
1727          before do
1728            build.erase(erased_by: erased_by)
1729          end
1730
1731          context 'erased by user' do
1732            let!(:erased_by) { create(:user, username: 'eraser') }
1733
1734            include_examples 'erasable'
1735
1736            it 'records user who erased a build' do
1737              expect(build.erased_by).to eq erased_by
1738            end
1739          end
1740
1741          context 'erased by system' do
1742            let(:erased_by) { nil }
1743
1744            include_examples 'erasable'
1745
1746            it 'does not set user who erased a build' do
1747              expect(build.erased_by).to be_nil
1748            end
1749          end
1750        end
1751
1752        describe '#erasable?' do
1753          subject { build.erasable? }
1754
1755          it { is_expected.to be_truthy }
1756        end
1757
1758        describe '#erased?' do
1759          let!(:build) { create(:ci_build, :trace_artifact, :success, :artifacts) }
1760
1761          subject { build.erased? }
1762
1763          context 'job has not been erased' do
1764            it { is_expected.to be_falsey }
1765          end
1766
1767          context 'job has been erased' do
1768            before do
1769              build.erase
1770            end
1771
1772            it { is_expected.to be_truthy }
1773          end
1774        end
1775
1776        context 'metadata and build trace are not available' do
1777          let!(:build) { create(:ci_build, :success, :artifacts) }
1778
1779          before do
1780            build.erase_erasable_artifacts!
1781          end
1782
1783          describe '#erase' do
1784            it 'does not raise error' do
1785              expect { build.erase }.not_to raise_error
1786            end
1787          end
1788        end
1789      end
1790    end
1791  end
1792
1793  describe '#erase_erasable_artifacts!' do
1794    let!(:build) { create(:ci_build, :success) }
1795
1796    subject { build.erase_erasable_artifacts! }
1797
1798    before do
1799      Ci::JobArtifact.file_types.keys.each do |file_type|
1800        create(:ci_job_artifact, job: build, file_type: file_type, file_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[file_type.to_sym])
1801      end
1802    end
1803
1804    it "erases erasable artifacts" do
1805      subject
1806
1807      expect(build.job_artifacts.erasable).to be_empty
1808    end
1809
1810    it "keeps non erasable artifacts" do
1811      subject
1812
1813      Ci::JobArtifact::NON_ERASABLE_FILE_TYPES.each do |file_type|
1814        expect(build.send("job_artifacts_#{file_type}")).not_to be_nil
1815      end
1816    end
1817  end
1818
1819  describe '#first_pending' do
1820    let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) }
1821    let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') }
1822
1823    subject { described_class.first_pending }
1824
1825    it { is_expected.to be_a(described_class) }
1826    it('returns with the first pending build') { is_expected.to eq(first) }
1827  end
1828
1829  describe '#failed_but_allowed?' do
1830    subject { build.failed_but_allowed? }
1831
1832    context 'when build is not allowed to fail' do
1833      before do
1834        build.allow_failure = false
1835      end
1836
1837      context 'and build.status is success' do
1838        before do
1839          build.status = 'success'
1840        end
1841
1842        it { is_expected.to be_falsey }
1843      end
1844
1845      context 'and build.status is failed' do
1846        before do
1847          build.status = 'failed'
1848        end
1849
1850        it { is_expected.to be_falsey }
1851      end
1852    end
1853
1854    context 'when build is allowed to fail' do
1855      before do
1856        build.allow_failure = true
1857      end
1858
1859      context 'and build.status is success' do
1860        before do
1861          build.status = 'success'
1862        end
1863
1864        it { is_expected.to be_falsey }
1865      end
1866
1867      context 'and build status is failed' do
1868        before do
1869          build.status = 'failed'
1870        end
1871
1872        it { is_expected.to be_truthy }
1873      end
1874
1875      context 'when build is a manual action' do
1876        before do
1877          build.status = 'manual'
1878        end
1879
1880        it { is_expected.to be_falsey }
1881      end
1882    end
1883  end
1884
1885  describe 'flags' do
1886    describe '#cancelable?' do
1887      subject { build }
1888
1889      context 'when build is cancelable' do
1890        context 'when build is pending' do
1891          it { is_expected.to be_cancelable }
1892        end
1893
1894        context 'when build is running' do
1895          before do
1896            build.run!
1897          end
1898
1899          it { is_expected.to be_cancelable }
1900        end
1901
1902        context 'when build is created' do
1903          let(:build) { create(:ci_build, :created) }
1904
1905          it { is_expected.to be_cancelable }
1906        end
1907
1908        context 'when build is waiting for resource' do
1909          let(:build) { create(:ci_build, :waiting_for_resource) }
1910
1911          it { is_expected.to be_cancelable }
1912        end
1913      end
1914
1915      context 'when build is not cancelable' do
1916        context 'when build is successful' do
1917          before do
1918            build.success!
1919          end
1920
1921          it { is_expected.not_to be_cancelable }
1922        end
1923
1924        context 'when build is failed' do
1925          before do
1926            build.drop!
1927          end
1928
1929          it { is_expected.not_to be_cancelable }
1930        end
1931      end
1932    end
1933
1934    describe '#retryable?' do
1935      subject { build }
1936
1937      context 'when build is retryable' do
1938        context 'when build is successful' do
1939          before do
1940            build.success!
1941          end
1942
1943          it { is_expected.to be_retryable }
1944        end
1945
1946        context 'when build is failed' do
1947          before do
1948            build.drop!
1949          end
1950
1951          it { is_expected.to be_retryable }
1952        end
1953
1954        context 'when build is canceled' do
1955          before do
1956            build.cancel!
1957          end
1958
1959          it { is_expected.to be_retryable }
1960        end
1961      end
1962
1963      context 'when build is not retryable' do
1964        context 'when build is running' do
1965          before do
1966            build.run!
1967          end
1968
1969          it { is_expected.not_to be_retryable }
1970        end
1971
1972        context 'when build is skipped' do
1973          before do
1974            build.skip!
1975          end
1976
1977          it { is_expected.not_to be_retryable }
1978        end
1979
1980        context 'when build is degenerated' do
1981          before do
1982            build.degenerate!
1983          end
1984
1985          it { is_expected.not_to be_retryable }
1986        end
1987
1988        context 'when a canceled build has been retried already' do
1989          before do
1990            project.add_developer(user)
1991            build.cancel!
1992            described_class.retry(build, user)
1993          end
1994
1995          it { is_expected.not_to be_retryable }
1996        end
1997
1998        context 'when deployment is rejected' do
1999          before do
2000            build.drop!(:deployment_rejected)
2001          end
2002
2003          it { is_expected.not_to be_retryable }
2004        end
2005      end
2006    end
2007
2008    describe '#action?' do
2009      before do
2010        build.update!(when: value)
2011      end
2012
2013      subject { build.action? }
2014
2015      context 'when is set to manual' do
2016        let(:value) { 'manual' }
2017
2018        it { is_expected.to be_truthy }
2019      end
2020
2021      context 'when is set to delayed' do
2022        let(:value) { 'delayed' }
2023
2024        it { is_expected.to be_truthy }
2025      end
2026
2027      context 'when set to something else' do
2028        let(:value) { 'something else' }
2029
2030        it { is_expected.to be_falsey }
2031      end
2032    end
2033  end
2034
2035  describe '#tag_list' do
2036    let_it_be(:build) { create(:ci_build, tag_list: ['tag']) }
2037
2038    context 'when tags are preloaded' do
2039      it 'does not trigger queries' do
2040        build_with_tags = described_class.eager_load_tags.id_in([build]).to_a.first
2041
2042        expect { build_with_tags.tag_list }.not_to exceed_all_query_limit(0)
2043        expect(build_with_tags.tag_list).to eq(['tag'])
2044      end
2045    end
2046
2047    context 'when tags are not preloaded' do
2048      it { expect(described_class.find(build.id).tag_list).to eq(['tag']) }
2049    end
2050  end
2051
2052  describe '#has_tags?' do
2053    context 'when build has tags' do
2054      subject { create(:ci_build, tag_list: ['tag']) }
2055
2056      it { is_expected.to have_tags }
2057    end
2058
2059    context 'when build does not have tags' do
2060      subject { create(:ci_build, tag_list: []) }
2061
2062      it { is_expected.not_to have_tags }
2063    end
2064  end
2065
2066  describe 'build auto retry feature' do
2067    describe '#retries_count' do
2068      subject { create(:ci_build, name: 'test', pipeline: pipeline) }
2069
2070      context 'when build has been retried several times' do
2071        before do
2072          create(:ci_build, :retried, name: 'test', pipeline: pipeline)
2073          create(:ci_build, :retried, name: 'test', pipeline: pipeline)
2074        end
2075
2076        it 'reports a correct retry count value' do
2077          expect(subject.retries_count).to eq 2
2078        end
2079      end
2080
2081      context 'when build has not been retried' do
2082        it 'returns zero' do
2083          expect(subject.retries_count).to eq 0
2084        end
2085      end
2086    end
2087  end
2088
2089  describe '.keep_artifacts!' do
2090    let!(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days) }
2091    let!(:builds_for_update) do
2092      Ci::Build.where(id: create_list(:ci_build, 3, artifacts_expire_at: Time.current + 7.days).map(&:id))
2093    end
2094
2095    it 'resets expire_at' do
2096      builds_for_update.keep_artifacts!
2097
2098      builds_for_update.each do |build|
2099        expect(build.reload.artifacts_expire_at).to be_nil
2100      end
2101    end
2102
2103    it 'does not reset expire_at for other builds' do
2104      builds_for_update.keep_artifacts!
2105
2106      expect(build.reload.artifacts_expire_at).to be_present
2107    end
2108
2109    context 'when having artifacts files' do
2110      let!(:artifact) { create(:ci_job_artifact, job: build, expire_in: '7 days') }
2111      let!(:artifacts_for_update) do
2112        builds_for_update.map do |build|
2113          create(:ci_job_artifact, job: build, expire_in: '7 days')
2114        end
2115      end
2116
2117      it 'resets dependent objects' do
2118        builds_for_update.keep_artifacts!
2119
2120        artifacts_for_update.each do |artifact|
2121          expect(artifact.reload.expire_at).to be_nil
2122        end
2123      end
2124
2125      it 'does not reset dependent object for other builds' do
2126        builds_for_update.keep_artifacts!
2127
2128        expect(artifact.reload.expire_at).to be_present
2129      end
2130    end
2131  end
2132
2133  describe '#keep_artifacts!' do
2134    let(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days) }
2135
2136    subject { build.keep_artifacts! }
2137
2138    it 'to reset expire_at' do
2139      subject
2140
2141      expect(build.artifacts_expire_at).to be_nil
2142    end
2143
2144    context 'when having artifacts files' do
2145      let!(:artifact) { create(:ci_job_artifact, job: build, expire_in: '7 days') }
2146
2147      it 'to reset dependent objects' do
2148        subject
2149
2150        expect(artifact.reload.expire_at).to be_nil
2151      end
2152    end
2153  end
2154
2155  describe '#artifacts_file_for_type' do
2156    let(:build) { create(:ci_build, :artifacts) }
2157    let(:file_type) { :archive }
2158
2159    subject { build.artifacts_file_for_type(file_type) }
2160
2161    it 'queries artifacts for type' do
2162      expect(build).to receive_message_chain(:job_artifacts, :find_by).with(file_type: [Ci::JobArtifact.file_types[file_type]])
2163
2164      subject
2165    end
2166  end
2167
2168  describe '#merge_request' do
2169    subject { pipeline.builds.take.merge_request }
2170
2171    context 'on a branch pipeline' do
2172      let!(:pipeline) { create(:ci_pipeline, :with_job, project: project, ref: 'fix') }
2173
2174      context 'with no merge request' do
2175        it { is_expected.to be_nil }
2176      end
2177
2178      context 'with an open merge request from the same ref name' do
2179        let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'fix') }
2180
2181        # If no diff exists, the pipeline commit was not part of the merge
2182        # request and may have simply incidentally used the same ref name.
2183        context 'without a merge request diff containing the pipeline commit' do
2184          it { is_expected.to be_nil }
2185        end
2186
2187        # If the merge request was truly opened from the branch that the
2188        # pipeline ran on, that head sha will be present in a diff.
2189        context 'with a merge request diff containing the pipeline commit' do
2190          let!(:mr_diff) { create(:merge_request_diff, merge_request: merge_request) }
2191          let!(:mr_diff_commit) { create(:merge_request_diff_commit, sha: build.sha, merge_request_diff: mr_diff) }
2192
2193          it { is_expected.to eq(merge_request) }
2194        end
2195      end
2196
2197      context 'with multiple open merge requests' do
2198        let!(:merge_request)  { create(:merge_request, source_project: project, source_branch: 'fix') }
2199        let!(:mr_diff)        { create(:merge_request_diff, merge_request: merge_request) }
2200        let!(:mr_diff_commit) { create(:merge_request_diff_commit, sha: build.sha, merge_request_diff: mr_diff) }
2201
2202        let!(:new_merge_request)  { create(:merge_request, source_project: project, source_branch: 'fix', target_branch: 'staging') }
2203        let!(:new_mr_diff)        { create(:merge_request_diff, merge_request: new_merge_request) }
2204        let!(:new_mr_diff_commit) { create(:merge_request_diff_commit, sha: build.sha, merge_request_diff: new_mr_diff) }
2205
2206        it 'returns the first merge request' do
2207          expect(subject).to eq(merge_request)
2208        end
2209      end
2210    end
2211
2212    context 'on a detached merged request pipeline' do
2213      let(:pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline, :with_job) }
2214
2215      it { is_expected.to eq(pipeline.merge_request) }
2216    end
2217
2218    context 'on a legacy detached merged request pipeline' do
2219      let(:pipeline) { create(:ci_pipeline, :legacy_detached_merge_request_pipeline, :with_job) }
2220
2221      it { is_expected.to eq(pipeline.merge_request) }
2222    end
2223
2224    context 'on a pipeline for merged results' do
2225      let(:pipeline) { create(:ci_pipeline, :merged_result_pipeline, :with_job) }
2226
2227      it { is_expected.to eq(pipeline.merge_request) }
2228    end
2229  end
2230
2231  describe '#options' do
2232    let(:options) do
2233      {
2234        image: "ruby:2.7",
2235        services: ["postgres"],
2236        script: ["ls -a"]
2237      }
2238    end
2239
2240    it 'contains options' do
2241      expect(build.options).to eq(options.symbolize_keys)
2242    end
2243
2244    it 'allows to access with symbolized keys' do
2245      expect(build.options[:image]).to eq('ruby:2.7')
2246    end
2247
2248    it 'rejects access with string keys' do
2249      expect(build.options['image']).to be_nil
2250    end
2251
2252    it 'persist data in build metadata' do
2253      expect(build.metadata.read_attribute(:config_options)).to eq(options.symbolize_keys)
2254    end
2255
2256    it 'does not persist data in build' do
2257      expect(build.read_attribute(:options)).to be_nil
2258    end
2259
2260    context 'when options include artifacts:expose_as' do
2261      let(:build) { create(:ci_build, options: { artifacts: { expose_as: 'test' } }) }
2262
2263      it 'saves the presence of expose_as into build metadata' do
2264        expect(build.metadata).to have_exposed_artifacts
2265      end
2266    end
2267  end
2268
2269  describe '#other_manual_actions' do
2270    let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
2271    let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') }
2272
2273    subject { build.other_manual_actions }
2274
2275    before do
2276      project.add_developer(user)
2277    end
2278
2279    it 'returns other actions' do
2280      is_expected.to contain_exactly(other_build)
2281    end
2282
2283    context 'when build is retried' do
2284      let!(:new_build) { described_class.retry(build, user) }
2285
2286      it 'does not return any of them' do
2287        is_expected.not_to include(build, new_build)
2288      end
2289    end
2290
2291    context 'when other build is retried' do
2292      let!(:retried_build) { described_class.retry(other_build, user) }
2293
2294      before do
2295        retried_build.success
2296      end
2297
2298      it 'returns a retried build' do
2299        is_expected.to contain_exactly(retried_build)
2300      end
2301    end
2302  end
2303
2304  describe '#other_scheduled_actions' do
2305    let(:build) { create(:ci_build, :scheduled, pipeline: pipeline) }
2306
2307    subject { build.other_scheduled_actions }
2308
2309    before do
2310      project.add_developer(user)
2311    end
2312
2313    context "when other build's status is success" do
2314      let!(:other_build) { create(:ci_build, :schedulable, :success, pipeline: pipeline, name: 'other action') }
2315
2316      it 'returns other actions' do
2317        is_expected.to contain_exactly(other_build)
2318      end
2319    end
2320
2321    context "when other build's status is failed" do
2322      let!(:other_build) { create(:ci_build, :schedulable, :failed, pipeline: pipeline, name: 'other action') }
2323
2324      it 'returns other actions' do
2325        is_expected.to contain_exactly(other_build)
2326      end
2327    end
2328
2329    context "when other build's status is running" do
2330      let!(:other_build) { create(:ci_build, :schedulable, :running, pipeline: pipeline, name: 'other action') }
2331
2332      it 'does not return other actions' do
2333        is_expected.to be_empty
2334      end
2335    end
2336
2337    context "when other build's status is scheduled" do
2338      let!(:other_build) { create(:ci_build, :scheduled, pipeline: pipeline, name: 'other action') }
2339
2340      it 'does not return other actions' do
2341        is_expected.to contain_exactly(other_build)
2342      end
2343    end
2344  end
2345
2346  describe '#persisted_environment' do
2347    let!(:environment) do
2348      create(:environment, project: project, name: "foo-#{project.default_branch}")
2349    end
2350
2351    subject { build.persisted_environment }
2352
2353    context 'when referenced literally' do
2354      let(:build) do
2355        create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}")
2356      end
2357
2358      it { is_expected.to eq(environment) }
2359    end
2360
2361    context 'when referenced with a variable' do
2362      let(:build) do
2363        create(:ci_build, pipeline: pipeline, environment: "foo-$CI_COMMIT_REF_NAME")
2364      end
2365
2366      it { is_expected.to eq(environment) }
2367    end
2368
2369    context 'when there is no environment' do
2370      it { is_expected.to be_nil }
2371    end
2372
2373    context 'when build has a stop environment' do
2374      let(:build) { create(:ci_build, :stop_review_app, pipeline: pipeline, environment: "foo-#{project.default_branch}") }
2375
2376      it 'expands environment name' do
2377        expect(build).to receive(:expanded_environment_name).and_call_original
2378
2379        is_expected.to eq(environment)
2380      end
2381    end
2382  end
2383
2384  describe '#play' do
2385    let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
2386
2387    before do
2388      project.add_developer(user)
2389    end
2390
2391    it 'enqueues the build' do
2392      expect(build.play(user)).to be_pending
2393    end
2394  end
2395
2396  describe '#playable?' do
2397    context 'when build is a manual action' do
2398      context 'when build has been skipped' do
2399        subject { build_stubbed(:ci_build, :manual, status: :skipped) }
2400
2401        it { is_expected.not_to be_playable }
2402      end
2403
2404      context 'when build has been canceled' do
2405        subject { build_stubbed(:ci_build, :manual, status: :canceled) }
2406
2407        it { is_expected.to be_playable }
2408      end
2409
2410      context 'when build is successful' do
2411        subject { build_stubbed(:ci_build, :manual, status: :success) }
2412
2413        it { is_expected.to be_playable }
2414      end
2415
2416      context 'when build has failed' do
2417        subject { build_stubbed(:ci_build, :manual, status: :failed) }
2418
2419        it { is_expected.to be_playable }
2420      end
2421
2422      context 'when build is a manual untriggered action' do
2423        subject { build_stubbed(:ci_build, :manual, status: :manual) }
2424
2425        it { is_expected.to be_playable }
2426      end
2427
2428      context 'when build is a manual and degenerated' do
2429        subject { build_stubbed(:ci_build, :manual, :degenerated, status: :manual) }
2430
2431        it { is_expected.not_to be_playable }
2432      end
2433    end
2434
2435    context 'when build is scheduled' do
2436      subject { build_stubbed(:ci_build, :scheduled) }
2437
2438      it { is_expected.to be_playable }
2439    end
2440
2441    context 'when build is not a manual action' do
2442      subject { build_stubbed(:ci_build, :success) }
2443
2444      it { is_expected.not_to be_playable }
2445    end
2446  end
2447
2448  describe 'project settings' do
2449    describe '#allow_git_fetch' do
2450      it 'return project allow_git_fetch configuration' do
2451        expect(build.allow_git_fetch).to eq(project.build_allow_git_fetch)
2452      end
2453    end
2454  end
2455
2456  describe '#project' do
2457    subject { build.project }
2458
2459    it { is_expected.to eq(pipeline.project) }
2460  end
2461
2462  describe '#project_id' do
2463    subject { build.project_id }
2464
2465    it { is_expected.to eq(pipeline.project_id) }
2466  end
2467
2468  describe '#project_name' do
2469    subject { build.project_name }
2470
2471    it { is_expected.to eq(project.name) }
2472  end
2473
2474  describe '#ref_slug' do
2475    {
2476      'master'                => 'master',
2477      '1-foo'                 => '1-foo',
2478      'fix/1-foo'             => 'fix-1-foo',
2479      'fix-1-foo'             => 'fix-1-foo',
2480      'a' * 63                => 'a' * 63,
2481      'a' * 64                => 'a' * 63,
2482      'FOO'                   => 'foo',
2483      '-' + 'a' * 61 + '-'    => 'a' * 61,
2484      '-' + 'a' * 62 + '-'    => 'a' * 62,
2485      '-' + 'a' * 63 + '-'    => 'a' * 62,
2486      'a' * 62 + ' '          => 'a' * 62
2487    }.each do |ref, slug|
2488      it "transforms #{ref} to #{slug}" do
2489        build.ref = ref
2490
2491        expect(build.ref_slug).to eq(slug)
2492      end
2493    end
2494  end
2495
2496  describe '#repo_url' do
2497    subject { build.repo_url }
2498
2499    context 'when token is set' do
2500      before do
2501        build.ensure_token
2502      end
2503
2504      it { is_expected.to be_a(String) }
2505      it { is_expected.to end_with(".git") }
2506      it { is_expected.to start_with(project.web_url[0..6]) }
2507      it { is_expected.to include(build.token) }
2508      it { is_expected.to include('gitlab-ci-token') }
2509      it { is_expected.to include(project.web_url[7..]) }
2510    end
2511
2512    context 'when token is empty' do
2513      before do
2514        build.update_columns(token: nil, token_encrypted: nil)
2515      end
2516
2517      it { is_expected.to be_nil}
2518    end
2519  end
2520
2521  describe '#stuck?' do
2522    subject { build.stuck? }
2523
2524    context "when commit_status.status is pending" do
2525      before do
2526        build.status = 'pending'
2527      end
2528
2529      it { is_expected.to be_truthy }
2530
2531      context "and there are specific runner" do
2532        let!(:runner) { create(:ci_runner, :project, projects: [build.project], contacted_at: 1.second.ago) }
2533
2534        it { is_expected.to be_falsey }
2535      end
2536    end
2537
2538    %w[success failed canceled running].each do |state|
2539      context "when commit_status.status is #{state}" do
2540        before do
2541          build.status = state
2542        end
2543
2544        it { is_expected.to be_falsey }
2545      end
2546    end
2547  end
2548
2549  describe '#has_expired_locked_archive_artifacts?' do
2550    subject { build.has_expired_locked_archive_artifacts? }
2551
2552    context 'when build does not have artifacts' do
2553      it { is_expected.to eq(nil) }
2554    end
2555
2556    context 'when build has artifacts' do
2557      before do
2558        create(:ci_job_artifact, :archive, job: build)
2559      end
2560
2561      context 'when artifacts are unlocked' do
2562        before do
2563          build.pipeline.unlocked!
2564        end
2565
2566        it { is_expected.to eq(false) }
2567      end
2568
2569      context 'when artifacts are locked' do
2570        before do
2571          build.pipeline.artifacts_locked!
2572        end
2573
2574        context 'when artifacts do not expire' do
2575          it { is_expected.to eq(false) }
2576        end
2577
2578        context 'when artifacts expire in the future' do
2579          before do
2580            build.update!(artifacts_expire_at: 1.day.from_now)
2581          end
2582
2583          it { is_expected.to eq(false) }
2584        end
2585
2586        context 'when artifacts expired in the past' do
2587          before do
2588            build.update!(artifacts_expire_at: 1.day.ago)
2589          end
2590
2591          it { is_expected.to eq(true) }
2592        end
2593      end
2594    end
2595  end
2596
2597  describe '#has_expiring_archive_artifacts?' do
2598    context 'when artifacts have expiration date set' do
2599      before do
2600        build.update!(artifacts_expire_at: 1.day.from_now)
2601      end
2602
2603      context 'and job artifacts archive record exists' do
2604        let!(:archive) { create(:ci_job_artifact, :archive, job: build) }
2605
2606        it 'has expiring artifacts' do
2607          expect(build).to have_expiring_archive_artifacts
2608        end
2609      end
2610
2611      context 'and job artifacts archive record does not exist' do
2612        it 'does not have expiring artifacts' do
2613          expect(build).not_to have_expiring_archive_artifacts
2614        end
2615      end
2616    end
2617
2618    context 'when artifacts do not have expiration date set' do
2619      before do
2620        build.update!(artifacts_expire_at: nil)
2621      end
2622
2623      it 'does not have expiring artifacts' do
2624        expect(build).not_to have_expiring_archive_artifacts
2625      end
2626    end
2627  end
2628
2629  describe '#variables' do
2630    let(:container_registry_enabled) { false }
2631
2632    before do
2633      stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com')
2634      stub_config(dependency_proxy: { enabled: true })
2635    end
2636
2637    subject { build.variables }
2638
2639    context 'returns variables' do
2640      let(:predefined_variables) do
2641        [
2642          { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true, masked: false },
2643          { key: 'CI_PIPELINE_URL', value: project.web_url + "/-/pipelines/#{pipeline.id}", public: true, masked: false },
2644          { key: 'CI_JOB_ID', value: build.id.to_s, public: true, masked: false },
2645          { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true, masked: false },
2646          { key: 'CI_JOB_TOKEN', value: 'my-token', public: false, masked: true },
2647          { key: 'CI_JOB_STARTED_AT', value: build.started_at&.iso8601, public: true, masked: false },
2648          { key: 'CI_BUILD_ID', value: build.id.to_s, public: true, masked: false },
2649          { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false, masked: true },
2650          { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false },
2651          { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: true },
2652          { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false },
2653          { key: 'CI_DEPENDENCY_PROXY_USER', value: 'gitlab-ci-token', public: true, masked: false },
2654          { key: 'CI_DEPENDENCY_PROXY_PASSWORD', value: 'my-token', public: false, masked: true },
2655          { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true },
2656          { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false },
2657          { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false },
2658          { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false },
2659          { key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false },
2660          { key: 'CI_BUILD_STAGE', value: 'test', public: true, masked: false },
2661          { key: 'CI', value: 'true', public: true, masked: false },
2662          { key: 'GITLAB_CI', value: 'true', public: true, masked: false },
2663          { key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url, public: true, masked: false },
2664          { key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host, public: true, masked: false },
2665          { key: 'CI_SERVER_PORT', value: Gitlab.config.gitlab.port.to_s, public: true, masked: false },
2666          { key: 'CI_SERVER_PROTOCOL', value: Gitlab.config.gitlab.protocol, public: true, masked: false },
2667          { key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false },
2668          { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false },
2669          { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false },
2670          { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true, masked: false },
2671          { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true, masked: false },
2672          { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true, masked: false },
2673          { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false },
2674          { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true, masked: false },
2675          { key: 'CI_PROJECT_NAME', value: project.path, public: true, masked: false },
2676          { key: 'CI_PROJECT_TITLE', value: project.title, public: true, masked: false },
2677          { key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false },
2678          { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false },
2679          { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false },
2680          { key: 'CI_PROJECT_ROOT_NAMESPACE', value: project.namespace.root_ancestor.path, public: true, masked: false },
2681          { key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false },
2682          { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false },
2683          { key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: project.repository_languages.map(&:name).join(',').downcase, public: true, masked: false },
2684          { key: 'CI_PROJECT_CLASSIFICATION_LABEL', value: project.external_authorization_classification_label, public: true, masked: false },
2685          { key: 'CI_DEFAULT_BRANCH', value: project.default_branch, public: true, masked: false },
2686          { key: 'CI_CONFIG_PATH', value: project.ci_config_path_or_default, public: true, masked: false },
2687          { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false },
2688          { key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
2689          { key: 'CI_DEPENDENCY_PROXY_SERVER', value: Gitlab.host_with_port, public: true, masked: false },
2690          { key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX',
2691            value: "#{Gitlab.host_with_port}/#{project.namespace.root_ancestor.path.downcase}#{DependencyProxy::URL_SUFFIX}",
2692            public: true,
2693            masked: false },
2694          { key: 'CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX',
2695            value: "#{Gitlab.host_with_port}/#{project.namespace.full_path.downcase}#{DependencyProxy::URL_SUFFIX}",
2696            public: true,
2697            masked: false },
2698          { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
2699          { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false },
2700          { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false },
2701          { key: 'CI_PIPELINE_CREATED_AT', value: pipeline.created_at.iso8601, public: true, masked: false },
2702          { key: 'CI_COMMIT_SHA', value: build.sha, public: true, masked: false },
2703          { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true, masked: false },
2704          { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true, masked: false },
2705          { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true, masked: false },
2706          { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true, masked: false },
2707          { key: 'CI_COMMIT_BRANCH', value: build.ref, public: true, masked: false },
2708          { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false },
2709          { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false },
2710          { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false },
2711          { key: 'CI_COMMIT_REF_PROTECTED', value: (!!pipeline.protected_ref?).to_s, public: true, masked: false },
2712          { key: 'CI_COMMIT_TIMESTAMP', value: pipeline.git_commit_timestamp, public: true, masked: false },
2713          { key: 'CI_COMMIT_AUTHOR', value: pipeline.git_author_full_text, public: true, masked: false },
2714          { key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false },
2715          { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false },
2716          { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false },
2717          { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true, masked: false }
2718        ]
2719      end
2720
2721      before do
2722        allow(Gitlab::Ci::Jwt).to receive(:for_build).and_return('ci.job.jwt')
2723        build.set_token('my-token')
2724        build.yaml_variables = []
2725      end
2726
2727      it { is_expected.to be_instance_of(Gitlab::Ci::Variables::Collection) }
2728      it { expect(subject.to_runner_variables).to eq(predefined_variables) }
2729
2730      it 'excludes variables that require an environment or user' do
2731        environment_based_variables_collection = subject.filter do |variable|
2732          %w[
2733            YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG
2734            CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL
2735          ].include?(variable[:key])
2736        end
2737
2738        expect(environment_based_variables_collection).to be_empty
2739      end
2740
2741      context 'when ci_job_jwt feature flag is disabled' do
2742        before do
2743          stub_feature_flags(ci_job_jwt: false)
2744        end
2745
2746        it 'CI_JOB_JWT is not included' do
2747          expect(subject.pluck(:key)).not_to include('CI_JOB_JWT')
2748        end
2749      end
2750
2751      context 'when CI_JOB_JWT generation fails' do
2752        [
2753          OpenSSL::PKey::RSAError,
2754          Gitlab::Ci::Jwt::NoSigningKeyError
2755        ].each do |reason_to_fail|
2756          it 'CI_JOB_JWT is not included' do
2757            expect(Gitlab::Ci::Jwt).to receive(:for_build).and_raise(reason_to_fail)
2758            expect(Gitlab::ErrorTracking).to receive(:track_exception)
2759
2760            expect { subject }.not_to raise_error
2761            expect(subject.pluck(:key)).not_to include('CI_JOB_JWT')
2762          end
2763        end
2764      end
2765
2766      describe 'variables ordering' do
2767        context 'when variables hierarchy is stubbed' do
2768          let(:build_pre_var) { { key: 'build', value: 'value', public: true, masked: false } }
2769          let(:project_pre_var) { { key: 'project', value: 'value', public: true, masked: false } }
2770          let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true, masked: false } }
2771          let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } }
2772          let(:dependency_proxy_var) { { key: 'dependency_proxy', value: 'value', public: true, masked: false } }
2773          let(:job_jwt_var) { { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true } }
2774          let(:job_dependency_var) { { key: 'job_dependency', value: 'value', public: true, masked: false } }
2775
2776          before do
2777            allow_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder|
2778              allow(builder).to receive(:predefined_variables) { [build_pre_var] }
2779            end
2780
2781            allow(build).to receive(:yaml_variables) { [build_yaml_var] }
2782            allow(build).to receive(:persisted_variables) { [] }
2783            allow(build).to receive(:job_jwt_variables) { [job_jwt_var] }
2784            allow(build).to receive(:dependency_variables) { [job_dependency_var] }
2785            allow(build).to receive(:dependency_proxy_variables) { [dependency_proxy_var] }
2786
2787            allow(build.project)
2788              .to receive(:predefined_variables) { [project_pre_var] }
2789
2790            project.variables.create!(key: 'secret', value: 'value')
2791
2792            allow(build.pipeline)
2793              .to receive(:predefined_variables).and_return([pipeline_pre_var])
2794          end
2795
2796          it 'returns variables in order depending on resource hierarchy' do
2797            expect(subject.to_runner_variables).to eq(
2798              [dependency_proxy_var,
2799               job_jwt_var,
2800               build_pre_var,
2801               project_pre_var,
2802               pipeline_pre_var,
2803               build_yaml_var,
2804               job_dependency_var,
2805               { key: 'secret', value: 'value', public: false, masked: false }])
2806          end
2807        end
2808
2809        context 'when build has environment and user-provided variables' do
2810          let(:expected_variables) do
2811            predefined_variables.map { |variable| variable.fetch(:key) } +
2812              %w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG
2813                 CI_ENVIRONMENT_TIER CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL]
2814          end
2815
2816          before do
2817            create(:environment, project: build.project,
2818                                 name: 'staging')
2819
2820            build.yaml_variables = [{ key: 'YAML_VARIABLE',
2821                                      value: 'var',
2822                                      public: true }]
2823            build.environment = 'staging'
2824          end
2825
2826          it 'matches explicit variables ordering' do
2827            received_variables = subject.map { |variable| variable[:key] }
2828
2829            expect(received_variables).to eq expected_variables
2830          end
2831
2832          describe 'CI_ENVIRONMENT_ACTION' do
2833            let(:enviroment_action_variable) { subject.find { |variable| variable[:key] == 'CI_ENVIRONMENT_ACTION' } }
2834
2835            shared_examples 'defaults value' do
2836              it 'value matches start' do
2837                expect(enviroment_action_variable[:value]).to eq('start')
2838              end
2839            end
2840
2841            it_behaves_like 'defaults value'
2842
2843            context 'when options is set' do
2844              before do
2845                build.update!(options: options)
2846              end
2847
2848              context 'when options is empty' do
2849                let(:options) { {} }
2850
2851                it_behaves_like 'defaults value'
2852              end
2853
2854              context 'when options is nil' do
2855                let(:options) { nil }
2856
2857                it_behaves_like 'defaults value'
2858              end
2859
2860              context 'when options environment is specified' do
2861                let(:options) { { environment: {} } }
2862
2863                it_behaves_like 'defaults value'
2864              end
2865
2866              context 'when options environment action specified' do
2867                let(:options) { { environment: { action: 'stop' } } }
2868
2869                it 'matches the specified action' do
2870                  expect(enviroment_action_variable[:value]).to eq('stop')
2871                end
2872              end
2873            end
2874          end
2875        end
2876      end
2877    end
2878
2879    context 'when build has user' do
2880      let(:user_variables) do
2881        [
2882          { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true, masked: false },
2883          { key: 'GITLAB_USER_EMAIL', value: user.email, public: true, masked: false },
2884          { key: 'GITLAB_USER_LOGIN', value: user.username, public: true, masked: false },
2885          { key: 'GITLAB_USER_NAME', value: user.name, public: true, masked: false }
2886        ]
2887      end
2888
2889      before do
2890        build.update!(user: user)
2891      end
2892
2893      it { user_variables.each { |v| is_expected.to include(v) } }
2894    end
2895
2896    context 'when build belongs to a pipeline for merge request' do
2897      let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_branch: 'improve/awesome') }
2898      let(:pipeline) { merge_request.all_pipelines.first }
2899      let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) }
2900
2901      it 'returns values based on source ref' do
2902        is_expected.to include(
2903          { key: 'CI_COMMIT_REF_NAME', value: 'improve/awesome', public: true, masked: false },
2904          { key: 'CI_COMMIT_REF_SLUG', value: 'improve-awesome', public: true, masked: false }
2905        )
2906      end
2907    end
2908
2909    context 'when build has an environment' do
2910      let(:environment_variables) do
2911        [
2912          { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true, masked: false },
2913          { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug',  public: true, masked: false },
2914          { key: 'CI_ENVIRONMENT_TIER', value: 'production', public: true, masked: false }
2915        ]
2916      end
2917
2918      let!(:environment) do
2919        create(:environment,
2920               project: build.project,
2921               name: 'production',
2922               slug: 'prod-slug',
2923               tier: 'production',
2924               external_url: '')
2925      end
2926
2927      before do
2928        build.update!(environment: 'production')
2929      end
2930
2931      shared_examples 'containing environment variables' do
2932        it { is_expected.to include(*environment_variables) }
2933      end
2934
2935      context 'when no URL was set' do
2936        it_behaves_like 'containing environment variables'
2937
2938        it 'does not have CI_ENVIRONMENT_URL' do
2939          keys = subject.pluck(:key)
2940
2941          expect(keys).not_to include('CI_ENVIRONMENT_URL')
2942        end
2943      end
2944
2945      context 'when an URL was set' do
2946        let(:url) { 'http://host/test' }
2947
2948        before do
2949          environment_variables <<
2950            { key: 'CI_ENVIRONMENT_URL', value: url, public: true, masked: false }
2951        end
2952
2953        context 'when the URL was set from the job' do
2954          before do
2955            build.update!(options: { environment: { url: url } })
2956          end
2957
2958          it_behaves_like 'containing environment variables'
2959
2960          context 'when variables are used in the URL, it does not expand' do
2961            let(:url) { 'http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG' }
2962
2963            it_behaves_like 'containing environment variables'
2964
2965            it 'puts $CI_ENVIRONMENT_URL in the last so all other variables are available to be used when runners are trying to expand it' do
2966              expect(subject.to_runner_variables.last).to eq(environment_variables.last)
2967            end
2968          end
2969        end
2970
2971        context 'when the URL was not set from the job, but environment' do
2972          before do
2973            environment.update!(external_url: url)
2974          end
2975
2976          it_behaves_like 'containing environment variables'
2977        end
2978      end
2979
2980      context 'when project has an environment specific variable' do
2981        let(:environment_specific_variable) do
2982          { key: 'MY_STAGING_ONLY_VARIABLE', value: 'environment_specific_variable', public: false, masked: false }
2983        end
2984
2985        before do
2986          create(:ci_variable, environment_specific_variable.slice(:key, :value)
2987            .merge(project: project, environment_scope: 'stag*'))
2988        end
2989
2990        it_behaves_like 'containing environment variables'
2991
2992        context 'when environment scope does not match build environment' do
2993          it { is_expected.not_to include(environment_specific_variable) }
2994        end
2995
2996        context 'when environment scope matches build environment' do
2997          before do
2998            create(:environment, name: 'staging', project: project)
2999            build.update!(environment: 'staging')
3000          end
3001
3002          it { is_expected.to include(environment_specific_variable) }
3003        end
3004      end
3005    end
3006
3007    context 'when build started manually' do
3008      before do
3009        build.update!(when: :manual)
3010      end
3011
3012      let(:manual_variable) do
3013        { key: 'CI_JOB_MANUAL', value: 'true', public: true, masked: false }
3014      end
3015
3016      it { is_expected.to include(manual_variable) }
3017    end
3018
3019    context 'when job variable is defined' do
3020      let(:job_variable) { { key: 'first', value: 'first', public: false, masked: false } }
3021
3022      before do
3023        create(:ci_job_variable, job_variable.slice(:key, :value).merge(job: build))
3024      end
3025
3026      it { is_expected.to include(job_variable) }
3027    end
3028
3029    context 'when build is for branch' do
3030      let(:branch_variable) do
3031        { key: 'CI_COMMIT_BRANCH', value: 'master', public: true, masked: false }
3032      end
3033
3034      before do
3035        build.update!(tag: false)
3036        pipeline.update!(tag: false)
3037      end
3038
3039      it { is_expected.to include(branch_variable) }
3040    end
3041
3042    context 'when build is for tag' do
3043      let(:tag_variable) do
3044        { key: 'CI_COMMIT_TAG', value: 'master', public: true, masked: false }
3045      end
3046
3047      before do
3048        build.update!(tag: true)
3049        pipeline.update!(tag: true)
3050      end
3051
3052      it do
3053        build.reload
3054
3055        expect(subject).to include(tag_variable)
3056      end
3057    end
3058
3059    context 'when CI variable is defined' do
3060      let(:ci_variable) do
3061        { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false }
3062      end
3063
3064      before do
3065        create(:ci_variable,
3066               ci_variable.slice(:key, :value).merge(project: project))
3067      end
3068
3069      it { is_expected.to include(ci_variable) }
3070    end
3071
3072    context 'when protected variable is defined' do
3073      let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref }
3074
3075      let(:protected_variable) do
3076        { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false }
3077      end
3078
3079      before do
3080        create(:ci_variable,
3081               :protected,
3082               protected_variable.slice(:key, :value).merge(project: project))
3083      end
3084
3085      context 'when the branch is protected' do
3086        before do
3087          allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
3088        end
3089
3090        it { is_expected.to include(protected_variable) }
3091      end
3092
3093      context 'when the tag is protected' do
3094        before do
3095          allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
3096        end
3097
3098        it { is_expected.to include(protected_variable) }
3099      end
3100
3101      context 'when the ref is not protected' do
3102        it { is_expected.not_to include(protected_variable) }
3103      end
3104    end
3105
3106    context 'when group CI variable is defined' do
3107      let(:ci_variable) do
3108        { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false }
3109      end
3110
3111      before do
3112        create(:ci_group_variable,
3113               ci_variable.slice(:key, :value).merge(group: group))
3114      end
3115
3116      it { is_expected.to include(ci_variable) }
3117    end
3118
3119    context 'when group protected variable is defined' do
3120      let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref }
3121
3122      let(:protected_variable) do
3123        { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false }
3124      end
3125
3126      before do
3127        create(:ci_group_variable,
3128               :protected,
3129               protected_variable.slice(:key, :value).merge(group: group))
3130      end
3131
3132      context 'when the branch is protected' do
3133        before do
3134          allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
3135        end
3136
3137        it { is_expected.to include(protected_variable) }
3138      end
3139
3140      context 'when the tag is protected' do
3141        before do
3142          allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
3143        end
3144
3145        it { is_expected.to include(protected_variable) }
3146      end
3147
3148      context 'when the ref is not protected' do
3149        before do
3150          build.update_column(:ref, 'some/feature')
3151        end
3152
3153        it { is_expected.not_to include(protected_variable) }
3154      end
3155    end
3156
3157    context 'when build is for triggers' do
3158      let(:trigger) { create(:ci_trigger, project: project) }
3159      let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
3160
3161      let(:user_trigger_variable) do
3162        { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false, masked: false }
3163      end
3164
3165      let(:predefined_trigger_variable) do
3166        { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true, masked: false }
3167      end
3168
3169      before do
3170        build.trigger_request = trigger_request
3171      end
3172
3173      shared_examples 'returns variables for triggers' do
3174        it { is_expected.to include(user_trigger_variable) }
3175        it { is_expected.to include(predefined_trigger_variable) }
3176      end
3177
3178      context 'when variables are stored in trigger_request' do
3179        before do
3180          trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
3181        end
3182
3183        it_behaves_like 'returns variables for triggers'
3184      end
3185
3186      context 'when variables are stored in pipeline_variables' do
3187        before do
3188          create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
3189        end
3190
3191        it_behaves_like 'returns variables for triggers'
3192      end
3193    end
3194
3195    context 'when pipeline has a variable' do
3196      let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline) }
3197
3198      it { is_expected.to include(key: pipeline_variable.key, value: pipeline_variable.value, public: false, masked: false) }
3199    end
3200
3201    context 'when a job was triggered by a pipeline schedule' do
3202      let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
3203
3204      let!(:pipeline_schedule_variable) do
3205        create(:ci_pipeline_schedule_variable,
3206               key: 'SCHEDULE_VARIABLE_KEY',
3207               pipeline_schedule: pipeline_schedule)
3208      end
3209
3210      before do
3211        pipeline_schedule.pipelines << pipeline.reload
3212        pipeline_schedule.reload
3213      end
3214
3215      it { is_expected.to include(key: pipeline_schedule_variable.key, value: pipeline_schedule_variable.value, public: false, masked: false) }
3216    end
3217
3218    context 'when container registry is enabled' do
3219      let_it_be_with_reload(:project) { create(:project, :public, :repository, group: group) }
3220
3221      let_it_be_with_reload(:pipeline) do
3222        create(:ci_pipeline, project: project,
3223                             sha: project.commit.id,
3224                             ref: project.default_branch,
3225                             status: 'success')
3226      end
3227
3228      let_it_be_with_refind(:build) { create(:ci_build, pipeline: pipeline) }
3229
3230      let(:container_registry_enabled) { true }
3231      let(:ci_registry) do
3232        { key: 'CI_REGISTRY', value: 'registry.example.com', public: true, masked: false }
3233      end
3234
3235      let(:ci_registry_image) do
3236        { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true, masked: false }
3237      end
3238
3239      context 'and is disabled for project' do
3240        before do
3241          project.project_feature.update_column(:container_registry_access_level, ProjectFeature::DISABLED)
3242        end
3243
3244        it { is_expected.to include(ci_registry) }
3245        it { is_expected.not_to include(ci_registry_image) }
3246      end
3247
3248      context 'and is enabled for project' do
3249        before do
3250          project.project_feature.update_column(:container_registry_access_level, ProjectFeature::ENABLED)
3251        end
3252
3253        it { is_expected.to include(ci_registry) }
3254        it { is_expected.to include(ci_registry_image) }
3255      end
3256
3257      context 'and is private for project' do
3258        before do
3259          project.project_feature.update_column(:container_registry_access_level, ProjectFeature::PRIVATE)
3260        end
3261
3262        it { is_expected.to include(ci_registry) }
3263        it { is_expected.to include(ci_registry_image) }
3264      end
3265    end
3266
3267    context 'when runner is assigned to build' do
3268      let(:runner) { create(:ci_runner, description: 'description', tag_list: %w(docker linux)) }
3269
3270      before do
3271        build.update!(runner: runner)
3272      end
3273
3274      it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true, masked: false }) }
3275      it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true, masked: false }) }
3276      it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true, masked: false }) }
3277    end
3278
3279    context 'when build is for a deployment' do
3280      let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false, masked: false } }
3281
3282      before do
3283        build.environment = 'production'
3284
3285        allow_any_instance_of(Project)
3286          .to receive(:deployment_variables)
3287          .and_return([deployment_variable])
3288      end
3289
3290      it { is_expected.to include(deployment_variable) }
3291    end
3292
3293    context 'when project has default CI config path' do
3294      let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: '.gitlab-ci.yml', public: true, masked: false } }
3295
3296      it { is_expected.to include(ci_config_path) }
3297    end
3298
3299    context 'when project has custom CI config path' do
3300      let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } }
3301
3302      before do
3303        project.update!(ci_config_path: 'custom')
3304      end
3305
3306      it { is_expected.to include(ci_config_path) }
3307    end
3308
3309    context 'when pipeline variable overrides build variable' do
3310      let(:build) do
3311        create(:ci_build, pipeline: pipeline, yaml_variables: [{ key: 'MYVAR', value: 'myvar', public: true }])
3312      end
3313
3314      before do
3315        pipeline.variables.build(key: 'MYVAR', value: 'pipeline value')
3316      end
3317
3318      it 'overrides YAML variable using a pipeline variable' do
3319        variables = subject.to_runner_variables.reverse.uniq { |variable| variable[:key] }.reverse
3320
3321        expect(variables)
3322          .not_to include(key: 'MYVAR', value: 'myvar', public: true, masked: false)
3323        expect(variables)
3324          .to include(key: 'MYVAR', value: 'pipeline value', public: false, masked: false)
3325      end
3326    end
3327
3328    context 'when build is parallelized' do
3329      shared_examples 'parallelized jobs config' do
3330        let(:index) { 3 }
3331        let(:total) { 5 }
3332
3333        before do
3334          build.options[:parallel] = config
3335          build.options[:instance] = index
3336        end
3337
3338        it 'includes CI_NODE_INDEX' do
3339          is_expected.to include(
3340            { key: 'CI_NODE_INDEX', value: index.to_s, public: true, masked: false }
3341          )
3342        end
3343
3344        it 'includes correct CI_NODE_TOTAL' do
3345          is_expected.to include(
3346            { key: 'CI_NODE_TOTAL', value: total.to_s, public: true, masked: false }
3347          )
3348        end
3349      end
3350
3351      context 'when parallel is a number' do
3352        let(:config) { 5 }
3353
3354        it_behaves_like 'parallelized jobs config'
3355      end
3356
3357      context 'when parallel is hash with the total key' do
3358        let(:config) { { total: 5 } }
3359
3360        it_behaves_like 'parallelized jobs config'
3361      end
3362
3363      context 'when parallel is nil' do
3364        let(:config) {}
3365
3366        it_behaves_like 'parallelized jobs config' do
3367          let(:total) { 1 }
3368        end
3369      end
3370    end
3371
3372    context 'when build has not been persisted yet' do
3373      let(:build) do
3374        described_class.new(
3375          name: 'rspec',
3376          stage: 'test',
3377          ref: 'feature',
3378          project: project,
3379          pipeline: pipeline
3380        )
3381      end
3382
3383      let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
3384
3385      it 'returns static predefined variables' do
3386        expect(build.variables.size).to be >= 28
3387        expect(build.variables)
3388          .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false)
3389        expect(build).not_to be_persisted
3390      end
3391    end
3392
3393    context 'for deploy tokens' do
3394      let(:deploy_token) { create(:deploy_token, :gitlab_deploy_token) }
3395
3396      let(:deploy_token_variables) do
3397        [
3398          { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true, masked: false },
3399          { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false, masked: true }
3400        ]
3401      end
3402
3403      context 'when gitlab-deploy-token exists' do
3404        before do
3405          project.deploy_tokens << deploy_token
3406        end
3407
3408        it 'includes deploy token variables' do
3409          is_expected.to include(*deploy_token_variables)
3410        end
3411      end
3412
3413      context 'when gitlab-deploy-token does not exist' do
3414        it 'does not include deploy token variables' do
3415          expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil
3416          expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil
3417        end
3418      end
3419    end
3420
3421    context 'when build has dependency which has dotenv variable' do
3422      let!(:prepare) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
3423      let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: [prepare.name] }) }
3424
3425      let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) }
3426
3427      it { is_expected.to include(key: job_variable.key, value: job_variable.value, public: false, masked: false) }
3428    end
3429  end
3430
3431  describe '#scoped_variables' do
3432    it 'records a prometheus metric' do
3433      histogram = double(:histogram)
3434      expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_builder_scoped_variables_histogram)
3435        .and_return(histogram)
3436
3437      expect(histogram).to receive(:observe)
3438        .with({}, a_kind_of(ActiveSupport::Duration))
3439
3440      build.scoped_variables
3441    end
3442
3443    shared_examples 'calculates scoped_variables' do
3444      context 'when build has not been persisted yet' do
3445        let(:build) do
3446          described_class.new(
3447            name: 'rspec',
3448            stage: 'test',
3449            ref: 'feature',
3450            project: project,
3451            pipeline: pipeline,
3452            scheduling_type: :stage
3453          )
3454        end
3455
3456        let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
3457
3458        it 'does not persist the build' do
3459          expect(build).to be_valid
3460          expect(build).not_to be_persisted
3461
3462          build.scoped_variables
3463
3464          expect(build).not_to be_persisted
3465        end
3466
3467        it 'returns static predefined variables' do
3468          keys = %w[CI_JOB_NAME
3469                    CI_COMMIT_SHA
3470                    CI_COMMIT_SHORT_SHA
3471                    CI_COMMIT_REF_NAME
3472                    CI_COMMIT_REF_SLUG
3473                    CI_JOB_STAGE]
3474
3475          variables = build.scoped_variables
3476
3477          variables.map { |env| env[:key] }.tap do |names|
3478            expect(names).to include(*keys)
3479          end
3480
3481          expect(variables)
3482            .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false)
3483        end
3484
3485        it 'does not return prohibited variables' do
3486          keys = %w[CI_JOB_ID
3487                    CI_JOB_URL
3488                    CI_JOB_TOKEN
3489                    CI_BUILD_ID
3490                    CI_BUILD_TOKEN
3491                    CI_REGISTRY_USER
3492                    CI_REGISTRY_PASSWORD
3493                    CI_REPOSITORY_URL
3494                    CI_ENVIRONMENT_URL
3495                    CI_DEPLOY_USER
3496                    CI_DEPLOY_PASSWORD]
3497
3498          build.scoped_variables.map { |env| env[:key] }.tap do |names|
3499            expect(names).not_to include(*keys)
3500          end
3501        end
3502      end
3503
3504      context 'with dependency variables' do
3505        let!(:prepare) { create(:ci_build, name: 'prepare', pipeline: pipeline, stage_idx: 0) }
3506        let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['prepare'] }) }
3507
3508        let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) }
3509
3510        it 'inherits dependent variables' do
3511          expect(build.scoped_variables.to_hash).to include(job_variable.key => job_variable.value)
3512        end
3513      end
3514    end
3515
3516    it_behaves_like 'calculates scoped_variables'
3517
3518    it 'delegates to the variable builders' do
3519      expect_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder|
3520        expect(builder)
3521          .to receive(:scoped_variables).with(build, hash_including(:environment, :dependencies))
3522          .and_call_original
3523
3524        expect(builder).to receive(:predefined_variables).and_call_original
3525      end
3526
3527      build.scoped_variables
3528    end
3529  end
3530
3531  describe '#simple_variables_without_dependencies' do
3532    it 'does not load dependencies' do
3533      expect(build).not_to receive(:dependency_variables)
3534
3535      build.simple_variables_without_dependencies
3536    end
3537  end
3538
3539  shared_examples "secret CI variables" do
3540    context 'when ref is branch' do
3541      let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) }
3542
3543      context 'when ref is protected' do
3544        before do
3545          create(:protected_branch, :developers_can_merge, name: 'master', project: project)
3546        end
3547
3548        it { is_expected.to include(variable) }
3549      end
3550
3551      context 'when ref is not protected' do
3552        it { is_expected.not_to include(variable) }
3553      end
3554    end
3555
3556    context 'when ref is tag' do
3557      let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) }
3558
3559      context 'when ref is protected' do
3560        before do
3561          create(:protected_tag, project: project, name: 'v*')
3562        end
3563
3564        it { is_expected.to include(variable) }
3565      end
3566
3567      context 'when ref is not protected' do
3568        it { is_expected.not_to include(variable) }
3569      end
3570    end
3571
3572    context 'when ref is merge request' do
3573      let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
3574      let(:pipeline) { merge_request.pipelines_for_merge_request.first }
3575      let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) }
3576
3577      context 'when ref is protected' do
3578        before do
3579          create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project)
3580        end
3581
3582        it 'does not return protected variables as it is not supported for merge request pipelines' do
3583          is_expected.not_to include(variable)
3584        end
3585      end
3586
3587      context 'when ref is not protected' do
3588        it { is_expected.not_to include(variable) }
3589      end
3590    end
3591  end
3592
3593  describe '#secret_instance_variables' do
3594    subject { build.secret_instance_variables }
3595
3596    let_it_be(:variable) { create(:ci_instance_variable, protected: true) }
3597
3598    include_examples "secret CI variables"
3599  end
3600
3601  describe '#secret_group_variables' do
3602    subject { build.secret_group_variables }
3603
3604    let_it_be(:variable) { create(:ci_group_variable, protected: true, group: group) }
3605
3606    include_examples "secret CI variables"
3607  end
3608
3609  describe '#secret_project_variables' do
3610    subject { build.secret_project_variables }
3611
3612    let_it_be(:variable) { create(:ci_variable, protected: true, project: project) }
3613
3614    include_examples "secret CI variables"
3615  end
3616
3617  describe '#kubernetes_variables' do
3618    let(:build) { create(:ci_build) }
3619    let(:service) { double(execute: template) }
3620    let(:template) { double(to_yaml: 'example-kubeconfig', valid?: template_valid) }
3621    let(:template_valid) { true }
3622
3623    subject { build.kubernetes_variables }
3624
3625    before do
3626      allow(Ci::GenerateKubeconfigService).to receive(:new).with(build).and_return(service)
3627    end
3628
3629    it { is_expected.to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) }
3630
3631    context 'generated config is invalid' do
3632      let(:template_valid) { false }
3633
3634      it { is_expected.not_to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) }
3635    end
3636  end
3637
3638  describe '#deployment_variables' do
3639    let(:build) { create(:ci_build, environment: environment) }
3640    let(:environment) { 'production' }
3641    let(:kubernetes_namespace) { 'namespace' }
3642    let(:project_variables) { double }
3643
3644    subject { build.deployment_variables(environment: environment) }
3645
3646    before do
3647      allow(build).to receive(:expanded_kubernetes_namespace)
3648        .and_return(kubernetes_namespace)
3649
3650      allow(build.project).to receive(:deployment_variables)
3651        .with(environment: environment, kubernetes_namespace: kubernetes_namespace)
3652        .and_return(project_variables)
3653    end
3654
3655    it { is_expected.to eq(project_variables) }
3656
3657    context 'environment is nil' do
3658      let(:environment) { nil }
3659
3660      it { is_expected.to be_empty }
3661    end
3662  end
3663
3664  describe '#any_unmet_prerequisites?' do
3665    let(:build) { create(:ci_build, :created) }
3666
3667    subject { build.any_unmet_prerequisites? }
3668
3669    before do
3670      allow(build).to receive(:prerequisites).and_return(prerequisites)
3671    end
3672
3673    context 'build has prerequisites' do
3674      let(:prerequisites) { [double] }
3675
3676      it { is_expected.to be_truthy }
3677    end
3678
3679    context 'build does not have prerequisites' do
3680      let(:prerequisites) { [] }
3681
3682      it { is_expected.to be_falsey }
3683    end
3684  end
3685
3686  describe '#yaml_variables' do
3687    let(:build) { create(:ci_build, pipeline: pipeline, yaml_variables: variables) }
3688
3689    let(:variables) do
3690      [
3691        { 'key' => :VARIABLE, 'value' => 'my value' },
3692        { 'key' => 'VARIABLE2', 'value' => 'my value 2' }
3693      ]
3694    end
3695
3696    shared_examples 'having consistent representation' do
3697      it 'allows to access using symbols' do
3698        expect(build.reload.yaml_variables.first[:key]).to eq('VARIABLE')
3699        expect(build.reload.yaml_variables.first[:value]).to eq('my value')
3700        expect(build.reload.yaml_variables.second[:key]).to eq('VARIABLE2')
3701        expect(build.reload.yaml_variables.second[:value]).to eq('my value 2')
3702      end
3703    end
3704
3705    it_behaves_like 'having consistent representation'
3706
3707    it 'persist data in build metadata' do
3708      expect(build.metadata.read_attribute(:config_variables)).not_to be_nil
3709    end
3710
3711    it 'does not persist data in build' do
3712      expect(build.read_attribute(:yaml_variables)).to be_nil
3713    end
3714  end
3715
3716  describe '#dependency_variables' do
3717    subject { build.dependency_variables }
3718
3719    context 'when using dependencies' do
3720      let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) }
3721      let!(:prepare2) { create(:ci_build, name: 'prepare2', pipeline: pipeline, stage_idx: 0) }
3722      let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['prepare1'] }) }
3723
3724      let!(:job_variable_1) { create(:ci_job_variable, :dotenv_source, job: prepare1) }
3725      let!(:job_variable_2) { create(:ci_job_variable, job: prepare1) }
3726      let!(:job_variable_3) { create(:ci_job_variable, :dotenv_source, job: prepare2) }
3727
3728      it 'inherits only dependent variables' do
3729        expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value)
3730      end
3731    end
3732
3733    context 'when using needs' do
3734      let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) }
3735      let!(:prepare2) { create(:ci_build, name: 'prepare2', pipeline: pipeline, stage_idx: 0) }
3736      let!(:prepare3) { create(:ci_build, name: 'prepare3', pipeline: pipeline, stage_idx: 0) }
3737      let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, scheduling_type: 'dag') }
3738      let!(:build_needs_prepare1) { create(:ci_build_need, build: build, name: 'prepare1', artifacts: true) }
3739      let!(:build_needs_prepare2) { create(:ci_build_need, build: build, name: 'prepare2', artifacts: false) }
3740
3741      let!(:job_variable_1) { create(:ci_job_variable, :dotenv_source, job: prepare1) }
3742      let!(:job_variable_2) { create(:ci_job_variable, :dotenv_source, job: prepare2) }
3743      let!(:job_variable_3) { create(:ci_job_variable, :dotenv_source, job: prepare3) }
3744
3745      it 'inherits only needs with artifacts variables' do
3746        expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value)
3747      end
3748    end
3749  end
3750
3751  describe 'state transition: any => [:preparing]' do
3752    let(:build) { create(:ci_build, :created) }
3753
3754    before do
3755      allow(build).to receive(:prerequisites).and_return([double])
3756    end
3757
3758    it 'queues BuildPrepareWorker' do
3759      expect(Ci::BuildPrepareWorker).to receive(:perform_async).with(build.id)
3760
3761      build.enqueue
3762    end
3763  end
3764
3765  describe 'state transition: any => [:pending]' do
3766    let(:build) { create(:ci_build, :created) }
3767
3768    it 'queues BuildQueueWorker' do
3769      expect(BuildQueueWorker).to receive(:perform_async).with(build.id)
3770
3771      build.enqueue
3772    end
3773
3774    it 'queues BuildHooksWorker' do
3775      expect(BuildHooksWorker).to receive(:perform_async).with(build.id)
3776
3777      build.enqueue
3778    end
3779  end
3780
3781  describe 'state transition: pending: :running' do
3782    let(:runner) { create(:ci_runner) }
3783    let(:job) { create(:ci_build, :pending, runner: runner) }
3784
3785    before do
3786      job.project.update_attribute(:build_timeout, 1800)
3787    end
3788
3789    def run_job_without_exception
3790      job.run!
3791    rescue StateMachines::InvalidTransition
3792    end
3793
3794    context 'for pipeline ref existence' do
3795      it 'ensures pipeline ref creation' do
3796        expect(job.pipeline.persistent_ref).to receive(:create).once
3797
3798        run_job_without_exception
3799      end
3800
3801      it 'ensures that it is not run in database transaction' do
3802        expect(job.pipeline.persistent_ref).to receive(:create) do
3803          expect(ApplicationRecord).not_to be_inside_transaction
3804        end
3805
3806        run_job_without_exception
3807      end
3808    end
3809
3810    shared_examples 'saves data on transition' do
3811      it 'saves timeout' do
3812        expect { job.run! }.to change { job.reload.ensure_metadata.timeout }.from(nil).to(expected_timeout)
3813      end
3814
3815      it 'saves timeout_source' do
3816        expect { job.run! }.to change { job.reload.ensure_metadata.timeout_source }.from('unknown_timeout_source').to(expected_timeout_source)
3817      end
3818
3819      context 'when Ci::BuildMetadata#update_timeout_state fails update' do
3820        before do
3821          allow_any_instance_of(Ci::BuildMetadata).to receive(:update_timeout_state).and_return(false)
3822        end
3823
3824        it "doesn't save timeout" do
3825          expect { run_job_without_exception }.not_to change { job.reload.ensure_metadata.timeout }
3826        end
3827
3828        it "doesn't save timeout_source" do
3829          expect { run_job_without_exception }.not_to change { job.reload.ensure_metadata.timeout_source }
3830        end
3831      end
3832    end
3833
3834    context 'when runner timeout overrides project timeout' do
3835      let(:expected_timeout) { 900 }
3836      let(:expected_timeout_source) { 'runner_timeout_source' }
3837
3838      before do
3839        runner.update_attribute(:maximum_timeout, 900)
3840      end
3841
3842      it_behaves_like 'saves data on transition'
3843    end
3844
3845    context "when runner timeout doesn't override project timeout" do
3846      let(:expected_timeout) { 1800 }
3847      let(:expected_timeout_source) { 'project_timeout_source' }
3848
3849      before do
3850        runner.update_attribute(:maximum_timeout, 3600)
3851      end
3852
3853      it_behaves_like 'saves data on transition'
3854    end
3855  end
3856
3857  describe '#has_valid_build_dependencies?' do
3858    shared_examples 'validation is active' do
3859      context 'when depended job has not been completed yet' do
3860        let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
3861
3862        it { expect(job).to have_valid_build_dependencies }
3863      end
3864
3865      context 'when artifacts of depended job has been expired' do
3866        let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
3867
3868        context 'when pipeline is not locked' do
3869          before do
3870            build.pipeline.unlocked!
3871          end
3872
3873          it { expect(job).not_to have_valid_build_dependencies }
3874        end
3875
3876        context 'when pipeline is locked' do
3877          before do
3878            build.pipeline.artifacts_locked!
3879          end
3880
3881          it { expect(job).to have_valid_build_dependencies }
3882        end
3883      end
3884
3885      context 'when artifacts of depended job has been erased' do
3886        let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
3887
3888        before do
3889          pre_stage_job.erase
3890        end
3891
3892        it { expect(job).not_to have_valid_build_dependencies }
3893      end
3894    end
3895
3896    shared_examples 'validation is not active' do
3897      context 'when depended job has not been completed yet' do
3898        let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
3899
3900        it { expect(job).to have_valid_build_dependencies }
3901      end
3902
3903      context 'when artifacts of depended job has been expired' do
3904        let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
3905
3906        it { expect(job).to have_valid_build_dependencies }
3907      end
3908
3909      context 'when artifacts of depended job has been erased' do
3910        let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) }
3911
3912        before do
3913          pre_stage_job.erase
3914        end
3915
3916        it { expect(job).to have_valid_build_dependencies }
3917      end
3918    end
3919
3920    let!(:job) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: options) }
3921    let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0) }
3922
3923    context 'when "dependencies" keyword is not defined' do
3924      let(:options) { {} }
3925
3926      it { expect(job).to have_valid_build_dependencies }
3927    end
3928
3929    context 'when "dependencies" keyword is empty' do
3930      let(:options) { { dependencies: [] } }
3931
3932      it { expect(job).to have_valid_build_dependencies }
3933    end
3934
3935    context 'when "dependencies" keyword is specified' do
3936      let(:options) { { dependencies: ['test'] } }
3937
3938      it_behaves_like 'validation is active'
3939    end
3940  end
3941
3942  describe 'state transition when build fails' do
3943    let(:service) { ::MergeRequests::AddTodoWhenBuildFailsService.new(project: project, current_user: user) }
3944
3945    before do
3946      allow(::MergeRequests::AddTodoWhenBuildFailsService).to receive(:new).and_return(service)
3947      allow(service).to receive(:close)
3948    end
3949
3950    context 'when build is configured to be retried' do
3951      subject { create(:ci_build, :running, options: { script: ["ls -al"], retry: 3 }, project: project, user: user) }
3952
3953      it 'retries build and assigns the same user to it' do
3954        expect(described_class).to receive(:retry)
3955          .with(subject, user)
3956
3957        subject.drop!
3958      end
3959
3960      it 'does not try to create a todo' do
3961        project.add_developer(user)
3962
3963        expect(service).not_to receive(:pipeline_merge_requests)
3964
3965        subject.drop!
3966      end
3967
3968      context 'when retry service raises Gitlab::Access::AccessDeniedError exception' do
3969        let(:retry_service) { Ci::RetryBuildService.new(subject.project, subject.user) }
3970
3971        before do
3972          allow_any_instance_of(Ci::RetryBuildService)
3973            .to receive(:execute)
3974            .with(subject)
3975            .and_raise(Gitlab::Access::AccessDeniedError)
3976          allow(Gitlab::AppLogger).to receive(:error)
3977        end
3978
3979        it 'handles raised exception' do
3980          expect { subject.drop! }.not_to raise_error
3981        end
3982
3983        it 'logs the error' do
3984          subject.drop!
3985
3986          expect(Gitlab::AppLogger)
3987            .to have_received(:error)
3988            .with(a_string_matching("Unable to auto-retry job #{subject.id}"))
3989        end
3990
3991        it 'fails the job' do
3992          subject.drop!
3993          expect(subject.failed?).to be_truthy
3994        end
3995      end
3996    end
3997
3998    context 'when build is not configured to be retried' do
3999      subject { create(:ci_build, :running, project: project, user: user, pipeline: pipeline) }
4000
4001      let(:pipeline) do
4002        create(:ci_pipeline,
4003          project: project,
4004          ref: 'feature',
4005          sha: merge_request.diff_head_sha,
4006          merge_requests_as_head_pipeline: [merge_request])
4007      end
4008
4009      let(:merge_request) do
4010        create(:merge_request, :opened,
4011          source_branch: 'feature',
4012          source_project: project,
4013          target_branch: 'master',
4014          target_project: project)
4015      end
4016
4017      it 'does not retry build' do
4018        expect(described_class).not_to receive(:retry)
4019
4020        subject.drop!
4021      end
4022
4023      it 'does not count retries when not necessary' do
4024        expect(described_class).not_to receive(:retry)
4025        expect_any_instance_of(described_class)
4026          .not_to receive(:retries_count)
4027
4028        subject.drop!
4029      end
4030
4031      it 'creates a todo async', :sidekiq_inline do
4032        project.add_developer(user)
4033
4034        expect_next_instance_of(TodoService) do |todo_service|
4035          expect(todo_service)
4036            .to receive(:merge_request_build_failed).with(merge_request)
4037        end
4038
4039        subject.drop!
4040      end
4041    end
4042
4043    context 'when associated deployment failed to update its status' do
4044      let(:build) { create(:ci_build, :running, pipeline: pipeline) }
4045      let!(:deployment) { create(:deployment, deployable: build) }
4046
4047      before do
4048        allow_any_instance_of(Deployment)
4049          .to receive(:drop!).and_raise('Unexpected error')
4050      end
4051
4052      it 'can drop the build' do
4053        expect(Gitlab::ErrorTracking).to receive(:track_exception)
4054
4055        expect { build.drop! }.not_to raise_error
4056
4057        expect(build).to be_failed
4058      end
4059    end
4060  end
4061
4062  describe '.matches_tag_ids' do
4063    let_it_be(:build, reload: true) { create(:ci_build, project: project, user: user) }
4064
4065    let(:tag_ids) { ::ActsAsTaggableOn::Tag.named_any(tag_list).ids }
4066
4067    subject { described_class.where(id: build).matches_tag_ids(tag_ids) }
4068
4069    before do
4070      build.update!(tag_list: build_tag_list)
4071    end
4072
4073    context 'when have different tags' do
4074      let(:build_tag_list) { %w(A B) }
4075      let(:tag_list) { %w(C D) }
4076
4077      it "does not match a build" do
4078        is_expected.not_to contain_exactly(build)
4079      end
4080    end
4081
4082    context 'when have a subset of tags' do
4083      let(:build_tag_list) { %w(A B) }
4084      let(:tag_list) { %w(A B C D) }
4085
4086      it "does match a build" do
4087        is_expected.to contain_exactly(build)
4088      end
4089    end
4090
4091    context 'when build does not have tags' do
4092      let(:build_tag_list) { [] }
4093      let(:tag_list) { %w(C D) }
4094
4095      it "does match a build" do
4096        is_expected.to contain_exactly(build)
4097      end
4098    end
4099
4100    context 'when does not have a subset of tags' do
4101      let(:build_tag_list) { %w(A B C) }
4102      let(:tag_list) { %w(C D) }
4103
4104      it "does not match a build" do
4105        is_expected.not_to contain_exactly(build)
4106      end
4107    end
4108  end
4109
4110  describe '.matches_tags' do
4111    let_it_be(:build, reload: true) { create(:ci_build, project: project, user: user) }
4112
4113    subject { described_class.where(id: build).with_any_tags }
4114
4115    before do
4116      build.update!(tag_list: tag_list)
4117    end
4118
4119    context 'when does have tags' do
4120      let(:tag_list) { %w(A B) }
4121
4122      it "does match a build" do
4123        is_expected.to contain_exactly(build)
4124      end
4125    end
4126
4127    context 'when does not have tags' do
4128      let(:tag_list) { [] }
4129
4130      it "does not match a build" do
4131        is_expected.not_to contain_exactly(build)
4132      end
4133    end
4134  end
4135
4136  describe 'pages deployments' do
4137    let_it_be(:build, reload: true) { create(:ci_build, project: project, user: user) }
4138
4139    context 'when job is "pages"' do
4140      before do
4141        build.name = 'pages'
4142      end
4143
4144      context 'when pages are enabled' do
4145        before do
4146          allow(Gitlab.config.pages).to receive_messages(enabled: true)
4147        end
4148
4149        it 'is marked as pages generator' do
4150          expect(build).to be_pages_generator
4151        end
4152
4153        context 'job succeeds' do
4154          it "calls pages worker" do
4155            expect(PagesWorker).to receive(:perform_async).with(:deploy, build.id)
4156
4157            build.success!
4158          end
4159        end
4160
4161        context 'job fails' do
4162          it "does not call pages worker" do
4163            expect(PagesWorker).not_to receive(:perform_async)
4164
4165            build.drop!
4166          end
4167        end
4168      end
4169
4170      context 'when pages are disabled' do
4171        before do
4172          allow(Gitlab.config.pages).to receive_messages(enabled: false)
4173        end
4174
4175        it 'is not marked as pages generator' do
4176          expect(build).not_to be_pages_generator
4177        end
4178
4179        context 'job succeeds' do
4180          it "does not call pages worker" do
4181            expect(PagesWorker).not_to receive(:perform_async)
4182
4183            build.success!
4184          end
4185        end
4186      end
4187    end
4188
4189    context 'when job is not "pages"' do
4190      before do
4191        build.name = 'other-job'
4192      end
4193
4194      it 'is not marked as pages generator' do
4195        expect(build).not_to be_pages_generator
4196      end
4197
4198      context 'job succeeds' do
4199        it "does not call pages worker" do
4200          expect(PagesWorker).not_to receive(:perform_async)
4201
4202          build.success
4203        end
4204      end
4205    end
4206  end
4207
4208  describe '#has_terminal?' do
4209    let(:states) { described_class.state_machines[:status].states.keys - [:running] }
4210
4211    subject { build.has_terminal? }
4212
4213    it 'returns true if the build is running and it has a runner_session_url' do
4214      build.build_runner_session(url: 'whatever')
4215      build.status = :running
4216
4217      expect(subject).to be_truthy
4218    end
4219
4220    context 'returns false' do
4221      it 'when runner_session_url is empty' do
4222        build.status = :running
4223
4224        expect(subject).to be_falsey
4225      end
4226
4227      context 'unless the build is running' do
4228        before do
4229          build.build_runner_session(url: 'whatever')
4230        end
4231
4232        it do
4233          states.each do |state|
4234            build.status = state
4235
4236            is_expected.to be_falsey
4237          end
4238        end
4239      end
4240    end
4241  end
4242
4243  describe '#collect_test_reports!' do
4244    subject { build.collect_test_reports!(test_reports) }
4245
4246    let(:test_reports) { Gitlab::Ci::Reports::TestReports.new }
4247
4248    it { expect(test_reports.get_suite(build.name).total_count).to eq(0) }
4249
4250    context 'when build has a test report' do
4251      context 'when there is a JUnit test report from rspec test suite' do
4252        before do
4253          create(:ci_job_artifact, :junit, job: build, project: build.project)
4254        end
4255
4256        it 'parses blobs and add the results to the test suite' do
4257          expect { subject }.not_to raise_error
4258
4259          expect(test_reports.get_suite(build.name).total_count).to eq(4)
4260          expect(test_reports.get_suite(build.name).success_count).to be(2)
4261          expect(test_reports.get_suite(build.name).failed_count).to be(2)
4262        end
4263      end
4264
4265      context 'when there is a JUnit test report from java ant test suite' do
4266        before do
4267          create(:ci_job_artifact, :junit_with_ant, job: build, project: build.project)
4268        end
4269
4270        it 'parses blobs and add the results to the test suite' do
4271          expect { subject }.not_to raise_error
4272
4273          expect(test_reports.get_suite(build.name).total_count).to eq(3)
4274          expect(test_reports.get_suite(build.name).success_count).to be(3)
4275          expect(test_reports.get_suite(build.name).failed_count).to be(0)
4276        end
4277      end
4278
4279      context 'when there is a corrupted JUnit test report' do
4280        before do
4281          create(:ci_job_artifact, :junit_with_corrupted_data, job: build, project: build.project)
4282        end
4283
4284        it 'returns no test data and includes a suite_error message' do
4285          expect { subject }.not_to raise_error
4286
4287          expect(test_reports.get_suite(build.name).total_count).to eq(0)
4288          expect(test_reports.get_suite(build.name).success_count).to eq(0)
4289          expect(test_reports.get_suite(build.name).failed_count).to eq(0)
4290          expect(test_reports.get_suite(build.name).suite_error).to eq('JUnit XML parsing failed: 1:1: FATAL: Document is empty')
4291        end
4292      end
4293    end
4294  end
4295
4296  describe '#collect_accessibility_reports!' do
4297    subject { build.collect_accessibility_reports!(accessibility_report) }
4298
4299    let(:accessibility_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
4300
4301    it { expect(accessibility_report.urls).to eq({}) }
4302
4303    context 'when build has an accessibility report' do
4304      context 'when there is an accessibility report with errors' do
4305        before do
4306          create(:ci_job_artifact, :accessibility, job: build, project: build.project)
4307        end
4308
4309        it 'parses blobs and add the results to the accessibility report' do
4310          expect { subject }.not_to raise_error
4311
4312          expect(accessibility_report.urls.keys).to match_array(['https://about.gitlab.com/'])
4313          expect(accessibility_report.errors_count).to eq(10)
4314          expect(accessibility_report.scans_count).to eq(1)
4315          expect(accessibility_report.passes_count).to eq(0)
4316        end
4317      end
4318
4319      context 'when there is an accessibility report without errors' do
4320        before do
4321          create(:ci_job_artifact, :accessibility_without_errors, job: build, project: build.project)
4322        end
4323
4324        it 'parses blobs and add the results to the accessibility report' do
4325          expect { subject }.not_to raise_error
4326
4327          expect(accessibility_report.urls.keys).to match_array(['https://pa11y.org/'])
4328          expect(accessibility_report.errors_count).to eq(0)
4329          expect(accessibility_report.scans_count).to eq(1)
4330          expect(accessibility_report.passes_count).to eq(1)
4331        end
4332      end
4333
4334      context 'when there is an accessibility report with an invalid url' do
4335        before do
4336          create(:ci_job_artifact, :accessibility_with_invalid_url, job: build, project: build.project)
4337        end
4338
4339        it 'parses blobs and add the results to the accessibility report' do
4340          expect { subject }.not_to raise_error
4341
4342          expect(accessibility_report.urls).to be_empty
4343          expect(accessibility_report.errors_count).to eq(0)
4344          expect(accessibility_report.scans_count).to eq(0)
4345          expect(accessibility_report.passes_count).to eq(0)
4346        end
4347      end
4348    end
4349  end
4350
4351  describe '#collect_coverage_reports!' do
4352    subject { build.collect_coverage_reports!(coverage_report) }
4353
4354    let(:coverage_report) { Gitlab::Ci::Reports::CoverageReports.new }
4355
4356    it { expect(coverage_report.files).to eq({}) }
4357
4358    context 'when build has a coverage report' do
4359      context 'when there is a Cobertura coverage report from simplecov-cobertura' do
4360        before do
4361          create(:ci_job_artifact, :cobertura, job: build, project: build.project)
4362        end
4363
4364        it 'parses blobs and add the results to the coverage report' do
4365          expect { subject }.not_to raise_error
4366
4367          expect(coverage_report.files.keys).to match_array(['app/controllers/abuse_reports_controller.rb'])
4368          expect(coverage_report.files['app/controllers/abuse_reports_controller.rb'].count).to eq(23)
4369        end
4370      end
4371
4372      context 'when there is a Cobertura coverage report from gocov-xml' do
4373        before do
4374          create(:ci_job_artifact, :coverage_gocov_xml, job: build, project: build.project)
4375        end
4376
4377        it 'parses blobs and add the results to the coverage report' do
4378          expect { subject }.not_to raise_error
4379
4380          expect(coverage_report.files.keys).to match_array(['auth/token.go', 'auth/rpccredentials.go'])
4381          expect(coverage_report.files['auth/token.go'].count).to eq(49)
4382          expect(coverage_report.files['auth/rpccredentials.go'].count).to eq(10)
4383        end
4384      end
4385
4386      context 'when there is a Cobertura coverage report with class filename paths not relative to project root' do
4387        before do
4388          allow(build.project).to receive(:full_path).and_return('root/javademo')
4389          allow(build.pipeline).to receive(:all_worktree_paths).and_return(['src/main/java/com/example/javademo/User.java'])
4390
4391          create(:ci_job_artifact, :coverage_with_paths_not_relative_to_project_root, job: build, project: build.project)
4392        end
4393
4394        it 'parses blobs and add the results to the coverage report with corrected paths' do
4395          expect { subject }.not_to raise_error
4396
4397          expect(coverage_report.files.keys).to match_array(['src/main/java/com/example/javademo/User.java'])
4398        end
4399      end
4400
4401      context 'when there is a corrupted Cobertura coverage report' do
4402        before do
4403          create(:ci_job_artifact, :coverage_with_corrupted_data, job: build, project: build.project)
4404        end
4405
4406        it 'raises an error' do
4407          expect { subject }.to raise_error(Gitlab::Ci::Parsers::Coverage::Cobertura::InvalidLineInformationError)
4408        end
4409      end
4410    end
4411  end
4412
4413  describe '#collect_codequality_reports!' do
4414    subject(:codequality_report) { build.collect_codequality_reports!(Gitlab::Ci::Reports::CodequalityReports.new) }
4415
4416    it { expect(codequality_report.degradations).to eq({}) }
4417
4418    context 'when build has a codequality report' do
4419      context 'when there is a codequality report' do
4420        before do
4421          create(:ci_job_artifact, :codequality, job: build, project: build.project)
4422        end
4423
4424        it 'parses blobs and add the results to the codequality report' do
4425          expect { codequality_report }.not_to raise_error
4426
4427          expect(codequality_report.degradations_count).to eq(3)
4428        end
4429      end
4430
4431      context 'when there is an codequality report without errors' do
4432        before do
4433          create(:ci_job_artifact, :codequality_without_errors, job: build, project: build.project)
4434        end
4435
4436        it 'parses blobs and add the results to the codequality report' do
4437          expect { codequality_report }.not_to raise_error
4438
4439          expect(codequality_report.degradations_count).to eq(0)
4440        end
4441      end
4442    end
4443  end
4444
4445  describe '#collect_terraform_reports!' do
4446    let(:terraform_reports) { Gitlab::Ci::Reports::TerraformReports.new }
4447
4448    it 'returns an empty hash' do
4449      expect(build.collect_terraform_reports!(terraform_reports).plans).to eq({})
4450    end
4451
4452    context 'when build has a terraform report' do
4453      context 'when there is a valid tfplan.json' do
4454        before do
4455          create(:ci_job_artifact, :terraform, job: build, project: build.project)
4456        end
4457
4458        it 'parses blobs and add the results to the terraform report' do
4459          expect { build.collect_terraform_reports!(terraform_reports) }.not_to raise_error
4460
4461          terraform_reports.plans.each do |key, hash_value|
4462            expect(hash_value.keys).to match_array(%w[create delete job_id job_name job_path update])
4463          end
4464
4465          expect(terraform_reports.plans).to match(
4466            a_hash_including(
4467              build.id.to_s => a_hash_including(
4468                'create' => 0,
4469                'update' => 1,
4470                'delete' => 0,
4471                'job_name' => build.name
4472              )
4473            )
4474          )
4475        end
4476      end
4477
4478      context 'when there is an invalid tfplan.json' do
4479        before do
4480          create(:ci_job_artifact, :terraform_with_corrupted_data, job: build, project: build.project)
4481        end
4482
4483        it 'adds invalid plan report' do
4484          expect { build.collect_terraform_reports!(terraform_reports) }.not_to raise_error
4485
4486          terraform_reports.plans.each do |key, hash_value|
4487            expect(hash_value.keys).to match_array(%w[job_id job_name job_path tf_report_error])
4488          end
4489
4490          expect(terraform_reports.plans).to match(
4491            a_hash_including(
4492              build.id.to_s => a_hash_including(
4493                'tf_report_error' => :invalid_json_format
4494              )
4495            )
4496          )
4497        end
4498      end
4499    end
4500  end
4501
4502  describe '#report_artifacts' do
4503    subject { build.report_artifacts }
4504
4505    context 'when the build has reports' do
4506      let!(:report) { create(:ci_job_artifact, :codequality, job: build) }
4507
4508      it 'returns the artifacts with reports' do
4509        expect(subject).to contain_exactly(report)
4510      end
4511    end
4512  end
4513
4514  describe '#artifacts_metadata_entry' do
4515    let_it_be(:build) { create(:ci_build, project: project) }
4516
4517    let(:path) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' }
4518
4519    around do |example|
4520      freeze_time { example.run }
4521    end
4522
4523    before do
4524      stub_artifacts_object_storage
4525    end
4526
4527    subject { build.artifacts_metadata_entry(path) }
4528
4529    context 'when using local storage' do
4530      let!(:metadata) { create(:ci_job_artifact, :metadata, job: build) }
4531
4532      context 'for existing file' do
4533        it 'does exist' do
4534          is_expected.to be_exists
4535        end
4536      end
4537
4538      context 'for non-existing file' do
4539        let(:path) { 'invalid-file' }
4540
4541        it 'does not exist' do
4542          is_expected.not_to be_exists
4543        end
4544      end
4545    end
4546
4547    context 'when using remote storage' do
4548      include HttpIOHelpers
4549
4550      let!(:metadata) { create(:ci_job_artifact, :remote_store, :metadata, job: build) }
4551      let(:file_path) { expand_fixture_path('ci_build_artifacts_metadata.gz') }
4552
4553      before do
4554        stub_remote_url_206(metadata.file.url, file_path)
4555      end
4556
4557      context 'for existing file' do
4558        it 'does exist' do
4559          is_expected.to be_exists
4560        end
4561      end
4562
4563      context 'for non-existing file' do
4564        let(:path) { 'invalid-file' }
4565
4566        it 'does not exist' do
4567          is_expected.not_to be_exists
4568        end
4569      end
4570    end
4571  end
4572
4573  describe '#publishes_artifacts_reports?' do
4574    let(:build) { create(:ci_build, options: options) }
4575
4576    subject { build.publishes_artifacts_reports? }
4577
4578    context 'when artifacts reports are defined' do
4579      let(:options) do
4580        { artifacts: { reports: { junit: "junit.xml" } } }
4581      end
4582
4583      it { is_expected.to be_truthy }
4584    end
4585
4586    context 'when artifacts reports missing defined' do
4587      let(:options) do
4588        { artifacts: { paths: ["file.txt"] } }
4589      end
4590
4591      it { is_expected.to be_falsey }
4592    end
4593
4594    context 'when options are missing' do
4595      let(:options) { nil }
4596
4597      it { is_expected.to be_falsey }
4598    end
4599  end
4600
4601  describe '#runner_required_feature_names' do
4602    let(:build) { create(:ci_build, options: options) }
4603
4604    subject { build.runner_required_feature_names }
4605
4606    context 'when artifacts reports are defined' do
4607      let(:options) do
4608        { artifacts: { reports: { junit: "junit.xml" } } }
4609      end
4610
4611      it { is_expected.to include(:upload_multiple_artifacts) }
4612    end
4613
4614    context 'when artifacts exclude is defined' do
4615      let(:options) do
4616        { artifacts: { exclude: %w[something] } }
4617      end
4618
4619      it { is_expected.to include(:artifacts_exclude) }
4620    end
4621  end
4622
4623  describe '#supported_runner?' do
4624    let_it_be_with_refind(:build) { create(:ci_build) }
4625
4626    subject { build.supported_runner?(runner_features) }
4627
4628    context 'when `upload_multiple_artifacts` feature is required by build' do
4629      before do
4630        expect(build).to receive(:runner_required_feature_names) do
4631          [:upload_multiple_artifacts]
4632        end
4633      end
4634
4635      context 'when runner provides given feature' do
4636        let(:runner_features) do
4637          { upload_multiple_artifacts: true }
4638        end
4639
4640        it { is_expected.to be_truthy }
4641      end
4642
4643      context 'when runner does not provide given feature' do
4644        let(:runner_features) do
4645          {}
4646        end
4647
4648        it { is_expected.to be_falsey }
4649      end
4650    end
4651
4652    context 'when `refspecs` feature is required by build' do
4653      before do
4654        allow(build).to receive(:merge_request_ref?) { true }
4655      end
4656
4657      context 'when runner provides given feature' do
4658        let(:runner_features) { { refspecs: true } }
4659
4660        it { is_expected.to be_truthy }
4661      end
4662
4663      context 'when runner does not provide given feature' do
4664        let(:runner_features) { {} }
4665
4666        it { is_expected.to be_falsey }
4667      end
4668    end
4669
4670    context 'when `multi_build_steps` feature is required by build' do
4671      before do
4672        expect(build).to receive(:runner_required_feature_names) do
4673          [:multi_build_steps]
4674        end
4675      end
4676
4677      context 'when runner provides given feature' do
4678        let(:runner_features) { { multi_build_steps: true } }
4679
4680        it { is_expected.to be_truthy }
4681      end
4682
4683      context 'when runner does not provide given feature' do
4684        let(:runner_features) { {} }
4685
4686        it { is_expected.to be_falsey }
4687      end
4688    end
4689
4690    context 'when `return_exit_code` feature is required by build' do
4691      let(:options) { { allow_failure_criteria: { exit_codes: [1] } } }
4692
4693      before do
4694        build.update!(options: options)
4695      end
4696
4697      context 'when runner provides given feature' do
4698        let(:runner_features) { { return_exit_code: true } }
4699
4700        it { is_expected.to be_truthy }
4701      end
4702
4703      context 'when runner does not provide given feature' do
4704        let(:runner_features) { {} }
4705
4706        it { is_expected.to be_falsey }
4707      end
4708
4709      context 'when the runner does not provide all of the required features' do
4710        let(:options) do
4711          {
4712            allow_failure_criteria: { exit_codes: [1] },
4713            artifacts: { reports: { junit: "junit.xml" } }
4714          }
4715        end
4716
4717        let(:runner_features) { { return_exit_code: true } }
4718
4719        it 'requires `upload_multiple_artifacts` too' do
4720          is_expected.to be_falsey
4721        end
4722      end
4723    end
4724  end
4725
4726  describe '#deployment_status' do
4727    before do
4728      allow_any_instance_of(described_class).to receive(:create_deployment)
4729    end
4730
4731    context 'when build is a last deployment' do
4732      let(:build) { create(:ci_build, :success, environment: 'production', pipeline: pipeline, project: project) }
4733      let(:environment) { create(:environment, name: 'production', project: build.project) }
4734      let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) }
4735
4736      it { expect(build.deployment_status).to eq(:last) }
4737    end
4738
4739    context 'when there is a newer build with deployment' do
4740      let(:build) { create(:ci_build, :success, environment: 'production', pipeline: pipeline, project: project) }
4741      let(:environment) { create(:environment, name: 'production', project: build.project) }
4742      let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) }
4743      let!(:last_deployment) { create(:deployment, :success, environment: environment, project: environment.project) }
4744
4745      it { expect(build.deployment_status).to eq(:out_of_date) }
4746    end
4747
4748    context 'when build with deployment has failed' do
4749      let(:build) { create(:ci_build, :failed, environment: 'production', pipeline: pipeline, project: project) }
4750      let(:environment) { create(:environment, name: 'production', project: build.project) }
4751      let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) }
4752
4753      it { expect(build.deployment_status).to eq(:failed) }
4754    end
4755
4756    context 'when build with deployment is running' do
4757      let(:build) { create(:ci_build, environment: 'production', pipeline: pipeline, project: project) }
4758      let(:environment) { create(:environment, name: 'production', project: build.project) }
4759      let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) }
4760
4761      it { expect(build.deployment_status).to eq(:creating) }
4762    end
4763  end
4764
4765  describe '#degenerated?' do
4766    context 'when build is degenerated' do
4767      subject { create(:ci_build, :degenerated) }
4768
4769      it { is_expected.to be_degenerated }
4770    end
4771
4772    context 'when build is valid' do
4773      subject { create(:ci_build) }
4774
4775      it { is_expected.not_to be_degenerated }
4776
4777      context 'and becomes degenerated' do
4778        before do
4779          subject.degenerate!
4780        end
4781
4782        it { is_expected.to be_degenerated }
4783      end
4784    end
4785  end
4786
4787  describe 'degenerate!' do
4788    let(:build) { create(:ci_build) }
4789
4790    subject { build.degenerate! }
4791
4792    before do
4793      build.ensure_metadata
4794      build.needs.create!(name: 'another-job')
4795    end
4796
4797    it 'drops metadata' do
4798      subject
4799
4800      expect(build.reload).to be_degenerated
4801      expect(build.metadata).to be_nil
4802      expect(build.needs).to be_empty
4803    end
4804  end
4805
4806  describe '#archived?' do
4807    context 'when build is degenerated' do
4808      subject { create(:ci_build, :degenerated) }
4809
4810      it { is_expected.to be_archived }
4811    end
4812
4813    context 'for old build' do
4814      subject { create(:ci_build, created_at: 1.day.ago) }
4815
4816      context 'when archive_builds_in is set' do
4817        before do
4818          stub_application_setting(archive_builds_in_seconds: 3600)
4819        end
4820
4821        it { is_expected.to be_archived }
4822      end
4823
4824      context 'when archive_builds_in is not set' do
4825        before do
4826          stub_application_setting(archive_builds_in_seconds: nil)
4827        end
4828
4829        it { is_expected.not_to be_archived }
4830      end
4831    end
4832  end
4833
4834  describe '#read_metadata_attribute' do
4835    let(:build) { create(:ci_build, :degenerated) }
4836    let(:build_options) { { key: "build" } }
4837    let(:metadata_options) { { key: "metadata" } }
4838    let(:default_options) { { key: "default" } }
4839
4840    subject { build.send(:read_metadata_attribute, :options, :config_options, default_options) }
4841
4842    context 'when build and metadata options is set' do
4843      before do
4844        build.write_attribute(:options, build_options)
4845        build.ensure_metadata.write_attribute(:config_options, metadata_options)
4846      end
4847
4848      it 'prefers build options' do
4849        is_expected.to eq(build_options)
4850      end
4851    end
4852
4853    context 'when only metadata options is set' do
4854      before do
4855        build.write_attribute(:options, nil)
4856        build.ensure_metadata.write_attribute(:config_options, metadata_options)
4857      end
4858
4859      it 'returns metadata options' do
4860        is_expected.to eq(metadata_options)
4861      end
4862    end
4863
4864    context 'when none is set' do
4865      it 'returns default value' do
4866        is_expected.to eq(default_options)
4867      end
4868    end
4869  end
4870
4871  describe '#write_metadata_attribute' do
4872    let(:build) { create(:ci_build, :degenerated) }
4873    let(:options) { { key: "new options" } }
4874    let(:existing_options) { { key: "existing options" } }
4875
4876    subject { build.send(:write_metadata_attribute, :options, :config_options, options) }
4877
4878    context 'when data in build is already set' do
4879      before do
4880        build.write_attribute(:options, existing_options)
4881      end
4882
4883      it 'does set metadata options' do
4884        subject
4885
4886        expect(build.metadata.read_attribute(:config_options)).to eq(options)
4887      end
4888
4889      it 'does reset build options' do
4890        subject
4891
4892        expect(build.read_attribute(:options)).to be_nil
4893      end
4894    end
4895  end
4896
4897  describe '#invalid_dependencies' do
4898    let!(:pre_stage_job_valid) { create(:ci_build, :manual, pipeline: pipeline, name: 'test1', stage_idx: 0) }
4899    let!(:pre_stage_job_invalid) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test2', stage_idx: 1) }
4900    let!(:job) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 2, options: { dependencies: %w(test1 test2) }) }
4901
4902    context 'when pipeline is locked' do
4903      before do
4904        build.pipeline.unlocked!
4905      end
4906
4907      it 'returns invalid dependencies when expired' do
4908        expect(job.invalid_dependencies).to eq([pre_stage_job_invalid])
4909      end
4910    end
4911
4912    context 'when pipeline is not locked' do
4913      before do
4914        build.pipeline.artifacts_locked!
4915      end
4916
4917      it 'returns no invalid dependencies when expired' do
4918        expect(job.invalid_dependencies).to eq([])
4919      end
4920    end
4921  end
4922
4923  describe '#execute_hooks' do
4924    before do
4925      build.clear_memoization(:build_data)
4926    end
4927
4928    context 'with project hooks' do
4929      let(:build_data) { double(:BuildData, dup: double(:DupedData)) }
4930
4931      before do
4932        create(:project_hook, project: project, job_events: true)
4933      end
4934
4935      it 'calls project.execute_hooks(build_data, :job_hooks)' do
4936        expect(::Gitlab::DataBuilder::Build)
4937          .to receive(:build).with(build).and_return(build_data)
4938        expect(build.project)
4939          .to receive(:execute_hooks).with(build_data.dup, :job_hooks)
4940
4941        build.execute_hooks
4942      end
4943    end
4944
4945    context 'without project hooks' do
4946      it 'does not call project.execute_hooks' do
4947        expect(build.project).not_to receive(:execute_hooks)
4948
4949        build.execute_hooks
4950      end
4951    end
4952
4953    context 'with project services' do
4954      before do
4955        create(:integration, active: true, job_events: true, project: project)
4956      end
4957
4958      it 'executes services' do
4959        allow_next_found_instance_of(Integration) do |integration|
4960          expect(integration).to receive(:async_execute)
4961        end
4962
4963        build.execute_hooks
4964      end
4965    end
4966
4967    context 'without relevant project services' do
4968      before do
4969        create(:integration, active: true, job_events: false, project: project)
4970      end
4971
4972      it 'does not execute services' do
4973        allow_next_found_instance_of(Integration) do |integration|
4974          expect(integration).not_to receive(:async_execute)
4975        end
4976
4977        build.execute_hooks
4978      end
4979    end
4980  end
4981
4982  describe '#environment_auto_stop_in' do
4983    subject { build.environment_auto_stop_in }
4984
4985    context 'when build option has environment auto_stop_in' do
4986      let(:build) { create(:ci_build, options: { environment: { name: 'test', auto_stop_in: '1 day' } }) }
4987
4988      it { is_expected.to eq('1 day') }
4989    end
4990
4991    context 'when build option does not have environment auto_stop_in' do
4992      let(:build) { create(:ci_build) }
4993
4994      it { is_expected.to be_nil }
4995    end
4996  end
4997
4998  describe '#degradation_threshold' do
4999    subject { build.degradation_threshold }
5000
5001    context 'when threshold variable is defined' do
5002      before do
5003        build.yaml_variables = [
5004          { key: 'SOME_VAR_1', value: 'SOME_VAL_1' },
5005          { key: 'DEGRADATION_THRESHOLD', value: '5' },
5006          { key: 'SOME_VAR_2', value: 'SOME_VAL_2' }
5007        ]
5008      end
5009
5010      it { is_expected.to eq(5) }
5011    end
5012
5013    context 'when threshold variable is not defined' do
5014      before do
5015        build.yaml_variables = [
5016          { key: 'SOME_VAR_1', value: 'SOME_VAL_1' },
5017          { key: 'SOME_VAR_2', value: 'SOME_VAL_2' }
5018        ]
5019      end
5020
5021      it { is_expected.to be_nil }
5022    end
5023  end
5024
5025  describe '#run_on_status_commit' do
5026    it 'runs provided hook after status commit' do
5027      action = spy('action')
5028
5029      build.run_on_status_commit { action.perform! }
5030      build.success!
5031
5032      expect(action).to have_received(:perform!).once
5033    end
5034
5035    it 'does not run hooks when status has not changed' do
5036      action = spy('action')
5037
5038      build.run_on_status_commit { action.perform! }
5039      build.save!
5040
5041      expect(action).not_to have_received(:perform!)
5042    end
5043  end
5044
5045  describe '#debug_mode?' do
5046    subject { build.debug_mode? }
5047
5048    context 'when CI_DEBUG_TRACE=true is in variables' do
5049      context 'when in instance variables' do
5050        before do
5051          create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true')
5052        end
5053
5054        it { is_expected.to eq true }
5055      end
5056
5057      context 'when in group variables' do
5058        before do
5059          create(:ci_group_variable, key: 'CI_DEBUG_TRACE', value: 'true', group: project.group)
5060        end
5061
5062        it { is_expected.to eq true }
5063      end
5064
5065      context 'when in pipeline variables' do
5066        before do
5067          create(:ci_pipeline_variable, key: 'CI_DEBUG_TRACE', value: 'true', pipeline: pipeline)
5068        end
5069
5070        it { is_expected.to eq true }
5071      end
5072
5073      context 'when in project variables' do
5074        before do
5075          create(:ci_variable, key: 'CI_DEBUG_TRACE', value: 'true', project: project)
5076        end
5077
5078        it { is_expected.to eq true }
5079      end
5080
5081      context 'when in job variables' do
5082        before do
5083          create(:ci_job_variable, key: 'CI_DEBUG_TRACE', value: 'true', job: build)
5084        end
5085
5086        it { is_expected.to eq true }
5087      end
5088
5089      context 'when in yaml variables' do
5090        before do
5091          build.update!(yaml_variables: [{ key: :CI_DEBUG_TRACE, value: 'true' }])
5092        end
5093
5094        it { is_expected.to eq true }
5095      end
5096    end
5097
5098    context 'when CI_DEBUG_TRACE is not in variables' do
5099      it { is_expected.to eq false }
5100    end
5101  end
5102
5103  describe '#drop_with_exit_code!' do
5104    let(:exit_code) { 1 }
5105    let(:options) { {} }
5106
5107    before do
5108      build.options.merge!(options)
5109      build.save!
5110    end
5111
5112    subject(:drop_with_exit_code) do
5113      build.drop_with_exit_code!(:unknown_failure, exit_code)
5114    end
5115
5116    shared_examples 'drops the build without changing allow_failure' do
5117      it 'does not change allow_failure' do
5118        expect { drop_with_exit_code }
5119          .not_to change { build.reload.allow_failure }
5120      end
5121
5122      it 'drops the build' do
5123        expect { drop_with_exit_code }
5124          .to change { build.reload.failed? }
5125      end
5126    end
5127
5128    context 'when exit_codes are not defined' do
5129      it_behaves_like 'drops the build without changing allow_failure'
5130    end
5131
5132    context 'when allow_failure_criteria is nil' do
5133      let(:options) { { allow_failure_criteria: nil } }
5134
5135      it_behaves_like 'drops the build without changing allow_failure'
5136    end
5137
5138    context 'when exit_codes is nil' do
5139      let(:options) do
5140        {
5141          allow_failure_criteria: {
5142            exit_codes: nil
5143          }
5144        }
5145      end
5146
5147      it_behaves_like 'drops the build without changing allow_failure'
5148    end
5149
5150    context 'when exit_codes do not match' do
5151      let(:options) do
5152        {
5153          allow_failure_criteria: {
5154            exit_codes: [2, 3, 4]
5155          }
5156        }
5157      end
5158
5159      it_behaves_like 'drops the build without changing allow_failure'
5160    end
5161
5162    context 'with matching exit codes' do
5163      let(:options) do
5164        { allow_failure_criteria: { exit_codes: [1, 2, 3] } }
5165      end
5166
5167      it 'changes allow_failure' do
5168        expect { drop_with_exit_code }
5169          .to change { build.reload.allow_failure }
5170      end
5171
5172      it 'drops the build' do
5173        expect { drop_with_exit_code }
5174          .to change { build.reload.failed? }
5175      end
5176
5177      it 'is executed inside a transaction' do
5178        expect(build).to receive(:drop!)
5179          .with(:unknown_failure)
5180          .and_raise(ActiveRecord::Rollback)
5181
5182        expect(build).to receive(:conditionally_allow_failure!)
5183          .with(1)
5184          .and_call_original
5185
5186        expect { drop_with_exit_code }
5187          .not_to change { build.reload.allow_failure }
5188      end
5189
5190      context 'when exit_code is nil' do
5191        let(:exit_code) {}
5192
5193        it_behaves_like 'drops the build without changing allow_failure'
5194      end
5195    end
5196  end
5197
5198  describe '#exit_codes_defined?' do
5199    let(:options) { {} }
5200
5201    before do
5202      build.options.merge!(options)
5203    end
5204
5205    subject(:exit_codes_defined) do
5206      build.exit_codes_defined?
5207    end
5208
5209    context 'without allow_failure_criteria' do
5210      it { is_expected.to be_falsey }
5211    end
5212
5213    context 'when exit_codes is nil' do
5214      let(:options) do
5215        {
5216          allow_failure_criteria: {
5217            exit_codes: nil
5218          }
5219        }
5220      end
5221
5222      it { is_expected.to be_falsey }
5223    end
5224
5225    context 'when exit_codes is an empty array' do
5226      let(:options) do
5227        {
5228          allow_failure_criteria: {
5229            exit_codes: []
5230          }
5231        }
5232      end
5233
5234      it { is_expected.to be_falsey }
5235    end
5236
5237    context 'when exit_codes are defined' do
5238      let(:options) do
5239        {
5240          allow_failure_criteria: {
5241            exit_codes: [5, 6]
5242          }
5243        }
5244      end
5245
5246      it { is_expected.to be_truthy }
5247    end
5248  end
5249
5250  describe '.build_matchers' do
5251    let_it_be(:pipeline) { create(:ci_pipeline, :protected) }
5252
5253    subject(:matchers) { pipeline.builds.build_matchers(pipeline.project) }
5254
5255    context 'when the pipeline is empty' do
5256      it 'does not throw errors' do
5257        is_expected.to eq([])
5258      end
5259    end
5260
5261    context 'when the pipeline has builds' do
5262      let_it_be(:build_without_tags) do
5263        create(:ci_build, pipeline: pipeline)
5264      end
5265
5266      let_it_be(:build_with_tags) do
5267        create(:ci_build, pipeline: pipeline, tag_list: %w[tag1 tag2])
5268      end
5269
5270      let_it_be(:other_build_with_tags) do
5271        create(:ci_build, pipeline: pipeline, tag_list: %w[tag2 tag1])
5272      end
5273
5274      it { expect(matchers.size).to eq(2) }
5275
5276      it 'groups build ids' do
5277        expect(matchers.map(&:build_ids)).to match_array([
5278          [build_without_tags.id],
5279          match_array([build_with_tags.id, other_build_with_tags.id])
5280        ])
5281      end
5282
5283      it { expect(matchers.map(&:tag_list)).to match_array([[], %w[tag1 tag2]]) }
5284
5285      it { expect(matchers.map(&:protected?)).to all be_falsey }
5286
5287      context 'when the builds are protected' do
5288        before do
5289          pipeline.builds.update_all(protected: true)
5290        end
5291
5292        it { expect(matchers).to all be_protected }
5293      end
5294    end
5295  end
5296
5297  describe '#build_matcher' do
5298    let_it_be(:build) do
5299      build_stubbed(:ci_build, tag_list: %w[tag1 tag2])
5300    end
5301
5302    subject(:matcher) { build.build_matcher }
5303
5304    it { expect(matcher.build_ids).to eq([build.id]) }
5305
5306    it { expect(matcher.tag_list).to match_array(%w[tag1 tag2]) }
5307
5308    it { expect(matcher.protected?).to eq(build.protected?) }
5309
5310    it { expect(matcher.project).to eq(build.project) }
5311  end
5312
5313  describe '#shared_runner_build?' do
5314    context 'when build does not have a runner assigned' do
5315      it 'is not a shared runner build' do
5316        expect(build.runner).to be_nil
5317
5318        expect(build).not_to be_shared_runner_build
5319      end
5320    end
5321
5322    context 'when build has a project runner assigned' do
5323      before do
5324        build.runner = create(:ci_runner, :project)
5325      end
5326
5327      it 'is not a shared runner build' do
5328        expect(build).not_to be_shared_runner_build
5329      end
5330    end
5331
5332    context 'when build has an instance runner assigned' do
5333      before do
5334        build.runner = create(:ci_runner, :instance_type)
5335      end
5336
5337      it 'is a shared runner build' do
5338        expect(build).to be_shared_runner_build
5339      end
5340    end
5341  end
5342
5343  describe '.with_project_and_metadata' do
5344    it 'does not join across databases' do
5345      with_cross_joins_prevented do
5346        ::Ci::Build.with_project_and_metadata.to_a
5347      end
5348    end
5349  end
5350
5351  describe '.without_coverage' do
5352    let!(:build_with_coverage) { create(:ci_build, pipeline: pipeline, coverage: 100.0) }
5353
5354    it 'returns builds without coverage values' do
5355      expect(described_class.without_coverage).to eq([build])
5356    end
5357  end
5358
5359  describe '.with_coverage_regex' do
5360    let!(:build_with_coverage_regex) { create(:ci_build, pipeline: pipeline, coverage_regex: '\d') }
5361
5362    it 'returns builds with coverage regex values' do
5363      expect(described_class.with_coverage_regex).to eq([build_with_coverage_regex])
5364    end
5365  end
5366
5367  describe '#ensure_trace_metadata!' do
5368    it 'delegates to Ci::BuildTraceMetadata' do
5369      expect(Ci::BuildTraceMetadata)
5370        .to receive(:find_or_upsert_for!)
5371        .with(build.id)
5372
5373      build.ensure_trace_metadata!
5374    end
5375  end
5376
5377  describe '#doom!' do
5378    subject { build.doom! }
5379
5380    let_it_be(:build) { create(:ci_build, :queued) }
5381
5382    it 'updates status and failure_reason', :aggregate_failures do
5383      subject
5384
5385      expect(build.status).to eq("failed")
5386      expect(build.failure_reason).to eq("data_integrity_failure")
5387    end
5388
5389    it 'drops associated pending build' do
5390      subject
5391
5392      expect(build.reload.queuing_entry).not_to be_present
5393    end
5394  end
5395
5396  it 'does not generate cross DB queries when a record is created via FactoryBot' do
5397    with_cross_database_modification_prevented do
5398      create(:ci_build)
5399    end
5400  end
5401
5402  describe '#runner_features' do
5403    subject do
5404      build.save!
5405      build.cancel_gracefully?
5406    end
5407
5408    let_it_be(:build) { create(:ci_build, pipeline: pipeline) }
5409
5410    it 'cannot cancel gracefully' do
5411      expect(subject).to be false
5412    end
5413
5414    it 'can cancel gracefully' do
5415      build.set_cancel_gracefully
5416
5417      expect(subject).to be true
5418    end
5419  end
5420
5421  it_behaves_like 'it has loose foreign keys' do
5422    let(:factory_name) { :ci_build }
5423  end
5424
5425  it_behaves_like 'cleanup by a loose foreign key' do
5426    let!(:model) { create(:ci_build, user: create(:user)) }
5427    let!(:parent) { model.user }
5428  end
5429end
5430