Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/ci')
-rw-r--r--spec/models/ci/build_report_result_spec.rb5
-rw-r--r--spec/models/ci/build_spec.rb228
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb11
-rw-r--r--spec/models/ci/daily_build_group_report_result_spec.rb12
-rw-r--r--spec/models/ci/freeze_period_spec.rb5
-rw-r--r--spec/models/ci/group_variable_spec.rb6
-rw-r--r--spec/models/ci/job_artifact_spec.rb27
-rw-r--r--spec/models/ci/job_token/project_scope_link_spec.rb5
-rw-r--r--spec/models/ci/namespace_mirror_spec.rb107
-rw-r--r--spec/models/ci/pending_build_spec.rb10
-rw-r--r--spec/models/ci/pipeline_artifact_spec.rb7
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb5
-rw-r--r--spec/models/ci/pipeline_spec.rb46
-rw-r--r--spec/models/ci/project_mirror_spec.rb34
-rw-r--r--spec/models/ci/resource_group_spec.rb5
-rw-r--r--spec/models/ci/runner_namespace_spec.rb6
-rw-r--r--spec/models/ci/runner_spec.rb112
-rw-r--r--spec/models/ci/running_build_spec.rb5
-rw-r--r--spec/models/ci/secure_file_spec.rb55
-rw-r--r--spec/models/ci/unit_test_spec.rb5
20 files changed, 560 insertions, 136 deletions
diff --git a/spec/models/ci/build_report_result_spec.rb b/spec/models/ci/build_report_result_spec.rb
index e78f602feef..3f53c6c1c0e 100644
--- a/spec/models/ci/build_report_result_spec.rb
+++ b/spec/models/ci/build_report_result_spec.rb
@@ -5,6 +5,11 @@ require 'spec_helper'
RSpec.describe Ci::BuildReportResult do
let(:build_report_result) { build(:ci_build_report_result, :with_junit_success) }
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_build_report_result, project: parent) }
+ end
+
describe 'associations' do
it { is_expected.to belong_to(:build) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index b9a12339e61..b8c5af5a911 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -565,6 +565,26 @@ RSpec.describe Ci::Build do
expect(build.reload.runtime_metadata).not_to be_present
end
end
+
+ context 'when a failure reason is provided' do
+ context 'when a failure reason is a symbol' do
+ it 'correctly sets a failure reason' do
+ build.drop!(:script_failure)
+
+ expect(build.failure_reason).to eq 'script_failure'
+ end
+ end
+
+ context 'when a failure reason is an object' do
+ it 'correctly sets a failure reason' do
+ reason = ::Gitlab::Ci::Build::Status::Reason.new(build, :script_failure)
+
+ build.drop!(reason)
+
+ expect(build.failure_reason).to eq 'script_failure'
+ end
+ end
+ end
end
describe '#schedulable?' do
@@ -2002,6 +2022,16 @@ RSpec.describe Ci::Build do
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
@@ -2064,6 +2094,31 @@ RSpec.describe Ci::Build do
end
describe 'build auto retry feature' do
+ context 'with deployment job' do
+ let(:build) do
+ create(:ci_build, :deploy_to_production, :with_deployment,
+ user: user, pipeline: pipeline, project: project)
+ end
+
+ before do
+ project.add_developer(user)
+ allow(build).to receive(:auto_retry_allowed?) { true }
+ end
+
+ it 'creates a deployment when a build is dropped' do
+ expect { build.drop!(:script_failure) }.to change { Deployment.count }.by(1)
+
+ retried_deployment = Deployment.last
+ expect(build.deployment.environment).to eq(retried_deployment.environment)
+ expect(build.deployment.ref).to eq(retried_deployment.ref)
+ expect(build.deployment.sha).to eq(retried_deployment.sha)
+ expect(build.deployment.tag).to eq(retried_deployment.tag)
+ expect(build.deployment.user).to eq(retried_deployment.user)
+ expect(build.deployment).to be_failed
+ expect(retried_deployment).to be_created
+ end
+ end
+
describe '#retries_count' do
subject { create(:ci_build, name: 'test', pipeline: pipeline) }
@@ -2152,6 +2207,28 @@ RSpec.describe Ci::Build do
end
end
+ describe '#auto_retry_expected?' do
+ subject { create(:ci_build, :failed) }
+
+ context 'when build is failed and auto retry is configured' do
+ before do
+ allow(subject)
+ .to receive(:auto_retry_allowed?)
+ .and_return(true)
+ end
+
+ it 'expects auto-retry to happen' do
+ expect(subject.auto_retry_expected?).to be true
+ end
+ end
+
+ context 'when build failed by auto retry is not configured' do
+ it 'does not expect auto-retry to happen' do
+ expect(subject.auto_retry_expected?).to be false
+ end
+ end
+ end
+
describe '#artifacts_file_for_type' do
let(:build) { create(:ci_build, :artifacts) }
let(:file_type) { :archive }
@@ -2443,6 +2520,16 @@ RSpec.describe Ci::Build do
it { is_expected.not_to be_playable }
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_playable }
+ end
end
describe 'project settings' do
@@ -2653,6 +2740,8 @@ RSpec.describe Ci::Build do
{ key: 'CI_DEPENDENCY_PROXY_USER', value: 'gitlab-ci-token', public: true, masked: false },
{ key: 'CI_DEPENDENCY_PROXY_PASSWORD', value: 'my-token', public: false, masked: true },
{ key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true },
+ { 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_STAGE', value: 'test', public: true, masked: false },
{ key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false },
@@ -2720,6 +2809,7 @@ RSpec.describe Ci::Build do
before do
allow(Gitlab::Ci::Jwt).to receive(:for_build).and_return('ci.job.jwt')
+ allow(Gitlab::Ci::JwtV2).to receive(:for_build).and_return('ci.job.jwtv2')
build.set_token('my-token')
build.yaml_variables = []
end
@@ -2771,6 +2861,8 @@ RSpec.describe Ci::Build do
let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } }
let(:dependency_proxy_var) { { key: 'dependency_proxy', value: 'value', public: true, masked: false } }
let(:job_jwt_var) { { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true } }
+ let(:job_jwt_var_v1) { { key: 'CI_JOB_JWT_V1', value: 'ci.job.jwt', public: false, masked: true } }
+ let(:job_jwt_var_v2) { { key: 'CI_JOB_JWT_V2', value: 'ci.job.jwtv2', public: false, masked: true } }
let(:job_dependency_var) { { key: 'job_dependency', value: 'value', public: true, masked: false } }
before do
@@ -2784,7 +2876,7 @@ RSpec.describe Ci::Build do
allow(build).to receive(:dependency_variables) { [job_dependency_var] }
allow(build).to receive(:dependency_proxy_variables) { [dependency_proxy_var] }
- allow(build.project)
+ allow(build.pipeline.project)
.to receive(:predefined_variables) { [project_pre_var] }
project.variables.create!(key: 'secret', value: 'value')
@@ -3084,7 +3176,7 @@ RSpec.describe Ci::Build do
context 'when the branch is protected' do
before do
- allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
+ allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end
it { is_expected.to include(protected_variable) }
@@ -3092,7 +3184,7 @@ RSpec.describe Ci::Build do
context 'when the tag is protected' do
before do
- allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
+ allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end
it { is_expected.to include(protected_variable) }
@@ -3131,7 +3223,7 @@ RSpec.describe Ci::Build do
context 'when the branch is protected' do
before do
- allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
+ allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end
it { is_expected.to include(protected_variable) }
@@ -3139,7 +3231,7 @@ RSpec.describe Ci::Build do
context 'when the tag is protected' do
before do
- allow(build.project).to receive(:protected_for?).with(ref).and_return(true)
+ allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end
it { is_expected.to include(protected_variable) }
@@ -3526,6 +3618,20 @@ RSpec.describe Ci::Build do
build.scoped_variables
end
+
+ context 'when variables builder is used' do
+ it 'returns the same variables' do
+ build.user = create(:user)
+
+ allow(build.pipeline).to receive(:use_variables_builder_definitions?).and_return(false)
+ legacy_variables = build.scoped_variables.to_hash
+
+ allow(build.pipeline).to receive(:use_variables_builder_definitions?).and_return(true)
+ new_variables = build.scoped_variables.to_hash
+
+ expect(new_variables).to eq(legacy_variables)
+ end
+ end
end
describe '#simple_variables_without_dependencies' do
@@ -3538,7 +3644,8 @@ RSpec.describe Ci::Build do
shared_examples "secret CI variables" do
context 'when ref is branch' do
- let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, ref: 'master', tag: false, pipeline: pipeline, project: project) }
context 'when ref is protected' do
before do
@@ -3554,7 +3661,8 @@ RSpec.describe Ci::Build do
end
context 'when ref is tag' do
- let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, pipeline: pipeline, project: project) }
context 'when ref is protected' do
before do
@@ -3652,8 +3760,6 @@ RSpec.describe Ci::Build do
.and_return(project_variables)
end
- it { is_expected.to eq(project_variables) }
-
context 'environment is nil' do
let(:environment) { nil }
@@ -3661,6 +3767,35 @@ RSpec.describe Ci::Build do
end
end
+ describe '#user_variables' do
+ subject { build.user_variables.to_hash }
+
+ context 'with user' do
+ let(:expected_variables) do
+ {
+ 'GITLAB_USER_EMAIL' => user.email,
+ 'GITLAB_USER_ID' => user.id.to_s,
+ 'GITLAB_USER_LOGIN' => user.username,
+ 'GITLAB_USER_NAME' => user.name
+ }
+ end
+
+ before do
+ build.user = user
+ end
+
+ it { is_expected.to eq(expected_variables) }
+ end
+
+ context 'without user' do
+ before do
+ expect(build).to receive(:user).and_return(nil)
+ end
+
+ it { is_expected.to be_empty }
+ end
+ end
+
describe '#any_unmet_prerequisites?' do
let(:build) { create(:ci_build, :created) }
@@ -3762,6 +3897,18 @@ RSpec.describe Ci::Build do
end
end
+ describe 'when the build is waiting for deployment approval' do
+ let(:build) { create(:ci_build, :manual, environment: 'production') }
+
+ before do
+ create(:deployment, :blocked, deployable: build)
+ end
+
+ it 'does not allow the build to be enqueued' do
+ expect { build.enqueue! }.to raise_error(StateMachines::InvalidTransition)
+ end
+ end
+
describe 'state transition: any => [:pending]' do
let(:build) { create(:ci_build, :created) }
@@ -5174,25 +5321,32 @@ RSpec.describe Ci::Build do
.to change { build.reload.failed? }
end
- it 'is executed inside a transaction' do
- expect(build).to receive(:drop!)
- .with(:unknown_failure)
- .and_raise(ActiveRecord::Rollback)
-
- expect(build).to receive(:conditionally_allow_failure!)
- .with(1)
- .and_call_original
-
- expect { drop_with_exit_code }
- .not_to change { build.reload.allow_failure }
- end
-
context 'when exit_code is nil' do
let(:exit_code) {}
it_behaves_like 'drops the build without changing allow_failure'
end
end
+
+ context 'when build is configured to be retried' do
+ let(:options) { { retry: 3 } }
+
+ context 'when there is an MR attached to the pipeline and a failed job todo for that MR' do
+ let!(:merge_request) { create(:merge_request, source_project: project, author: user, head_pipeline: pipeline) }
+ let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: user, target: merge_request) }
+
+ before do
+ build.update!(user: user)
+ project.add_developer(user)
+ end
+
+ it 'resolves the todo for the old failed build' do
+ expect do
+ drop_with_exit_code
+ end.to change { todo.reload.state }.from('pending').to('done')
+ end
+ end
+ end
end
describe '#exit_codes_defined?' do
@@ -5377,7 +5531,8 @@ RSpec.describe Ci::Build do
describe '#doom!' do
subject { build.doom! }
- let_it_be(:build) { create(:ci_build, :queued) }
+ let(:traits) { [] }
+ let(:build) { create(:ci_build, *traits, pipeline: pipeline) }
it 'updates status and failure_reason', :aggregate_failures do
subject
@@ -5386,10 +5541,33 @@ RSpec.describe Ci::Build do
expect(build.failure_reason).to eq("data_integrity_failure")
end
- it 'drops associated pending build' do
+ it 'logs a message' do
+ expect(Gitlab::AppLogger)
+ .to receive(:info)
+ .with(a_hash_including(message: 'Build doomed', class: build.class.name, build_id: build.id))
+ .and_call_original
+
subject
+ end
+
+ context 'with queued builds' do
+ let(:traits) { [:queued] }
+
+ it 'drops associated pending build' do
+ subject
- expect(build.reload.queuing_entry).not_to be_present
+ expect(build.reload.queuing_entry).not_to be_present
+ end
+ end
+
+ context 'with running builds' do
+ let(:traits) { [:picked] }
+
+ it 'drops associated runtime metadata' do
+ subject
+
+ expect(build.reload.runtime_metadata).not_to be_present
+ end
end
end
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index b6e128c317c..31c7c7a44bc 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -49,9 +49,8 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git
end
context 'FastDestroyAll' do
- let(:parent) { create(:project) }
- let(:pipeline) { create(:ci_pipeline, project: parent) }
- let!(:build) { create(:ci_build, :running, :trace_live, pipeline: pipeline, project: parent) }
+ let(:pipeline) { create(:ci_pipeline) }
+ let!(:build) { create(:ci_build, :running, :trace_live, pipeline: pipeline) }
let(:subjects) { build.trace_chunks }
describe 'Forbid #destroy and #destroy_all' do
@@ -84,7 +83,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git
expect(external_data_counter).to be > 0
expect(subjects.count).to be > 0
- expect { parent.destroy! }.not_to raise_error
+ expect { pipeline.destroy! }.not_to raise_error
expect(subjects.count).to eq(0)
expect(external_data_counter).to eq(0)
@@ -830,7 +829,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git
expect(described_class.count).to eq(3)
- subject
+ expect(subject).to be_truthy
expect(described_class.count).to eq(0)
@@ -852,7 +851,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git
context 'when project is destroyed' do
let(:subject) do
- project.destroy!
+ Projects::DestroyService.new(project, project.owner).execute
end
it_behaves_like 'deletes all build_trace_chunk and data in redis'
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 acc87c61036..43ba4c32477 100644
--- a/spec/models/ci/daily_build_group_report_result_spec.rb
+++ b/spec/models/ci/daily_build_group_report_result_spec.rb
@@ -164,4 +164,16 @@ RSpec.describe Ci::DailyBuildGroupReportResult do
end
end
end
+
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:model) { create(:ci_daily_build_group_report_result) }
+
+ let!(:parent) { model.group }
+ end
+
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:model) { create(:ci_daily_build_group_report_result) }
+
+ let!(:parent) { model.project }
+ end
end
diff --git a/spec/models/ci/freeze_period_spec.rb b/spec/models/ci/freeze_period_spec.rb
index f7f840c6696..b9bf1657e28 100644
--- a/spec/models/ci/freeze_period_spec.rb
+++ b/spec/models/ci/freeze_period_spec.rb
@@ -5,6 +5,11 @@ require 'spec_helper'
RSpec.describe Ci::FreezePeriod, type: :model do
subject { build(:ci_freeze_period) }
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_freeze_period, project: parent) }
+ end
+
let(:invalid_cron) { '0 0 0 * *' }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb
index f0eec549da7..4cb3b9eef0c 100644
--- a/spec/models/ci/group_variable_spec.rb
+++ b/spec/models/ci/group_variable_spec.rb
@@ -42,4 +42,10 @@ RSpec.describe Ci::GroupVariable do
end
end
end
+
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:model) { create(:ci_group_variable) }
+
+ let!(:parent) { model.group }
+ end
end
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 38061e0975f..2e8c41b410a 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -143,6 +143,17 @@ RSpec.describe Ci::JobArtifact do
end
end
+ describe '.erasable_file_types' do
+ subject { described_class.erasable_file_types }
+
+ it 'returns a list of erasable file types' do
+ all_types = described_class.file_types.keys
+ erasable_types = all_types - described_class::NON_ERASABLE_FILE_TYPES
+
+ expect(subject).to contain_exactly(*erasable_types)
+ end
+ end
+
describe '.erasable' do
subject { described_class.erasable }
@@ -534,20 +545,8 @@ RSpec.describe Ci::JobArtifact do
context 'when the artifact is a trace' do
let(:file_type) { :trace }
- context 'when ci_store_trace_outside_transaction is enabled' do
- it 'returns true' do
- expect(artifact.store_after_commit?).to be_truthy
- end
- end
-
- context 'when ci_store_trace_outside_transaction is disabled' do
- before do
- stub_feature_flags(ci_store_trace_outside_transaction: false)
- end
-
- it 'returns false' do
- expect(artifact.store_after_commit?).to be_falsey
- end
+ it 'returns true' do
+ expect(artifact.store_after_commit?).to be_truthy
end
end
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 dd6a75dfd89..8d7bb44bd16 100644
--- a/spec/models/ci/job_token/project_scope_link_spec.rb
+++ b/spec/models/ci/job_token/project_scope_link_spec.rb
@@ -9,6 +9,11 @@ RSpec.describe Ci::JobToken::ProjectScopeLink do
let_it_be(:project) { create(:project) }
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:user) }
+ let!(:model) { create(:ci_job_token_project_scope_link, added_by: parent) }
+ end
+
describe 'unique index' do
let!(:link) { create(:ci_job_token_project_scope_link) }
diff --git a/spec/models/ci/namespace_mirror_spec.rb b/spec/models/ci/namespace_mirror_spec.rb
index b4c71f51377..a9d916115fc 100644
--- a/spec/models/ci/namespace_mirror_spec.rb
+++ b/spec/models/ci/namespace_mirror_spec.rb
@@ -8,50 +8,91 @@ RSpec.describe Ci::NamespaceMirror do
let!(:group3) { create(:group, parent: group2) }
let!(:group4) { create(:group, parent: group3) }
- describe '.sync!' do
- let!(:event) { namespace.sync_events.create! }
+ before do
+ # refreshing ci mirrors according to the parent tree above
+ Namespaces::SyncEvent.find_each { |event| Ci::NamespaceMirror.sync!(event) }
+
+ # checking initial situation. we need to reload to reflect the changes of event sync
+ expect(group1.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id])
+ expect(group2.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id])
+ expect(group3.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id])
+ expect(group4.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id, group4.id])
+ end
+
+ context 'scopes' do
+ describe '.contains_namespace' do
+ let_it_be(:another_group) { create(:group) }
+
+ subject(:result) { described_class.contains_namespace(group2.id) }
+
+ it 'returns groups having group2.id in traversal_ids' do
+ expect(result.pluck(:namespace_id)).to contain_exactly(group2.id, group3.id, group4.id)
+ end
+ end
+
+ describe '.contains_any_of_namespaces' do
+ let!(:other_group1) { create(:group) }
+ let!(:other_group2) { create(:group, parent: other_group1) }
+ let!(:other_group3) { create(:group, parent: other_group2) }
+
+ subject(:result) { described_class.contains_any_of_namespaces([group2.id, other_group2.id]) }
+
+ it 'returns groups having group2.id in traversal_ids' do
+ expect(result.pluck(:namespace_id)).to contain_exactly(
+ group2.id, group3.id, group4.id, other_group2.id, other_group3.id
+ )
+ end
+ end
+
+ describe '.by_namespace_id' do
+ subject(:result) { described_class.by_namespace_id(group2.id) }
+
+ it 'returns namesapce mirrors of namespace id' do
+ expect(result).to contain_exactly(group2.ci_namespace_mirror)
+ end
+ end
+ end
- subject(:sync) { described_class.sync!(event.reload) }
+ describe '.sync!' do
+ subject(:sync) { described_class.sync!(Namespaces::SyncEvent.last) }
- context 'when namespace hierarchy does not exist in the first place' do
+ context 'when namespace mirror does not exist in the first place' do
let(:namespace) { group3 }
- it 'creates the hierarchy' do
- expect { sync }.to change { described_class.count }.from(0).to(1)
+ before do
+ namespace.ci_namespace_mirror.destroy!
+ namespace.sync_events.create!
+ end
+
+ it 'creates the mirror' do
+ expect { sync }.to change { described_class.count }.from(3).to(4)
- expect(namespace.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id])
+ expect(namespace.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id])
end
end
- context 'when namespace hierarchy does already exist' do
+ context 'when namespace mirror does already exist' do
let(:namespace) { group3 }
before do
- described_class.create!(namespace: namespace, traversal_ids: [namespace.id])
+ namespace.sync_events.create!
end
- it 'updates the hierarchy' do
+ it 'updates the mirror' do
expect { sync }.not_to change { described_class.count }
- expect(namespace.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id])
+ expect(namespace.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id])
end
end
- # I did not extract this context to a `shared_context` because the behavior will change
- # after implementing the TODO in `Ci::NamespaceMirror.sync!`
- context 'changing the middle namespace' do
+ shared_context 'changing the middle namespace' do
let(:namespace) { group2 }
before do
- described_class.create!(namespace_id: group1.id, traversal_ids: [group1.id])
- described_class.create!(namespace_id: group2.id, traversal_ids: [group1.id, group2.id])
- described_class.create!(namespace_id: group3.id, traversal_ids: [group1.id, group2.id, group3.id])
- described_class.create!(namespace_id: group4.id, traversal_ids: [group1.id, group2.id, group3.id, group4.id])
-
- group2.update!(parent: nil)
+ group2.update!(parent: nil) # creates a sync event
end
- it 'updates hierarchies for the base but wait for events for the children' do
+ it 'updates traversal_ids for the base and descendants' do
expect { sync }.not_to change { described_class.count }
expect(group1.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id])
@@ -61,6 +102,8 @@ RSpec.describe Ci::NamespaceMirror do
end
end
+ it_behaves_like 'changing the middle namespace'
+
context 'when the FFs sync_traversal_ids, use_traversal_ids and use_traversal_ids_for_ancestors are disabled' do
before do
stub_feature_flags(sync_traversal_ids: false,
@@ -68,27 +111,7 @@ RSpec.describe Ci::NamespaceMirror do
use_traversal_ids_for_ancestors: false)
end
- context 'changing the middle namespace' do
- let(:namespace) { group2 }
-
- before do
- described_class.create!(namespace_id: group1.id, traversal_ids: [group1.id])
- described_class.create!(namespace_id: group2.id, traversal_ids: [group1.id, group2.id])
- described_class.create!(namespace_id: group3.id, traversal_ids: [group1.id, group2.id, group3.id])
- described_class.create!(namespace_id: group4.id, traversal_ids: [group1.id, group2.id, group3.id, group4.id])
-
- group2.update!(parent: nil)
- end
-
- it 'updates hierarchies for the base and descendants' do
- expect { sync }.not_to change { described_class.count }
-
- expect(group1.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id])
- expect(group2.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id])
- expect(group3.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id, group3.id])
- expect(group4.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id, group3.id, group4.id])
- end
- end
+ it_behaves_like 'changing the middle namespace'
end
end
end
diff --git a/spec/models/ci/pending_build_spec.rb b/spec/models/ci/pending_build_spec.rb
index abf0fb443bb..5692444339f 100644
--- a/spec/models/ci/pending_build_spec.rb
+++ b/spec/models/ci/pending_build_spec.rb
@@ -223,4 +223,14 @@ RSpec.describe Ci::PendingBuild do
end
end
end
+
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:namespace) }
+ let!(:model) { create(:ci_pending_build, namespace: parent) }
+ end
+
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_pending_build, project: parent) }
+ end
end
diff --git a/spec/models/ci/pipeline_artifact_spec.rb b/spec/models/ci/pipeline_artifact_spec.rb
index f65483d2290..801505f0231 100644
--- a/spec/models/ci/pipeline_artifact_spec.rb
+++ b/spec/models/ci/pipeline_artifact_spec.rb
@@ -215,4 +215,11 @@ RSpec.describe Ci::PipelineArtifact, type: :model do
end
end
end
+
+ context 'loose foreign key on ci_pipeline_artifacts.project_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_pipeline_artifact, project: parent) }
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index fee74f8f674..0f1cb721e95 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -23,6 +23,11 @@ RSpec.describe Ci::PipelineSchedule do
subject { build(:ci_pipeline_schedule, project: project) }
end
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:user) }
+ let!(:model) { create(:ci_pipeline_schedule, owner: parent) }
+ end
+
describe 'validations' do
it 'does not allow invalid cron patterns' do
pipeline_schedule = build(:ci_pipeline_schedule, cron: '0 0 0 * *')
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index fd9970699d7..90f56c1e0a4 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -31,6 +31,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
it { is_expected.to have_many(:statuses_order_id_desc) }
it { is_expected.to have_many(:bridges) }
it { is_expected.to have_many(:job_artifacts).through(:builds) }
+ it { is_expected.to have_many(:build_trace_chunks).through(:builds) }
it { is_expected.to have_many(:auto_canceled_pipelines) }
it { is_expected.to have_many(:auto_canceled_jobs) }
it { is_expected.to have_many(:sourced_pipelines) }
@@ -1516,30 +1517,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
describe 'pipeline caching' do
- context 'when expire_job_and_pipeline_cache_synchronously is enabled' do
- before do
- stub_feature_flags(expire_job_and_pipeline_cache_synchronously: true)
- end
-
- it 'executes Ci::ExpirePipelineCacheService' do
- expect_next_instance_of(Ci::ExpirePipelineCacheService) do |service|
- expect(service).to receive(:execute).with(pipeline)
- end
-
- pipeline.cancel
+ it 'executes Ci::ExpirePipelineCacheService' do
+ expect_next_instance_of(Ci::ExpirePipelineCacheService) do |service|
+ expect(service).to receive(:execute).with(pipeline)
end
- end
-
- context 'when expire_job_and_pipeline_cache_synchronously is disabled' do
- before do
- stub_feature_flags(expire_job_and_pipeline_cache_synchronously: false)
- end
-
- it 'performs ExpirePipelinesCacheWorker' do
- expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id)
- pipeline.cancel
- end
+ pipeline.cancel
end
end
@@ -4677,4 +4660,23 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let!(:model) { create(:ci_pipeline, user: create(:user)) }
let!(:parent) { model.user }
end
+
+ describe 'tags count' do
+ let_it_be_with_refind(:pipeline) do
+ create(:ci_empty_pipeline, project: project)
+ end
+
+ it { expect(pipeline.tags_count).to eq(0) }
+ it { expect(pipeline.distinct_tags_count).to eq(0) }
+
+ context 'with builds' do
+ before do
+ create(:ci_build, pipeline: pipeline, tag_list: %w[a b])
+ create(:ci_build, pipeline: pipeline, tag_list: %w[b c])
+ end
+
+ it { expect(pipeline.tags_count).to eq(4) }
+ it { expect(pipeline.distinct_tags_count).to eq(3) }
+ end
+ end
end
diff --git a/spec/models/ci/project_mirror_spec.rb b/spec/models/ci/project_mirror_spec.rb
index 199285b036c..5ef520b4230 100644
--- a/spec/models/ci/project_mirror_spec.rb
+++ b/spec/models/ci/project_mirror_spec.rb
@@ -8,12 +8,36 @@ RSpec.describe Ci::ProjectMirror do
let!(:project) { create(:project, namespace: group2) }
+ context 'scopes' do
+ let_it_be(:another_project) { create(:project, namespace: group1) }
+
+ describe '.by_project_id' do
+ subject(:result) { described_class.by_project_id(project.id) }
+
+ it 'returns project mirrors of project' do
+ expect(result.pluck(:project_id)).to contain_exactly(project.id)
+ end
+ end
+
+ describe '.by_namespace_id' do
+ subject(:result) { described_class.by_namespace_id(group2.id) }
+
+ it 'returns project mirrors of namespace id' do
+ expect(result).to contain_exactly(project.ci_project_mirror)
+ end
+ end
+ end
+
describe '.sync!' do
let!(:event) { Projects::SyncEvent.create!(project: project) }
- subject(:sync) { described_class.sync!(event.reload) }
+ subject(:sync) { described_class.sync!(event) }
+
+ context 'when project mirror does not exist in the first place' do
+ before do
+ project.ci_project_mirror.destroy!
+ end
- context 'when project hierarchy does not exist in the first place' do
it 'creates a ci_projects record' do
expect { sync }.to change { described_class.count }.from(0).to(1)
@@ -21,11 +45,7 @@ RSpec.describe Ci::ProjectMirror do
end
end
- context 'when project hierarchy does already exist' do
- before do
- described_class.create!(project_id: project.id, namespace_id: group1.id)
- end
-
+ context 'when project mirror does already exist' do
it 'updates the related ci_projects record' do
expect { sync }.not_to change { described_class.count }
diff --git a/spec/models/ci/resource_group_spec.rb b/spec/models/ci/resource_group_spec.rb
index aae16157fbf..76e74f3193c 100644
--- a/spec/models/ci/resource_group_spec.rb
+++ b/spec/models/ci/resource_group_spec.rb
@@ -3,6 +3,11 @@
require 'spec_helper'
RSpec.describe Ci::ResourceGroup do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_resource_group, project: parent) }
+ end
+
describe 'validation' do
it 'valids when key includes allowed character' do
resource_group = build(:ci_resource_group, key: 'test')
diff --git a/spec/models/ci/runner_namespace_spec.rb b/spec/models/ci/runner_namespace_spec.rb
index 41d805adb9f..2d1fe11147c 100644
--- a/spec/models/ci/runner_namespace_spec.rb
+++ b/spec/models/ci/runner_namespace_spec.rb
@@ -6,4 +6,10 @@ RSpec.describe Ci::RunnerNamespace do
it_behaves_like 'includes Limitable concern' do
subject { build(:ci_runner_namespace, group: create(:group, :nested), runner: create(:ci_runner, :group)) }
end
+
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:model) { create(:ci_runner_namespace) }
+
+ let!(:parent) { model.namespace }
+ end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 5142f70fa2c..6830a8daa3b 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -48,7 +48,7 @@ RSpec.describe Ci::Runner do
let(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'disallows assigning group if already assigned to a group' do
- runner.runner_namespaces << build(:ci_runner_namespace)
+ runner.runner_namespaces << create(:ci_runner_namespace)
expect(runner).not_to be_valid
expect(runner.errors.full_messages).to include('Runner needs to be assigned to exactly one group')
@@ -203,28 +203,56 @@ RSpec.describe Ci::Runner do
end
end
- describe '.belonging_to_parent_group_of_project' do
- let(:project) { create(:project, group: group) }
- let(:group) { create(:group) }
- let(:runner) { create(:ci_runner, :group, groups: [group]) }
- let!(:unrelated_group) { create(:group) }
- let!(:unrelated_project) { create(:project, group: unrelated_group) }
- let!(:unrelated_runner) { create(:ci_runner, :group, groups: [unrelated_group]) }
+ shared_examples '.belonging_to_parent_group_of_project' do
+ let!(:group1) { create(:group) }
+ let!(:project1) { create(:project, group: group1) }
+ let!(:runner1) { create(:ci_runner, :group, groups: [group1]) }
+
+ let!(:group2) { create(:group) }
+ let!(:project2) { create(:project, group: group2) }
+ let!(:runner2) { create(:ci_runner, :group, groups: [group2]) }
+
+ let(:project_id) { project1.id }
+
+ subject(:result) { described_class.belonging_to_parent_group_of_project(project_id) }
it 'returns the specific group runner' do
- expect(described_class.belonging_to_parent_group_of_project(project.id)).to contain_exactly(runner)
+ expect(result).to contain_exactly(runner1)
end
- context 'with a parent group with a runner' do
- let(:runner) { create(:ci_runner, :group, groups: [parent_group]) }
- let(:project) { create(:project, group: group) }
- let(:group) { create(:group, parent: parent_group) }
- let(:parent_group) { create(:group) }
+ context 'with a parent group with a runner', :sidekiq_inline do
+ before do
+ group1.update!(parent: group2)
+ end
- it 'returns the group runner from the parent group' do
- expect(described_class.belonging_to_parent_group_of_project(project.id)).to contain_exactly(runner)
+ it 'returns the group runner from the group and the parent group' do
+ expect(result).to contain_exactly(runner1, runner2)
end
end
+
+ context 'with multiple project ids' do
+ let(:project_id) { [project1.id, project2.id] }
+
+ it 'raises ArgumentError' do
+ expect { result }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context 'when use_traversal_ids* are enabled' do
+ it_behaves_like '.belonging_to_parent_group_of_project'
+ end
+
+ context 'when use_traversal_ids* are disabled' do
+ before do
+ stub_feature_flags(
+ use_traversal_ids: false,
+ use_traversal_ids_for_ancestors: false,
+ use_traversal_ids_for_ancestor_scopes: false
+ )
+ end
+
+ it_behaves_like '.belonging_to_parent_group_of_project'
end
describe '.owned_or_instance_wide' do
@@ -1358,7 +1386,7 @@ RSpec.describe Ci::Runner do
it { is_expected.to eq(contacted_at_stored) }
end
- describe '.belonging_to_group' do
+ describe '.legacy_belonging_to_group' do
shared_examples 'returns group runners' do
it 'returns the specific group runner' do
group = create(:group)
@@ -1366,7 +1394,7 @@ RSpec.describe Ci::Runner do
unrelated_group = create(:group)
create(:ci_runner, :group, groups: [unrelated_group])
- expect(described_class.belonging_to_group(group.id)).to contain_exactly(runner)
+ expect(described_class.legacy_belonging_to_group(group.id)).to contain_exactly(runner)
end
context 'runner belonging to parent group' do
@@ -1376,13 +1404,13 @@ RSpec.describe Ci::Runner do
context 'when include_parent option is passed' do
it 'returns the group runner from the parent group' do
- expect(described_class.belonging_to_group(group.id, include_ancestors: true)).to contain_exactly(parent_runner)
+ expect(described_class.legacy_belonging_to_group(group.id, include_ancestors: true)).to contain_exactly(parent_runner)
end
end
context 'when include_parent option is not passed' do
it 'does not return the group runner from the parent group' do
- expect(described_class.belonging_to_group(group.id)).to be_empty
+ expect(described_class.legacy_belonging_to_group(group.id)).to be_empty
end
end
end
@@ -1398,4 +1426,48 @@ RSpec.describe Ci::Runner do
it_behaves_like 'returns group runners'
end
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])
+
+ expect(described_class.belonging_to_group(group.id)).to contain_exactly(runner)
+ 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) }
+
+ 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)
+ 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
+ )
+ end
+ end
end
diff --git a/spec/models/ci/running_build_spec.rb b/spec/models/ci/running_build_spec.rb
index 629861e35b8..d2f74494308 100644
--- a/spec/models/ci/running_build_spec.rb
+++ b/spec/models/ci/running_build_spec.rb
@@ -49,4 +49,9 @@ RSpec.describe Ci::RunningBuild do
end
end
end
+
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_running_build, project: parent) }
+ end
end
diff --git a/spec/models/ci/secure_file_spec.rb b/spec/models/ci/secure_file_spec.rb
new file mode 100644
index 00000000000..ae57b63e7a4
--- /dev/null
+++ b/spec/models/ci/secure_file_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+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
+
+ it { is_expected.to be_a FileStoreMounter }
+
+ it { is_expected.to belong_to(:project).required }
+
+ it_behaves_like 'having unique enum values'
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:checksum) }
+ it { is_expected.to validate_presence_of(:file_store) }
+ 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) }
+ end
+
+ describe '#permissions' do
+ it 'defaults to read_only file permssions' do
+ expect(subject.permissions).to eq('read_only')
+ end
+ end
+
+ 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
+
+ describe '#checksum_algorithm' do
+ it 'returns the configured checksum_algorithm' do
+ expect(subject.checksum_algorithm).to eq('sha256')
+ end
+ end
+
+ 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
+end
diff --git a/spec/models/ci/unit_test_spec.rb b/spec/models/ci/unit_test_spec.rb
index 2207a362be3..556cf93c266 100644
--- a/spec/models/ci/unit_test_spec.rb
+++ b/spec/models/ci/unit_test_spec.rb
@@ -3,6 +3,11 @@
require 'spec_helper'
RSpec.describe Ci::UnitTest do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_unit_test, project: parent) }
+ end
+
describe 'relationships' do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:unit_test_failures) }