summaryrefslogtreecommitdiff
path: root/spec/models/ci
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/ci')
-rw-r--r--spec/models/ci/bridge_spec.rb6
-rw-r--r--spec/models/ci/build_metadata_spec.rb57
-rw-r--r--spec/models/ci/build_spec.rb152
-rw-r--r--spec/models/ci/build_trace_chunks/redis_spec.rb8
-rw-r--r--spec/models/ci/build_trace_spec.rb7
-rw-r--r--spec/models/ci/daily_build_group_report_result_spec.rb37
-rw-r--r--spec/models/ci/job_token/project_scope_link_spec.rb11
-rw-r--r--spec/models/ci/job_token/scope_spec.rb4
-rw-r--r--spec/models/ci/pipeline_metadata_spec.rb14
-rw-r--r--spec/models/ci/pipeline_spec.rb113
-rw-r--r--spec/models/ci/processable_spec.rb2
-rw-r--r--spec/models/ci/resource_group_spec.rb6
-rw-r--r--spec/models/ci/runner_spec.rb184
-rw-r--r--spec/models/ci/secure_file_spec.rb66
-rw-r--r--spec/models/ci/unit_test_spec.rb40
-rw-r--r--spec/models/ci/variable_spec.rb2
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