diff options
Diffstat (limited to 'spec/models/ci')
-rw-r--r-- | spec/models/ci/bridge_spec.rb | 6 | ||||
-rw-r--r-- | spec/models/ci/build_metadata_spec.rb | 57 | ||||
-rw-r--r-- | spec/models/ci/build_spec.rb | 152 | ||||
-rw-r--r-- | spec/models/ci/build_trace_chunks/redis_spec.rb | 8 | ||||
-rw-r--r-- | spec/models/ci/build_trace_spec.rb | 7 | ||||
-rw-r--r-- | spec/models/ci/daily_build_group_report_result_spec.rb | 37 | ||||
-rw-r--r-- | spec/models/ci/job_token/project_scope_link_spec.rb | 11 | ||||
-rw-r--r-- | spec/models/ci/job_token/scope_spec.rb | 4 | ||||
-rw-r--r-- | spec/models/ci/pipeline_metadata_spec.rb | 14 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 113 | ||||
-rw-r--r-- | spec/models/ci/processable_spec.rb | 2 | ||||
-rw-r--r-- | spec/models/ci/resource_group_spec.rb | 6 | ||||
-rw-r--r-- | spec/models/ci/runner_spec.rb | 184 | ||||
-rw-r--r-- | spec/models/ci/secure_file_spec.rb | 66 | ||||
-rw-r--r-- | spec/models/ci/unit_test_spec.rb | 40 | ||||
-rw-r--r-- | spec/models/ci/variable_spec.rb | 2 |
16 files changed, 564 insertions, 145 deletions
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 40c2d62c465..44a6bec0130 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -86,9 +86,9 @@ RSpec.describe Ci::Bridge do describe '#scoped_variables' do it 'returns a hash representing variables' do variables = %w[ - CI_JOB_NAME CI_JOB_STAGE CI_COMMIT_SHA CI_COMMIT_SHORT_SHA - CI_COMMIT_BEFORE_SHA CI_COMMIT_REF_NAME CI_COMMIT_REF_SLUG - CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH + CI_JOB_NAME CI_JOB_NAME_SLUG CI_JOB_STAGE CI_COMMIT_SHA + CI_COMMIT_SHORT_SHA CI_COMMIT_BEFORE_SHA CI_COMMIT_REF_NAME + CI_COMMIT_REF_SLUG CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_ROOT_NAMESPACE CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index e904463a5ca..16cff72db64 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -14,8 +14,8 @@ RSpec.describe Ci::BuildMetadata do status: 'success') end - let(:build) { create(:ci_build, pipeline: pipeline) } - let(:metadata) { build.metadata } + let(:job) { create(:ci_build, pipeline: pipeline) } + let(:metadata) { job.metadata } it_behaves_like 'having unique enum values' @@ -35,7 +35,7 @@ RSpec.describe Ci::BuildMetadata do context 'when project timeout is set' do context 'when runner is assigned to the job' do before do - build.update!(runner: runner) + job.update!(runner: runner) end context 'when runner timeout is not set' do @@ -59,13 +59,13 @@ RSpec.describe Ci::BuildMetadata do context 'when job timeout is set' do context 'when job timeout is higher than project timeout' do - let(:build) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 3000 }) } + let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 3000 }) } it_behaves_like 'sets timeout', 'job_timeout_source', 3000 end context 'when job timeout is lower than project timeout' do - let(:build) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 1000 }) } + let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 1000 }) } it_behaves_like 'sets timeout', 'job_timeout_source', 1000 end @@ -73,18 +73,18 @@ RSpec.describe Ci::BuildMetadata do context 'when both runner and job timeouts are set' do before do - build.update!(runner: runner) + job.update!(runner: runner) end context 'when job timeout is higher than runner timeout' do - let(:build) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 3000 }) } + let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 3000 }) } let(:runner) { create(:ci_runner, maximum_timeout: 2100) } it_behaves_like 'sets timeout', 'runner_timeout_source', 2100 end context 'when job timeout is lower than runner timeout' do - let(:build) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 1900 }) } + let(:job) { create(:ci_build, pipeline: pipeline, options: { job_timeout: 1900 }) } let(:runner) { create(:ci_runner, maximum_timeout: 2100) } it_behaves_like 'sets timeout', 'job_timeout_source', 1900 @@ -135,20 +135,51 @@ RSpec.describe Ci::BuildMetadata do describe 'set_cancel_gracefully' do it 'sets cancel_gracefully' do - build.set_cancel_gracefully + job.set_cancel_gracefully - expect(build.cancel_gracefully?).to be true + expect(job.cancel_gracefully?).to be true end it 'returns false' do - expect(build.cancel_gracefully?).to be false + expect(job.cancel_gracefully?).to be false end end context 'loose foreign key on ci_builds_metadata.project_id' do it_behaves_like 'cleanup by a loose foreign key' do - let!(:parent) { create(:project) } - let!(:model) { create(:ci_build_metadata, project: parent) } + let!(:parent) { project } + let!(:model) { metadata } + end + end + + describe 'partitioning' do + context 'with job' do + let(:status) { build(:commit_status, partition_id: 123) } + let(:metadata) { build(:ci_build_metadata, build: status) } + + it 'copies the partition_id from job' do + expect { metadata.valid? }.to change(metadata, :partition_id).to(123) + end + + context 'when it is already set' do + let(:metadata) { build(:ci_build_metadata, build: status, partition_id: 125) } + + it 'does not change the partition_id value' do + expect { metadata.valid? }.not_to change(metadata, :partition_id) + end + end + end + + context 'without job' do + subject(:metadata) do + build(:ci_build_metadata, build: nil) + end + + it { is_expected.to validate_presence_of(:partition_id) } + + it 'does not change the partition_id value' do + expect { metadata.valid? }.not_to change(metadata, :partition_id) + end end end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7ee381b29ea..9713734e97a 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -160,6 +160,42 @@ RSpec.describe Ci::Build do end end + describe '.with_erasable_artifacts' do + subject { described_class.with_erasable_artifacts } + + context 'when job does not have any artifacts' do + let!(:job) { create(:ci_build) } + + it 'does not return the job' do + is_expected.not_to include(job) + end + end + + ::Ci::JobArtifact.erasable_file_types.each do |type| + context "when job has a #{type} artifact" do + it 'returns the job' do + job = create(:ci_build) + create( + :ci_job_artifact, + file_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[type.to_sym], + file_type: type, + job: job + ) + + is_expected.to include(job) + end + end + end + + context 'when job has a non-erasable artifact' do + let!(:job) { create(:ci_build, :trace_artifact) } + + it 'does not return the job' do + is_expected.not_to include(job) + end + end + end + describe '.with_live_trace' do subject { described_class.with_live_trace } @@ -284,10 +320,10 @@ RSpec.describe Ci::Build do let(:artifact_scope) { Ci::JobArtifact.where(file_type: 'archive') } - let!(:build_1) { create(:ci_build, :artifacts) } - let!(:build_2) { create(:ci_build, :codequality_reports) } - let!(:build_3) { create(:ci_build, :test_reports) } - let!(:build_4) { create(:ci_build, :artifacts) } + let!(:build_1) { create(:ci_build, :artifacts, pipeline: pipeline) } + let!(:build_2) { create(:ci_build, :codequality_reports, pipeline: pipeline) } + let!(:build_3) { create(:ci_build, :test_reports, pipeline: pipeline) } + let!(:build_4) { create(:ci_build, :artifacts, pipeline: pipeline) } it 'returns artifacts matching the given scope' do expect(builds).to contain_exactly(build_1, build_4) @@ -596,15 +632,6 @@ RSpec.describe Ci::Build do it { expect(subject).to be_falsey } end - context 'when prevent_outdated_deployment_jobs FF is disabled' do - before do - stub_feature_flags(prevent_outdated_deployment_jobs: false) - expect(build.deployment).not_to receive(:rollback?) - end - - it { expect(subject).to be_falsey } - end - context 'when build can prevent rollback deployment' do before do expect(build.deployment).to receive(:older_than_last_successful_deployment?).and_return(true) @@ -2668,6 +2695,7 @@ RSpec.describe Ci::Build do { key: 'CI_JOB_JWT_V1', value: 'ci.job.jwt', public: false, masked: true }, { key: 'CI_JOB_JWT_V2', value: 'ci.job.jwtv2', public: false, masked: true }, { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false }, + { key: 'CI_JOB_NAME_SLUG', value: 'test', public: true, masked: false }, { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false }, { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false }, { key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false }, @@ -2780,6 +2808,14 @@ RSpec.describe Ci::Build do end end + context 'when the opt_in_jwt project setting is true' do + it 'does not include the JWT variables' do + project.ci_cd_settings.update!(opt_in_jwt: true) + + expect(subject.pluck(:key)).not_to include('CI_JOB_JWT', 'CI_JOB_JWT_V1', 'CI_JOB_JWT_V2') + end + end + describe 'variables ordering' do context 'when variables hierarchy is stubbed' do let(:build_pre_var) { { key: 'build', value: 'value', public: true, masked: false } } @@ -3069,8 +3105,24 @@ RSpec.describe Ci::Build do end context 'when build is for tag' do + let(:tag_name) { project.repository.tags.first.name } + let(:tag_message) { project.repository.tags.first.message } + + let!(:pipeline) do + create(:ci_pipeline, project: project, + sha: project.commit.id, + ref: tag_name, + status: 'success') + end + + let!(:build) { create(:ci_build, pipeline: pipeline, ref: tag_name) } + let(:tag_variable) do - { key: 'CI_COMMIT_TAG', value: 'master', public: true, masked: false } + { key: 'CI_COMMIT_TAG', value: tag_name, public: true, masked: false } + end + + let(:tag_message_variable) do + { key: 'CI_COMMIT_TAG_MESSAGE', value: tag_message, public: true, masked: false } end before do @@ -3081,7 +3133,7 @@ RSpec.describe Ci::Build do it do build.reload - expect(subject).to include(tag_variable) + expect(subject).to include(tag_variable, tag_message_variable) end end @@ -3474,6 +3526,49 @@ RSpec.describe Ci::Build do it { is_expected.to include(key: job_variable.key, value: job_variable.value, public: false, masked: false) } end + + context 'when ID tokens are defined on the build' do + before do + rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s + stub_application_setting(ci_jwt_signing_key: rsa_key) + build.metadata.update!(id_tokens: { + 'ID_TOKEN_1' => { id_token: { aud: 'developers' } }, + 'ID_TOKEN_2' => { id_token: { aud: 'maintainers' } } + }) + end + + subject(:runner_vars) { build.variables.to_runner_variables } + + it 'includes the ID token variables' do + expect(runner_vars).to include( + a_hash_including(key: 'ID_TOKEN_1', public: false, masked: true), + a_hash_including(key: 'ID_TOKEN_2', public: false, masked: true) + ) + + id_token_var_1 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_1' } + id_token_var_2 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_2' } + id_token_1 = JWT.decode(id_token_var_1[:value], nil, false).first + id_token_2 = JWT.decode(id_token_var_2[:value], nil, false).first + expect(id_token_1['aud']).to eq('developers') + expect(id_token_2['aud']).to eq('maintainers') + end + + context 'when a NoSigningKeyError is raised' do + it 'does not include the ID token variables' do + allow(::Gitlab::Ci::JwtV2).to receive(:for_build).and_raise(::Gitlab::Ci::Jwt::NoSigningKeyError) + + expect(runner_vars.map { |var| var[:key] }).not_to include('ID_TOKEN_1', 'ID_TOKEN_2') + end + end + + context 'when a RSAError is raised' do + it 'does not include the ID token variables' do + allow(::Gitlab::Ci::JwtV2).to receive(:for_build).and_raise(::OpenSSL::PKey::RSAError) + + expect(runner_vars.map { |var| var[:key] }).not_to include('ID_TOKEN_1', 'ID_TOKEN_2') + end + end + end end describe '#scoped_variables' do @@ -5171,10 +5266,11 @@ RSpec.describe Ci::Build do it { expect(matchers.size).to eq(2) } it 'groups build ids' do - expect(matchers.map(&:build_ids)).to match_array([ - [build_without_tags.id], - match_array([build_with_tags.id, other_build_with_tags.id]) - ]) + expect(matchers.map(&:build_ids)).to match_array( + [ + [build_without_tags.id], + match_array([build_with_tags.id, other_build_with_tags.id]) + ]) end it { expect(matchers.map(&:tag_list)).to match_array([[], %w[tag1 tag2]]) } @@ -5362,7 +5458,7 @@ RSpec.describe Ci::Build do end describe '#clone' do - let_it_be(:user) { FactoryBot.build(:user) } + let_it_be(:user) { create(:user) } context 'when given new job variables' do context 'when the cloned build has an action' do @@ -5371,10 +5467,11 @@ RSpec.describe Ci::Build do create(:ci_job_variable, job: build, key: 'TEST_KEY', value: 'old value') create(:ci_job_variable, job: build, key: 'OLD_KEY', value: 'i will not live for long') - new_build = build.clone(current_user: user, new_job_variables_attributes: [ - { key: 'TEST_KEY', value: 'new value' }, - { key: 'NEW_KEY', value: 'exciting new value' } - ]) + new_build = build.clone(current_user: user, new_job_variables_attributes: + [ + { key: 'TEST_KEY', value: 'new value' }, + { key: 'NEW_KEY', value: 'exciting new value' } + ]) new_build.save! expect(new_build.job_variables.count).to be(2) @@ -5388,9 +5485,10 @@ RSpec.describe Ci::Build do build = create(:ci_build) create(:ci_job_variable, job: build, key: 'TEST_KEY', value: 'old value') - new_build = build.clone(current_user: user, new_job_variables_attributes: [ - { key: 'TEST_KEY', value: 'new value' } - ]) + new_build = build.clone( + current_user: user, + new_job_variables_attributes: [{ key: 'TEST_KEY', value: 'new value' }] + ) new_build.save! expect(new_build.job_variables.count).to be(1) diff --git a/spec/models/ci/build_trace_chunks/redis_spec.rb b/spec/models/ci/build_trace_chunks/redis_spec.rb index c004887d609..0d8cda7b3d8 100644 --- a/spec/models/ci/build_trace_chunks/redis_spec.rb +++ b/spec/models/ci/build_trace_chunks/redis_spec.rb @@ -211,15 +211,15 @@ RSpec.describe Ci::BuildTraceChunks::Redis, :clean_gitlab_redis_shared_state do it 'deletes multiple data' do Gitlab::Redis::SharedState.with do |redis| - expect(redis.exists("gitlab:ci:trace:#{build.id}:chunks:0")).to be_truthy - expect(redis.exists("gitlab:ci:trace:#{build.id}:chunks:1")).to be_truthy + expect(redis.exists?("gitlab:ci:trace:#{build.id}:chunks:0")).to eq(true) + expect(redis.exists?("gitlab:ci:trace:#{build.id}:chunks:1")).to eq(true) end subject Gitlab::Redis::SharedState.with do |redis| - expect(redis.exists("gitlab:ci:trace:#{build.id}:chunks:0")).to be_falsy - expect(redis.exists("gitlab:ci:trace:#{build.id}:chunks:1")).to be_falsy + expect(redis.exists?("gitlab:ci:trace:#{build.id}:chunks:0")).to eq(false) + expect(redis.exists?("gitlab:ci:trace:#{build.id}:chunks:1")).to eq(false) end end end diff --git a/spec/models/ci/build_trace_spec.rb b/spec/models/ci/build_trace_spec.rb index bd24e8be1ac..f2df4874b84 100644 --- a/spec/models/ci/build_trace_spec.rb +++ b/spec/models/ci/build_trace_spec.rb @@ -28,9 +28,10 @@ RSpec.describe Ci::BuildTrace do it_behaves_like 'delegates methods' it 'returns formatted trace' do - expect(subject.lines).to eq([ - { offset: 0, content: [{ text: 'the-stream' }] } - ]) + expect(subject.lines).to eq( + [ + { offset: 0, content: [{ text: 'the-stream' }] } + ]) end context 'with invalid UTF-8 data' do diff --git a/spec/models/ci/daily_build_group_report_result_spec.rb b/spec/models/ci/daily_build_group_report_result_spec.rb index d0141a1469e..cd55817243f 100644 --- a/spec/models/ci/daily_build_group_report_result_spec.rb +++ b/spec/models/ci/daily_build_group_report_result_spec.rb @@ -41,24 +41,25 @@ RSpec.describe Ci::DailyBuildGroupReportResult do let!(:new_pipeline) { create(:ci_pipeline) } it 'creates or updates matching report results' do - described_class.upsert_reports([ - { - project_id: rspec_coverage.project_id, - ref_path: rspec_coverage.ref_path, - last_pipeline_id: new_pipeline.id, - date: rspec_coverage.date, - group_name: 'rspec', - data: { 'coverage' => 81.0 } - }, - { - project_id: rspec_coverage.project_id, - ref_path: rspec_coverage.ref_path, - last_pipeline_id: new_pipeline.id, - date: rspec_coverage.date, - group_name: 'karma', - data: { 'coverage' => 87.0 } - } - ]) + described_class.upsert_reports( + [ + { + project_id: rspec_coverage.project_id, + ref_path: rspec_coverage.ref_path, + last_pipeline_id: new_pipeline.id, + date: rspec_coverage.date, + group_name: 'rspec', + data: { 'coverage' => 81.0 } + }, + { + project_id: rspec_coverage.project_id, + ref_path: rspec_coverage.ref_path, + last_pipeline_id: new_pipeline.id, + date: rspec_coverage.date, + group_name: 'karma', + data: { 'coverage' => 87.0 } + } + ]) rspec_coverage.reload diff --git a/spec/models/ci/job_token/project_scope_link_spec.rb b/spec/models/ci/job_token/project_scope_link_spec.rb index c000a3e29f7..92ed86b55b2 100644 --- a/spec/models/ci/job_token/project_scope_link_spec.rb +++ b/spec/models/ci/job_token/project_scope_link_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Ci::JobToken::ProjectScopeLink do it { is_expected.to belong_to(:target_project) } it { is_expected.to belong_to(:added_by) } + let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project) } it_behaves_like 'cleanup by a loose foreign key' do @@ -89,16 +90,22 @@ RSpec.describe Ci::JobToken::ProjectScopeLink do end end + describe 'enums' do + let(:directions) { { outbound: 0, inbound: 1 } } + + it { is_expected.to define_enum_for(:direction).with_values(directions) } + end + context 'loose foreign key on ci_job_token_project_scope_links.source_project_id' do it_behaves_like 'cleanup by a loose foreign key' do - let!(:parent) { create(:project) } + let!(:parent) { create(:project, namespace: group) } let!(:model) { create(:ci_job_token_project_scope_link, source_project: parent) } end end context 'loose foreign key on ci_job_token_project_scope_links.target_project_id' do it_behaves_like 'cleanup by a loose foreign key' do - let!(:parent) { create(:project) } + let!(:parent) { create(:project, namespace: group) } let!(:model) { create(:ci_job_token_project_scope_link, target_project: parent) } end end diff --git a/spec/models/ci/job_token/scope_spec.rb b/spec/models/ci/job_token/scope_spec.rb index 4b95adf8476..1e3f6d044d2 100644 --- a/spec/models/ci/job_token/scope_spec.rb +++ b/spec/models/ci/job_token/scope_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::JobToken::Scope do - let_it_be(:project) { create(:project, ci_job_token_scope_enabled: true).tap(&:save!) } + let_it_be(:project) { create(:project, ci_outbound_job_token_scope_enabled: true).tap(&:save!) } let(:scope) { described_class.new(project) } @@ -53,7 +53,7 @@ RSpec.describe Ci::JobToken::Scope do context 'when project scope setting is disabled' do before do - project.ci_job_token_scope_enabled = false + project.ci_outbound_job_token_scope_enabled = false end it 'considers any project to be part of the scope' do diff --git a/spec/models/ci/pipeline_metadata_spec.rb b/spec/models/ci/pipeline_metadata_spec.rb new file mode 100644 index 00000000000..0704cbc8ec1 --- /dev/null +++ b/spec/models/ci/pipeline_metadata_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::PipelineMetadata do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:pipeline) } + + describe 'validations' do + it { is_expected.to validate_length_of(:title).is_at_least(1).is_at_most(255) } + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:pipeline) } + end +end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index ec03030a4b8..b2316949497 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -43,12 +43,14 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do it { is_expected.to have_one(:triggered_by_pipeline) } it { is_expected.to have_one(:source_job) } it { is_expected.to have_one(:pipeline_config) } + it { is_expected.to have_one(:pipeline_metadata) } it { is_expected.to respond_to :git_author_name } it { is_expected.to respond_to :git_author_email } it { is_expected.to respond_to :git_author_full_text } it { is_expected.to respond_to :short_sha } it { is_expected.to delegate_method(:full_path).to(:project).with_prefix } + it { is_expected.to delegate_method(:title).to(:pipeline_metadata).allow_nil } describe 'validations' do it { is_expected.to validate_presence_of(:sha) } @@ -2981,6 +2983,24 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } + it 'logs the event' do + allow(Gitlab::AppJsonLogger).to receive(:info) + + pipeline.cancel_running + + expect(Gitlab::AppJsonLogger) + .to have_received(:info) + .with( + a_hash_including( + event: 'pipeline_cancel_running', + pipeline_id: pipeline.id, + auto_canceled_by_pipeline_id: nil, + cascade_to_children: true, + execute_async: true + ) + ) + end + context 'when there is a running external job and a regular job' do before do create(:ci_build, :running, pipeline: pipeline) @@ -3813,7 +3833,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#upstream_root' do subject { pipeline.upstream_root } - let_it_be(:pipeline) { create(:ci_pipeline) } + let_it_be_with_refind(:pipeline) { create(:ci_pipeline) } context 'when pipeline is child of child pipeline' do let!(:root_ancestor) { create(:ci_pipeline) } @@ -4529,10 +4549,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end it 'returns accessibility report with collected data' do - expect(subject.urls.keys).to match_array([ - "https://pa11y.org/", - "https://about.gitlab.com/" - ]) + expect(subject.urls.keys).to match_array( + [ + "https://pa11y.org/", + "https://about.gitlab.com/" + ]) end context 'when builds are retried' do @@ -5316,19 +5337,18 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end - describe '#authorized_cluster_agents' do + describe '#cluster_agent_authorizations' do let(:pipeline) { create(:ci_empty_pipeline, :created) } - let(:agent) { instance_double(Clusters::Agent) } - let(:authorization) { instance_double(Clusters::Agents::GroupAuthorization, agent: agent) } + let(:authorization) { instance_double(Clusters::Agents::GroupAuthorization) } let(:finder) { double(execute: [authorization]) } - it 'retrieves agent records from the finder and caches the result' do + it 'retrieves authorization records from the finder and caches the result' do expect(Clusters::AgentAuthorizationsFinder).to receive(:new).once .with(pipeline.project) .and_return(finder) - expect(pipeline.authorized_cluster_agents).to contain_exactly(agent) - expect(pipeline.authorized_cluster_agents).to contain_exactly(agent) # cached + expect(pipeline.cluster_agent_authorizations).to contain_exactly(authorization) + expect(pipeline.cluster_agent_authorizations).to contain_exactly(authorization) # cached end end @@ -5486,7 +5506,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe 'partitioning' do - let(:pipeline) { build(:ci_pipeline) } + let(:pipeline) { build(:ci_pipeline, partition_id: nil) } before do allow(described_class).to receive(:current_partition_value) { 123 } @@ -5516,4 +5536,73 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end end + + describe '#notes=' do + context 'when notes already exist' do + it 'does not create duplicate notes', :aggregate_failures do + time = Time.zone.now + pipeline = create(:ci_pipeline, user: user, project: project) + note = Note.new( + note: 'note', + noteable_type: 'Commit', + noteable_id: pipeline.id, + commit_id: pipeline.id, + author_id: user.id, + project_id: pipeline.project_id, + created_at: time + ) + another_note = note.dup.tap { |note| note.note = 'another note' } + + expect(project.notes.for_commit_id(pipeline.sha).count).to eq(0) + + pipeline.notes = [note] + + expect(project.notes.for_commit_id(pipeline.sha).count).to eq(1) + + pipeline.notes = [note, note, another_note] + + expect(project.notes.for_commit_id(pipeline.sha).count).to eq(2) + expect(project.notes.for_commit_id(pipeline.sha).pluck(:note)).to contain_exactly(note.note, another_note.note) + end + end + end + + describe '#has_erasable_artifacts?' do + subject { pipeline.has_erasable_artifacts? } + + context 'when pipeline is not complete' do + let(:pipeline) { create(:ci_pipeline, :running, :with_job) } + + context 'and has erasable artifacts' do + before do + create(:ci_job_artifact, :archive, job: pipeline.builds.first) + end + + it { is_expected.to be_falsey } + end + end + + context 'when pipeline is complete' do + let(:pipeline) { create(:ci_pipeline, :success, :with_job) } + + context 'and has no artifacts' do + it { is_expected.to be_falsey } + end + + Ci::JobArtifact.erasable_file_types.each do |type| + context "and has an artifact of type #{type}" do + before do + create( + :ci_job_artifact, + file_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[type.to_sym], + file_type: type, + job: pipeline.builds.first + ) + end + + it { is_expected.to be_truthy } + end + end + end + end end diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb index 61e2864a518..a199111b1e3 100644 --- a/spec/models/ci/processable_spec.rb +++ b/spec/models/ci/processable_spec.rb @@ -177,7 +177,7 @@ RSpec.describe Ci::Processable do Ci::Build.attribute_names.map(&:to_sym) + Ci::Build.attribute_aliases.keys.map(&:to_sym) + Ci::Build.reflect_on_all_associations.map(&:name) + - [:tag_list, :needs_attributes, :job_variables_attributes] - + [:tag_list, :needs_attributes, :job_variables_attributes, :id_tokens] - # ToDo: Move EE accessors to ee/ ::Ci::Build.extra_accessors - [:dast_site_profiles_build, :dast_scanner_profiles_build] diff --git a/spec/models/ci/resource_group_spec.rb b/spec/models/ci/resource_group_spec.rb index 76e74f3193c..e8eccc233db 100644 --- a/spec/models/ci/resource_group_spec.rb +++ b/spec/models/ci/resource_group_spec.rb @@ -3,8 +3,10 @@ require 'spec_helper' RSpec.describe Ci::ResourceGroup do + let_it_be(:group) { create(:group) } + it_behaves_like 'cleanup by a loose foreign key' do - let!(:parent) { create(:project) } + let!(:parent) { create(:project, group: group) } let!(:model) { create(:ci_resource_group, project: parent) } end @@ -94,7 +96,7 @@ RSpec.describe Ci::ResourceGroup do describe '#upcoming_processables' do subject { resource_group.upcoming_processables } - let_it_be(:project) { create(:project, :repository) } + let_it_be(:project) { create(:project, :repository, group: group) } let_it_be(:pipeline_1) { create(:ci_pipeline, project: project) } let_it_be(:pipeline_2) { create(:ci_pipeline, project: project) } diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 181351222c1..13eb7086586 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -1186,7 +1186,7 @@ RSpec.describe Ci::Runner do end end - context 'Project-related queries' do + describe 'Project-related queries' do let_it_be(:project1) { create(:project) } let_it_be(:project2) { create(:project) } @@ -1206,14 +1206,14 @@ RSpec.describe Ci::Runner do end end - describe "belongs_to_one_project?" do + describe '#belongs_to_one_project?' do it "returns false if there are two projects runner is assigned to" do runner = create(:ci_runner, :project, projects: [project1, project2]) expect(runner.belongs_to_one_project?).to be_falsey end - it "returns true if there is only one project runner is assigned to" do + it 'returns true if there is only one project runner is assigned to' do runner = create(:ci_runner, :project, projects: [project1]) expect(runner.belongs_to_one_project?).to be_truthy @@ -1537,47 +1537,155 @@ RSpec.describe Ci::Runner do it { is_expected.to eq(contacted_at_stored) } end - describe '.belonging_to_group' do - it 'returns the specific group runner' do - group = create(:group) - runner = create(:ci_runner, :group, groups: [group]) - unrelated_group = create(:group) - create(:ci_runner, :group, groups: [unrelated_group]) + describe 'Group-related queries' do + # Groups + let_it_be(:top_level_group) { create(:group) } + let_it_be(:child_group) { create(:group, parent: top_level_group) } + let_it_be(:child_group2) { create(:group, parent: top_level_group) } + let_it_be(:other_top_level_group) { create(:group) } + + # Projects + let_it_be(:top_level_group_project) { create(:project, group: top_level_group) } + let_it_be(:child_group_project) { create(:project, group: child_group) } + let_it_be(:other_top_level_group_project) { create(:project, group: other_top_level_group) } - expect(described_class.belonging_to_group(group.id)).to contain_exactly(runner) + # Runners + let_it_be(:instance_runner) { create(:ci_runner, :instance) } + let_it_be(:top_level_group_runner) { create(:ci_runner, :group, groups: [top_level_group]) } + let_it_be(:child_group_runner) { create(:ci_runner, :group, groups: [child_group]) } + let_it_be(:child_group2_runner) { create(:ci_runner, :group, groups: [child_group2]) } + let_it_be(:other_top_level_group_runner) do + create(:ci_runner, :group, groups: [other_top_level_group]) end - end - describe '.belonging_to_group_and_ancestors' do - let_it_be(:parent_group) { create(:group) } - let_it_be(:parent_runner) { create(:ci_runner, :group, groups: [parent_group]) } - let_it_be(:group) { create(:group, parent: parent_group) } + let_it_be(:top_level_group_project_runner) do + create(:ci_runner, :project, projects: [top_level_group_project]) + end - it 'returns the group runner from the parent group' do - expect(described_class.belonging_to_group_and_ancestors(group.id)).to contain_exactly(parent_runner) + let_it_be(:child_group_project_runner) do + create(:ci_runner, :project, projects: [child_group_project]) end - end - describe '.belonging_to_group_or_project_descendants' do - it 'returns the specific group runners' do - group1 = create(:group) - group2 = create(:group, parent: group1) - group3 = create(:group) - - project1 = create(:project, namespace: group1) - project2 = create(:project, namespace: group2) - project3 = create(:project, namespace: group3) - - runner1 = create(:ci_runner, :group, groups: [group1]) - runner2 = create(:ci_runner, :group, groups: [group2]) - _runner3 = create(:ci_runner, :group, groups: [group3]) - runner4 = create(:ci_runner, :project, projects: [project1]) - runner5 = create(:ci_runner, :project, projects: [project2]) - _runner6 = create(:ci_runner, :project, projects: [project3]) - - expect(described_class.belonging_to_group_or_project_descendants(group1.id)).to contain_exactly( - runner1, runner2, runner4, runner5 - ) + let_it_be(:other_top_level_group_project_runner) do + create(:ci_runner, :project, projects: [other_top_level_group_project]) + end + + let_it_be(:shared_top_level_group_project_runner) do + create(:ci_runner, :project, projects: [top_level_group_project, child_group_project]) + end + + describe '.belonging_to_group' do + subject(:relation) { described_class.belonging_to_group(scope.id) } + + context 'with scope set to top_level_group' do + let(:scope) { top_level_group } + + it 'returns the group runners from the top_level_group' do + is_expected.to contain_exactly(top_level_group_runner) + end + end + + context 'with scope set to child_group' do + let(:scope) { child_group } + + it 'returns the group runners from the child_group' do + is_expected.to contain_exactly(child_group_runner) + end + end + end + + describe '.belonging_to_group_and_ancestors' do + subject(:relation) { described_class.belonging_to_group_and_ancestors(child_group.id) } + + it 'returns the group runners from the group and parent group' do + is_expected.to contain_exactly(child_group_runner, top_level_group_runner) + end + end + + describe '.belonging_to_group_or_project_descendants' do + subject(:relation) { described_class.belonging_to_group_or_project_descendants(scope.id) } + + context 'with scope set to top_level_group' do + let(:scope) { top_level_group } + + it 'returns the expected group and project runners without duplicates', :aggregate_failures do + expect(relation).to contain_exactly( + top_level_group_runner, + top_level_group_project_runner, + child_group_runner, + child_group_project_runner, + child_group2_runner, + shared_top_level_group_project_runner + ) + + # Ensure no duplicates are returned + expect(relation.distinct).to match_array(relation) + end + end + + context 'with scope set to child_group' do + let(:scope) { child_group } + + it 'returns the expected group and project runners without duplicates', :aggregate_failures do + expect(relation).to contain_exactly( + child_group_runner, + child_group_project_runner, + shared_top_level_group_project_runner + ) + + # Ensure no duplicates are returned + expect(relation.distinct).to match_array(relation) + end + end + end + + describe '.usable_from_scope' do + subject(:relation) { described_class.usable_from_scope(scope) } + + context 'with scope set to top_level_group' do + let(:scope) { top_level_group } + + it 'returns all runners usable from top_level_group without duplicates' do + expect(relation).to contain_exactly( + instance_runner, + top_level_group_runner, + top_level_group_project_runner, + child_group_runner, + child_group_project_runner, + child_group2_runner, + shared_top_level_group_project_runner + ) + + # Ensure no duplicates are returned + expect(relation.distinct).to match_array(relation) + end + end + + context 'with scope set to child_group' do + let(:scope) { child_group } + + it 'returns all runners usable from child_group' do + expect(relation).to contain_exactly( + instance_runner, + top_level_group_runner, + child_group_runner, + child_group_project_runner, + shared_top_level_group_project_runner + ) + end + end + + context 'with scope set to other_top_level_group' do + let(:scope) { other_top_level_group } + + it 'returns all runners usable from other_top_level_group' do + expect(relation).to contain_exactly( + instance_runner, + other_top_level_group_runner, + other_top_level_group_project_runner + ) + end + end end end diff --git a/spec/models/ci/secure_file_spec.rb b/spec/models/ci/secure_file_spec.rb index e47efff5dfd..20f64d40865 100644 --- a/spec/models/ci/secure_file_spec.rb +++ b/spec/models/ci/secure_file_spec.rb @@ -81,4 +81,70 @@ RSpec.describe Ci::SecureFile do expect(Base64.encode64(subject.file.read)).to eq(Base64.encode64(sample_file)) end end + + describe '#file_extension' do + it 'returns the extension for the file name' do + file = build(:ci_secure_file, name: 'file1.cer') + expect(file.file_extension).to eq('cer') + end + + it 'returns only the last part of the extension for the file name' do + file = build(:ci_secure_file, name: 'file1.tar.gz') + expect(file.file_extension).to eq('gz') + end + end + + describe '#metadata_parsable?' do + it 'returns true when the file extension has a supported parser' do + file = build(:ci_secure_file, name: 'file1.cer') + expect(file.metadata_parsable?).to be true + end + + it 'returns false when the file extension does not have a supported parser' do + file = build(:ci_secure_file, name: 'file1.foo') + expect(file.metadata_parsable?).to be false + end + end + + describe '#metadata_parser' do + it 'returns an instance of Gitlab::Ci::SecureFiles::Cer when a .cer file is supplied' do + file = build(:ci_secure_file, name: 'file1.cer') + expect(file.metadata_parser).to be_an_instance_of(Gitlab::Ci::SecureFiles::Cer) + end + + it 'returns an instance of Gitlab::Ci::SecureFiles::P12 when a .p12 file is supplied' do + file = build(:ci_secure_file, name: 'file1.p12') + expect(file.metadata_parser).to be_an_instance_of(Gitlab::Ci::SecureFiles::P12) + end + + it 'returns an instance of Gitlab::Ci::SecureFiles::MobileProvision when a .mobileprovision file is supplied' do + file = build(:ci_secure_file, name: 'file1.mobileprovision') + expect(file.metadata_parser).to be_an_instance_of(Gitlab::Ci::SecureFiles::MobileProvision) + end + + it 'returns nil when the file type is not supported by any parsers' do + file = build(:ci_secure_file, name: 'file1.foo') + expect(file.metadata_parser).to be nil + end + end + + describe '#update_metadata!' do + it 'assigns the expected metadata when a parsable file is supplied' do + file = create(:ci_secure_file, name: 'file1.cer', + file: CarrierWaveStringFile.new(fixture_file('ci_secure_files/sample.cer') )) + file.update_metadata! + + expect(file.expires_at).to eq(DateTime.parse('2022-04-26 19:20:40')) + expect(file.metadata['id']).to eq('33669367788748363528491290218354043267') + expect(file.metadata['issuer']['CN']).to eq('Apple Worldwide Developer Relations Certification Authority') + expect(file.metadata['subject']['OU']).to eq('N7SYAN8PX8') + end + + it 'logs an error when something goes wrong with the file parsing' do + corrupt_file = create(:ci_secure_file, name: 'file1.cer', file: CarrierWaveStringFile.new('11111111')) + message = 'Validation failed: Metadata must be a valid json schema - not enough data.' + expect(Gitlab::AppLogger).to receive(:error).with("Secure File Parser Failure (#{corrupt_file.id}): #{message}") + corrupt_file.update_metadata! + end + end end diff --git a/spec/models/ci/unit_test_spec.rb b/spec/models/ci/unit_test_spec.rb index 556cf93c266..b3180492a36 100644 --- a/spec/models/ci/unit_test_spec.rb +++ b/spec/models/ci/unit_test_spec.rb @@ -43,18 +43,19 @@ RSpec.describe Ci::UnitTest do result = described_class.find_or_create_by_batch(project, attrs) - expect(result).to match_array([ - have_attributes( - key_hash: existing_test.key_hash, - suite_name: 'rspec', - name: 'Math#sum adds numbers' - ), - have_attributes( - key_hash: new_key, - suite_name: 'jest', - name: 'Component works' - ) - ]) + expect(result).to match_array( + [ + have_attributes( + key_hash: existing_test.key_hash, + suite_name: 'rspec', + name: 'Math#sum adds numbers' + ), + have_attributes( + key_hash: new_key, + suite_name: 'jest', + name: 'Component works' + ) + ]) expect(result).to all(be_persisted) end @@ -77,13 +78,14 @@ RSpec.describe Ci::UnitTest do result = described_class.find_or_create_by_batch(project, attrs) - expect(result).to match_array([ - have_attributes( - key_hash: new_key, - suite_name: 'abc...', - name: 'abc...' - ) - ]) + expect(result).to match_array( + [ + have_attributes( + key_hash: new_key, + suite_name: 'abc...', + name: 'abc...' + ) + ]) expect(result).to all(be_persisted) end diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index f0af229ff2c..5f2b5971508 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -47,7 +47,7 @@ RSpec.describe Ci::Variable do context 'loose foreign key on ci_variables.project_id' do it_behaves_like 'cleanup by a loose foreign key' do - let!(:parent) { create(:project) } + let!(:parent) { create(:project, namespace: create(:group)) } let!(:model) { create(:ci_variable, project: parent) } end end |