diff options
Diffstat (limited to 'spec/models/ci')
-rw-r--r-- | spec/models/ci/bridge_spec.rb | 26 | ||||
-rw-r--r-- | spec/models/ci/build_dependencies_spec.rb | 4 | ||||
-rw-r--r-- | spec/models/ci/build_spec.rb | 136 | ||||
-rw-r--r-- | spec/models/ci/job_artifact_spec.rb | 13 | ||||
-rw-r--r-- | spec/models/ci/namespace_mirror_spec.rb | 47 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 73 | ||||
-rw-r--r-- | spec/models/ci/processable_spec.rb | 94 | ||||
-rw-r--r-- | spec/models/ci/runner_spec.rb | 8 | ||||
-rw-r--r-- | spec/models/ci/secure_file_spec.rb | 32 |
9 files changed, 306 insertions, 127 deletions
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 7c3c02a5ab7..5ee560c4925 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -30,6 +30,12 @@ RSpec.describe Ci::Bridge do expect(bridge).to have_one(:downstream_pipeline) end + describe '#retryable?' do + it 'returns false' do + expect(bridge.retryable?).to eq(false) + end + end + describe '#tags' do it 'only has a bridge tag' do expect(bridge.tags).to eq [:bridge] @@ -282,6 +288,26 @@ RSpec.describe Ci::Bridge do ) end end + + context 'when the pipeline runs from a pipeline schedule' do + let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project ) } + let(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) } + + let(:options) do + { trigger: { project: 'my/project', forward: { pipeline_variables: true } } } + end + + before do + pipeline_schedule.variables.create!(key: 'schedule_var_key', value: 'schedule var value') + end + + it 'adds the schedule variable' do + expect(bridge.downstream_variables).to contain_exactly( + { key: 'BRIDGE', value: 'cross' }, + { key: 'schedule_var_key', value: 'schedule var value' } + ) + end + end end end diff --git a/spec/models/ci/build_dependencies_spec.rb b/spec/models/ci/build_dependencies_spec.rb index cd330324840..91048cae064 100644 --- a/spec/models/ci/build_dependencies_spec.rb +++ b/spec/models/ci/build_dependencies_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Ci::BuildDependencies do end let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') } - let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } + let!(:rspec_test) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') } let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') } @@ -48,7 +48,7 @@ RSpec.describe Ci::BuildDependencies do project.add_developer(user) end - let!(:retried_job) { Ci::Build.retry(rspec_test, user) } + let!(:retried_job) { Ci::RetryJobService.new(rspec_test.project, user).execute(rspec_test)[:job] } it 'contains the retried job instead of the original one' do is_expected.to contain_exactly(build, retried_job, rubocop_test) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 240b932638a..fd87a388442 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1585,6 +1585,31 @@ RSpec.describe Ci::Build do it { is_expected.to eq('review/x') } end + + context 'when environment name uses a nested variable' do + let(:yaml_variables) do + [ + { key: 'ENVIRONMENT_NAME', value: '${CI_COMMIT_REF_NAME}' } + ] + end + + let(:build) do + create(:ci_build, + ref: 'master', + yaml_variables: yaml_variables, + environment: 'review/$ENVIRONMENT_NAME') + end + + it { is_expected.to eq('review/master') } + + context 'when the FF ci_expand_environment_name_and_url is disabled' do + before do + stub_feature_flags(ci_expand_environment_name_and_url: false) + end + + it { is_expected.to eq('review/${CI_COMMIT_REF_NAME}') } + end + end end describe '#expanded_kubernetes_namespace' do @@ -1951,90 +1976,6 @@ RSpec.describe Ci::Build do end end - describe '#retryable?' do - subject { build } - - context 'when build is retryable' do - context 'when build is successful' do - before do - build.success! - end - - it { is_expected.to be_retryable } - end - - context 'when build is failed' do - before do - build.drop! - end - - it { is_expected.to be_retryable } - end - - context 'when build is canceled' do - before do - build.cancel! - end - - it { is_expected.to be_retryable } - end - end - - context 'when build is not retryable' do - context 'when build is running' do - before do - build.run! - end - - it { is_expected.not_to be_retryable } - end - - context 'when build is skipped' do - before do - build.skip! - end - - it { is_expected.not_to be_retryable } - end - - context 'when build is degenerated' do - before do - build.degenerate! - end - - it { is_expected.not_to be_retryable } - end - - context 'when a canceled build has been retried already' do - before do - project.add_developer(user) - build.cancel! - described_class.retry(build, user) - end - - it { is_expected.not_to be_retryable } - end - - context 'when deployment is rejected' do - before do - build.drop!(:deployment_rejected) - end - - it { is_expected.not_to be_retryable } - end - - context 'when build is waiting for deployment approval' do - subject { build_stubbed(:ci_build, :manual, environment: 'production') } - - before do - create(:deployment, :blocked, deployable: subject) - end - - it { is_expected.not_to be_retryable } - end - end - end - describe '#action?' do before do build.update!(when: value) @@ -2308,7 +2249,7 @@ RSpec.describe Ci::Build do describe '#options' do let(:options) do { - image: "ruby:2.7", + image: "image:1.0", services: ["postgres"], script: ["ls -a"] } @@ -2319,7 +2260,7 @@ RSpec.describe Ci::Build do end it 'allows to access with symbolized keys' do - expect(build.options[:image]).to eq('ruby:2.7') + expect(build.options[:image]).to eq('image:1.0') end it 'rejects access with string keys' do @@ -2358,24 +2299,12 @@ RSpec.describe Ci::Build do end context 'when build is retried' do - let!(:new_build) { described_class.retry(build, user) } + let!(:new_build) { Ci::RetryJobService.new(project, user).execute(build)[:job] } it 'does not return any of them' do is_expected.not_to include(build, new_build) end end - - context 'when other build is retried' do - let!(:retried_build) { described_class.retry(other_build, user) } - - before do - retried_build.success - end - - it 'returns a retried build' do - is_expected.to contain_exactly(retried_build) - end - end end describe '#other_scheduled_actions' do @@ -3962,8 +3891,9 @@ RSpec.describe Ci::Build do subject { create(:ci_build, :running, options: { script: ["ls -al"], retry: 3 }, project: project, user: user) } it 'retries build and assigns the same user to it' do - expect(described_class).to receive(:retry) - .with(subject, user) + expect_next_instance_of(::Ci::RetryJobService) do |service| + expect(service).to receive(:execute).with(subject) + end subject.drop! end @@ -3977,10 +3907,10 @@ RSpec.describe Ci::Build do end context 'when retry service raises Gitlab::Access::AccessDeniedError exception' do - let(:retry_service) { Ci::RetryBuildService.new(subject.project, subject.user) } + let(:retry_service) { Ci::RetryJobService.new(subject.project, subject.user) } before do - allow_any_instance_of(Ci::RetryBuildService) + allow_any_instance_of(Ci::RetryJobService) .to receive(:execute) .with(subject) .and_raise(Gitlab::Access::AccessDeniedError) diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index bd0397e0396..24c318d0218 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -279,6 +279,15 @@ RSpec.describe Ci::JobArtifact do end end + describe '.order_expired_asc' do + let_it_be(:first_artifact) { create(:ci_job_artifact, expire_at: 2.days.ago) } + let_it_be(:second_artifact) { create(:ci_job_artifact, expire_at: 1.day.ago) } + + it 'returns ordered artifacts' do + expect(described_class.order_expired_asc).to eq([first_artifact, second_artifact]) + end + end + describe '.for_project' do it 'returns artifacts only for given project(s)', :aggregate_failures do artifact1 = create(:ci_job_artifact) @@ -700,10 +709,6 @@ RSpec.describe Ci::JobArtifact do MSG end - it_behaves_like 'it has loose foreign keys' do - let(:factory_name) { :ci_job_artifact } - end - context 'loose foreign key on ci_job_artifacts.project_id' do it_behaves_like 'cleanup by a loose foreign key' do let!(:parent) { create(:project) } diff --git a/spec/models/ci/namespace_mirror_spec.rb b/spec/models/ci/namespace_mirror_spec.rb index 38471f15849..9b4e86916b8 100644 --- a/spec/models/ci/namespace_mirror_spec.rb +++ b/spec/models/ci/namespace_mirror_spec.rb @@ -44,6 +44,53 @@ RSpec.describe Ci::NamespaceMirror do end end + describe '.contains_traversal_ids' do + let!(:other_group1) { create(:group) } + let!(:other_group2) { create(:group, parent: other_group1) } + let!(:other_group3) { create(:group, parent: other_group2) } + let!(:other_group4) { create(:group) } + + subject(:result) { described_class.contains_traversal_ids(all_traversal_ids) } + + context 'when passing a top-level group' do + let(:all_traversal_ids) do + [ + [other_group1.id] + ] + end + + it 'returns only itself and children of that group' do + expect(result.map(&:namespace)).to contain_exactly(other_group1, other_group2, other_group3) + end + end + + context 'when passing many levels of groups' do + let(:all_traversal_ids) do + [ + [other_group2.parent_id, other_group2.id], + [other_group3.parent_id, other_group3.id], + [other_group4.id] + ] + end + + it 'returns only the asked group' do + expect(result.map(&:namespace)).to contain_exactly(other_group2, other_group3, other_group4) + end + end + + context 'when passing invalid data ' do + let(:all_traversal_ids) do + [ + ["; UPDATE"] + ] + end + + it 'data is properly sanitised' do + expect(result.to_sql).to include "((traversal_ids[1])) IN (('; UPDATE'))" + end + end + end + describe '.by_namespace_id' do subject(:result) { described_class.by_namespace_id(group2.id) } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 294ec07ee3e..45b51d5bf44 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1146,6 +1146,50 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end end + + describe 'variable CI_GITLAB_FIPS_MODE' do + context 'when FIPS flag is enabled' do + before do + allow(Gitlab::FIPS).to receive(:enabled?).and_return(true) + end + + it "is included with value 'true'" do + expect(subject.to_hash).to include('CI_GITLAB_FIPS_MODE' => 'true') + end + end + + context 'when FIPS flag is disabled' do + before do + allow(Gitlab::FIPS).to receive(:enabled?).and_return(false) + end + + it 'is not included' do + expect(subject.to_hash).not_to have_key('CI_GITLAB_FIPS_MODE') + end + end + end + + context 'without a commit' do + let(:pipeline) { build(:ci_empty_pipeline, :created, sha: nil) } + + it 'does not expose commit variables' do + expect(subject.to_hash.keys) + .not_to include( + 'CI_COMMIT_SHA', + 'CI_COMMIT_SHORT_SHA', + 'CI_COMMIT_BEFORE_SHA', + 'CI_COMMIT_REF_NAME', + 'CI_COMMIT_REF_SLUG', + 'CI_COMMIT_BRANCH', + 'CI_COMMIT_TAG', + 'CI_COMMIT_MESSAGE', + 'CI_COMMIT_TITLE', + 'CI_COMMIT_DESCRIPTION', + 'CI_COMMIT_REF_PROTECTED', + 'CI_COMMIT_TIMESTAMP', + 'CI_COMMIT_AUTHOR') + end + end end describe '#protected_ref?' do @@ -1663,7 +1707,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do expect(upstream_pipeline.reload).to be_failed Sidekiq::Testing.inline! do - new_job = Ci::Build.retry(job, project.users.first) + new_job = Ci::RetryJobService.new(project, project.users.first).execute(job)[:job] expect(downstream_pipeline.reload).to be_running expect(upstream_pipeline.reload).to be_running @@ -1684,7 +1728,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do expect(upstream_pipeline.reload).to be_success Sidekiq::Testing.inline! do - new_job = Ci::Build.retry(job, project.users.first) + new_job = Ci::RetryJobService.new(project, project.users.first).execute(job)[:job] expect(downstream_pipeline.reload).to be_running expect(upstream_pipeline.reload).to be_running @@ -1715,7 +1759,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do expect(upstream_of_upstream_pipeline.reload).to be_failed Sidekiq::Testing.inline! do - new_job = Ci::Build.retry(job, project.users.first) + new_job = Ci::RetryJobService.new(project, project.users.first).execute(job)[:job] expect(downstream_pipeline.reload).to be_running expect(upstream_pipeline.reload).to be_running @@ -2583,8 +2627,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do build.drop project.add_developer(user) - - Ci::Build.retry(build, user) + ::Ci::RetryJobService.new(project, user).execute(build)[:job] end # We are changing a state: created > failed > running @@ -4688,7 +4731,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do project.add_developer(user) retried_build.cancel! - ::Ci::Build.retry(retried_build, user) + Ci::RetryJobService.new(project, user).execute(retried_build)[:job] end it 'does not include retried builds' do @@ -4714,6 +4757,24 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end + describe '#has_expired_test_reports?' do + subject { pipeline_with_test_report.has_expired_test_reports? } + + let(:pipeline_with_test_report) { create(:ci_pipeline, :with_test_reports) } + + context 'when artifacts are not expired' do + it { is_expected.to be_falsey } + end + + context 'when artifacts are expired' do + before do + pipeline_with_test_report.job_artifacts.first.update!(expire_at: Date.yesterday) + end + + it { is_expected.to be_truthy } + end + end + it_behaves_like 'it has loose foreign keys' do let(:factory_name) { :ci_pipeline } end diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb index ac1a8247aaa..71fef3c1b5b 100644 --- a/spec/models/ci/processable_spec.rb +++ b/spec/models/ci/processable_spec.rb @@ -14,6 +14,100 @@ RSpec.describe Ci::Processable do it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) } end + describe '#retryable' do + shared_examples_for 'retryable processable' do + context 'when processable is successful' do + before do + processable.success! + end + + it { is_expected.to be_retryable } + end + + context 'when processable is failed' do + before do + processable.drop! + end + + it { is_expected.to be_retryable } + end + + context 'when processable is canceled' do + before do + processable.cancel! + end + + it { is_expected.to be_retryable } + end + end + + shared_examples_for 'non-retryable processable' do + context 'when processable is skipped' do + before do + processable.skip! + end + + it { is_expected.not_to be_retryable } + end + + context 'when processable is degenerated' do + before do + processable.degenerate! + end + + it { is_expected.not_to be_retryable } + end + + context 'when a canceled processable has been retried already' do + before do + project.add_developer(create(:user)) + processable.cancel! + processable.update!(retried: true) + end + + it { is_expected.not_to be_retryable } + end + end + + context 'when the processable is a build' do + subject(:processable) { create(:ci_build, pipeline: pipeline) } + + context 'when the processable is retryable' do + it_behaves_like 'retryable processable' + + context 'when deployment is rejected' do + before do + processable.drop!(:deployment_rejected) + end + + it { is_expected.not_to be_retryable } + end + + context 'when build is waiting for deployment approval' do + subject { build_stubbed(:ci_build, :manual, environment: 'production') } + + before do + create(:deployment, :blocked, deployable: subject) + end + + it { is_expected.not_to be_retryable } + end + end + + context 'when the processable is non-retryable' do + it_behaves_like 'non-retryable processable' + + context 'when processable is running' do + before do + processable.run! + end + + it { is_expected.not_to be_retryable } + end + end + end + end + describe '#aggregated_needs_names' do let(:with_aggregated_needs) { pipeline.processables.select_with_aggregated_needs(project) } diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 42187c3ef99..05b7bc39a74 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -134,28 +134,28 @@ RSpec.describe Ci::Runner do end context 'cost factors validations' do - it 'dissalows :private_projects_minutes_cost_factor being nil' do + it 'disallows :private_projects_minutes_cost_factor being nil' do runner = build(:ci_runner, private_projects_minutes_cost_factor: nil) expect(runner).to be_invalid expect(runner.errors.full_messages).to include('Private projects minutes cost factor needs to be non-negative') end - it 'dissalows :public_projects_minutes_cost_factor being nil' do + it 'disallows :public_projects_minutes_cost_factor being nil' do runner = build(:ci_runner, public_projects_minutes_cost_factor: nil) expect(runner).to be_invalid expect(runner.errors.full_messages).to include('Public projects minutes cost factor needs to be non-negative') end - it 'dissalows :private_projects_minutes_cost_factor being negative' do + it 'disallows :private_projects_minutes_cost_factor being negative' do runner = build(:ci_runner, private_projects_minutes_cost_factor: -1.1) expect(runner).to be_invalid expect(runner.errors.full_messages).to include('Private projects minutes cost factor needs to be non-negative') end - it 'dissalows :public_projects_minutes_cost_factor being negative' do + it 'disallows :public_projects_minutes_cost_factor being negative' do runner = build(:ci_runner, public_projects_minutes_cost_factor: -2.2) expect(runner).to be_invalid diff --git a/spec/models/ci/secure_file_spec.rb b/spec/models/ci/secure_file_spec.rb index 4382385aaf5..f92db3fe8db 100644 --- a/spec/models/ci/secure_file_spec.rb +++ b/spec/models/ci/secure_file_spec.rb @@ -3,14 +3,14 @@ require 'spec_helper' RSpec.describe Ci::SecureFile do - let(:sample_file) { fixture_file('ci_secure_files/upload-keystore.jks') } - - subject { create(:ci_secure_file) } - before do stub_ci_secure_file_object_storage end + let(:sample_file) { fixture_file('ci_secure_files/upload-keystore.jks') } + + subject { create(:ci_secure_file, file: CarrierWaveStringFile.new(sample_file)) } + it { is_expected.to be_a FileStoreMounter } it { is_expected.to belong_to(:project).required } @@ -27,6 +27,26 @@ RSpec.describe Ci::SecureFile do it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_presence_of(:permissions) } it { is_expected.to validate_presence_of(:project_id) } + context 'unique filename' do + let_it_be(:project1) { create(:project) } + + it 'ensures the file name is unique within a given project' do + file1 = create(:ci_secure_file, project: project1, name: 'file1') + expect do + create(:ci_secure_file, project: project1, name: 'file1') + end.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Name has already been taken') + + expect(project1.secure_files.where(name: 'file1').count).to be 1 + expect(project1.secure_files.find_by(name: 'file1').id).to eq(file1.id) + end + + it 'allows duplicate file names in different projects' do + create(:ci_secure_file, project: project1) + expect do + create(:ci_secure_file, project: create(:project)) + end.not_to raise_error + end + end end describe '#permissions' do @@ -37,8 +57,6 @@ RSpec.describe Ci::SecureFile do describe '#checksum' do it 'computes SHA256 checksum on the file before encrypted' do - subject.file = CarrierWaveStringFile.new(sample_file) - subject.save! expect(subject.checksum).to eq(Digest::SHA256.hexdigest(sample_file)) end end @@ -51,8 +69,6 @@ RSpec.describe Ci::SecureFile do describe '#file' do it 'returns the saved file' do - subject.file = CarrierWaveStringFile.new(sample_file) - subject.save! expect(Base64.encode64(subject.file.read)).to eq(Base64.encode64(sample_file)) end end |