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')
-rw-r--r--spec/models/alert_management/http_integration_spec.rb83
-rw-r--r--spec/models/analytics/cycle_analytics/project_stage_spec.rb2
-rw-r--r--spec/models/analytics/devops_adoption/segment_selection_spec.rb69
-rw-r--r--spec/models/analytics/instance_statistics/measurement_spec.rb77
-rw-r--r--spec/models/application_record_spec.rb11
-rw-r--r--spec/models/application_setting_spec.rb32
-rw-r--r--spec/models/authentication_event_spec.rb8
-rw-r--r--spec/models/broadcast_message_spec.rb6
-rw-r--r--spec/models/bulk_imports/tracker_spec.rb27
-rw-r--r--spec/models/ci/bridge_spec.rb60
-rw-r--r--spec/models/ci/build_spec.rb19
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb66
-rw-r--r--spec/models/ci/build_trace_chunks/legacy_fog_spec.rb164
-rw-r--r--spec/models/ci/daily_build_group_report_result_spec.rb77
-rw-r--r--spec/models/ci/pipeline_spec.rb28
-rw-r--r--spec/models/ci/test_case_failure_spec.rb73
-rw-r--r--spec/models/ci/test_case_spec.rb31
-rw-r--r--spec/models/clusters/agent_token_spec.rb5
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb4
-rw-r--r--spec/models/clusters/applications/crossplane_spec.rb2
-rw-r--r--spec/models/clusters/applications/elastic_stack_spec.rb8
-rw-r--r--spec/models/clusters/applications/fluentd_spec.rb2
-rw-r--r--spec/models/clusters/applications/helm_spec.rb6
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb2
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb2
-rw-r--r--spec/models/clusters/applications/knative_spec.rb4
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb6
-rw-r--r--spec/models/clusters/applications/runner_spec.rb2
-rw-r--r--spec/models/clusters/cluster_spec.rb21
-rw-r--r--spec/models/commit_status_spec.rb137
-rw-r--r--spec/models/concerns/atomic_internal_id_spec.rb16
-rw-r--r--spec/models/concerns/from_union_spec.rb10
-rw-r--r--spec/models/concerns/optionally_search_spec.rb2
-rw-r--r--spec/models/container_expiration_policy_spec.rb55
-rw-r--r--spec/models/container_repository_spec.rb16
-rw-r--r--spec/models/custom_emoji_spec.rb14
-rw-r--r--spec/models/dependency_proxy/blob_spec.rb55
-rw-r--r--spec/models/dependency_proxy/group_setting_spec.rb13
-rw-r--r--spec/models/dependency_proxy/registry_spec.rb57
-rw-r--r--spec/models/deploy_key_spec.rb53
-rw-r--r--spec/models/deploy_keys_project_spec.rb15
-rw-r--r--spec/models/deploy_token_spec.rb33
-rw-r--r--spec/models/deployment_spec.rb8
-rw-r--r--spec/models/design_management/design_at_version_spec.rb2
-rw-r--r--spec/models/design_management/design_spec.rb52
-rw-r--r--spec/models/design_management/version_spec.rb2
-rw-r--r--spec/models/diff_viewer/image_spec.rb40
-rw-r--r--spec/models/environment_spec.rb16
-rw-r--r--spec/models/experiment_spec.rb120
-rw-r--r--spec/models/group_spec.rb143
-rw-r--r--spec/models/instance_metadata_spec.rb12
-rw-r--r--spec/models/internal_id_spec.rb23
-rw-r--r--spec/models/issue_link_spec.rb9
-rw-r--r--spec/models/issues/csv_import_spec.rb10
-rw-r--r--spec/models/key_spec.rb4
-rw-r--r--spec/models/member_spec.rb11
-rw-r--r--spec/models/members/group_member_spec.rb14
-rw-r--r--spec/models/merge_request/cleanup_schedule_spec.rb32
-rw-r--r--spec/models/merge_request_spec.rb13
-rw-r--r--spec/models/namespace/root_storage_statistics_spec.rb2
-rw-r--r--spec/models/namespace_setting_spec.rb7
-rw-r--r--spec/models/namespace_spec.rb95
-rw-r--r--spec/models/operations/feature_flag_spec.rb34
-rw-r--r--spec/models/operations/feature_flags/user_list_spec.rb19
-rw-r--r--spec/models/packages/build_info_spec.rb9
-rw-r--r--spec/models/packages/package_file_build_info_spec.rb9
-rw-r--r--spec/models/packages/package_file_spec.rb2
-rw-r--r--spec/models/packages/package_spec.rb32
-rw-r--r--spec/models/pages/lookup_path_spec.rb120
-rw-r--r--spec/models/pages_deployment_spec.rb36
-rw-r--r--spec/models/personal_access_token_spec.rb12
-rw-r--r--spec/models/personal_snippet_spec.rb3
-rw-r--r--spec/models/project_snippet_spec.rb3
-rw-r--r--spec/models/project_spec.rb99
-rw-r--r--spec/models/project_statistics_spec.rb40
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb30
-rw-r--r--spec/models/route_spec.rb9
-rw-r--r--spec/models/service_spec.rb116
-rw-r--r--spec/models/terraform/state_spec.rb8
-rw-r--r--spec/models/terraform/state_version_spec.rb1
-rw-r--r--spec/models/user_spec.rb78
81 files changed, 2022 insertions, 626 deletions
diff --git a/spec/models/alert_management/http_integration_spec.rb b/spec/models/alert_management/http_integration_spec.rb
index 37d67dfe09a..a3e7b47c116 100644
--- a/spec/models/alert_management/http_integration_spec.rb
+++ b/spec/models/alert_management/http_integration_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe AlertManagement::HttpIntegration do
+ include ::Gitlab::Routing.url_helpers
+
let_it_be(:project) { create(:project) }
subject(:integration) { build(:alert_management_http_integration) }
@@ -15,19 +17,17 @@ RSpec.describe AlertManagement::HttpIntegration do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_length_of(:name).is_at_most(255) }
- it { is_expected.to validate_presence_of(:endpoint_identifier) }
- it { is_expected.to validate_length_of(:endpoint_identifier).is_at_most(255) }
context 'when active' do
# Using `create` instead of `build` the integration so `token` is set.
# Uniqueness spec saves integration with `validate: false` otherwise.
- subject { create(:alert_management_http_integration) }
+ subject { create(:alert_management_http_integration, :legacy) }
it { is_expected.to validate_uniqueness_of(:endpoint_identifier).scoped_to(:project_id, :active) }
end
context 'when inactive' do
- subject { create(:alert_management_http_integration, :inactive) }
+ subject { create(:alert_management_http_integration, :legacy, :inactive) }
it { is_expected.not_to validate_uniqueness_of(:endpoint_identifier).scoped_to(:project_id, :active) }
end
@@ -51,10 +51,6 @@ RSpec.describe AlertManagement::HttpIntegration do
context 'when unsaved' do
context 'when unassigned' do
- before do
- integration.valid?
- end
-
it_behaves_like 'valid token'
end
@@ -89,4 +85,75 @@ RSpec.describe AlertManagement::HttpIntegration do
end
end
end
+
+ describe '#endpoint_identifier' do
+ subject { integration.endpoint_identifier }
+
+ context 'when defined on initialize' do
+ let(:integration) { described_class.new }
+
+ it { is_expected.to match(/\A\h{16}\z/) }
+ end
+
+ context 'when included in initialization args' do
+ let(:integration) { described_class.new(endpoint_identifier: 'legacy') }
+
+ it { is_expected.to eq('legacy') }
+ end
+
+ context 'when reassigning' do
+ let(:integration) { create(:alert_management_http_integration) }
+ let!(:starting_identifier) { subject }
+
+ it 'does not allow reassignment' do
+ integration.endpoint_identifier = 'newValidId'
+ integration.save!
+
+ expect(integration.reload.endpoint_identifier).to eq(starting_identifier)
+ end
+ end
+ end
+
+ describe '#url' do
+ subject { integration.url }
+
+ it do
+ is_expected.to eq(
+ project_alert_http_integration_url(
+ integration.project,
+ 'datadog',
+ integration.endpoint_identifier,
+ format: :json
+ )
+ )
+ end
+
+ context 'when name is not defined' do
+ let(:integration) { described_class.new(project: project) }
+
+ it do
+ is_expected.to eq(
+ project_alert_http_integration_url(
+ integration.project,
+ 'http-endpoint',
+ integration.endpoint_identifier,
+ format: :json
+ )
+ )
+ end
+ end
+
+ context 'for a legacy integration' do
+ let(:integration) { build(:alert_management_http_integration, :legacy) }
+
+ it do
+ is_expected.to eq(
+ project_alerts_notify_url(
+ integration.project,
+ format: :json
+ )
+ )
+ end
+ end
+ end
end
diff --git a/spec/models/analytics/cycle_analytics/project_stage_spec.rb b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
index 4675f037957..fce31af619c 100644
--- a/spec/models/analytics/cycle_analytics/project_stage_spec.rb
+++ b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe Analytics::CycleAnalytics::ProjectStage do
end
end
- it_behaves_like 'cycle analytics stage' do
+ it_behaves_like 'value stream analytics stage' do
let(:parent) { build(:project) }
let(:parent_name) { :project }
end
diff --git a/spec/models/analytics/devops_adoption/segment_selection_spec.rb b/spec/models/analytics/devops_adoption/segment_selection_spec.rb
new file mode 100644
index 00000000000..5866cbaa48e
--- /dev/null
+++ b/spec/models/analytics/devops_adoption/segment_selection_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Analytics::DevopsAdoption::SegmentSelection, type: :model do
+ subject { build(:devops_adoption_segment_selection, :project) }
+
+ describe 'validation' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project) }
+
+ it { is_expected.to validate_presence_of(:segment) }
+
+ context do
+ subject { create(:devops_adoption_segment_selection, :project, project: project) }
+
+ it { is_expected.to validate_uniqueness_of(:project_id).scoped_to(:segment_id) }
+ end
+
+ context do
+ subject { create(:devops_adoption_segment_selection, :group, group: group) }
+
+ it { is_expected.to validate_uniqueness_of(:group_id).scoped_to(:segment_id) }
+ end
+
+ it 'project is required' do
+ selection = build(:devops_adoption_segment_selection, project: nil, group: nil)
+
+ selection.validate
+
+ expect(selection.errors).to have_key(:project)
+ end
+
+ it 'project is not required when a group is given' do
+ selection = build(:devops_adoption_segment_selection, :group, group: group)
+
+ expect(selection).to be_valid
+ end
+
+ it 'does not allow group to be set when project is present' do
+ selection = build(:devops_adoption_segment_selection)
+
+ selection.group = group
+ selection.project = project
+
+ selection.validate
+
+ expect(selection.errors[:group]).to eq([s_('DevopsAdoptionSegmentSelection|The selection cannot be configured for a project and for a group at the same time')])
+ end
+
+ context 'limit the number of segment selections' do
+ let_it_be(:segment) { create(:devops_adoption_segment) }
+
+ subject { build(:devops_adoption_segment_selection, segment: segment, project: project) }
+
+ before do
+ create(:devops_adoption_segment_selection, :project, segment: segment)
+
+ stub_const("#{described_class}::ALLOWED_SELECTIONS_PER_SEGMENT", 1)
+ end
+
+ it 'shows validation error' do
+ subject.validate
+
+ expect(subject.errors[:segment]).to eq([s_('DevopsAdoptionSegment|The maximum number of selections has been reached')])
+ end
+ end
+ end
+end
diff --git a/spec/models/analytics/instance_statistics/measurement_spec.rb b/spec/models/analytics/instance_statistics/measurement_spec.rb
index 379272cfcb9..dbb16c5ffbe 100644
--- a/spec/models/analytics/instance_statistics/measurement_spec.rb
+++ b/spec/models/analytics/instance_statistics/measurement_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do
describe 'identifiers enum' do
it 'maps to the correct values' do
- expect(described_class.identifiers).to eq({
+ identifiers = {
projects: 1,
users: 2,
issues: 3,
@@ -24,8 +24,11 @@ RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do
pipelines_succeeded: 7,
pipelines_failed: 8,
pipelines_canceled: 9,
- pipelines_skipped: 10
- }.with_indifferent_access)
+ pipelines_skipped: 10,
+ billable_users: 11
+ }
+
+ expect(described_class.identifiers).to eq(identifiers.with_indifferent_access)
end
end
@@ -45,29 +48,71 @@ RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do
it { is_expected.to match_array([measurement_1, measurement_2]) }
end
- end
- describe '#measurement_identifier_values' do
- subject { described_class.measurement_identifier_values.count }
+ describe '.recorded_after' do
+ subject { described_class.recorded_after(8.days.ago) }
- context 'when the `store_ci_pipeline_counts_by_status` feature flag is off' do
- let(:expected_count) { Analytics::InstanceStatistics::Measurement.identifiers.size - Analytics::InstanceStatistics::Measurement::EXPERIMENTAL_IDENTIFIERS.size }
+ it { is_expected.to match_array([measurement_2, measurement_3]) }
- before do
- stub_feature_flags(store_ci_pipeline_counts_by_status: false)
+ context 'when nil is given' do
+ subject { described_class.recorded_after(nil) }
+
+ it 'does not apply filtering' do
+ expect(subject).to match_array([measurement_1, measurement_2, measurement_3])
+ end
end
+ end
+
+ describe '.recorded_before' do
+ subject { described_class.recorded_before(4.days.ago) }
- it { is_expected.to eq(expected_count) }
+ it { is_expected.to match_array([measurement_1, measurement_3]) }
+
+ context 'when nil is given' do
+ subject { described_class.recorded_after(nil) }
+
+ it 'does not apply filtering' do
+ expect(subject).to match_array([measurement_1, measurement_2, measurement_3])
+ end
+ end
end
+ end
+
+ describe '.identifier_query_mapping' do
+ subject { described_class.identifier_query_mapping }
+
+ it { is_expected.to be_a Hash }
+ end
+
+ describe '.identifier_min_max_queries' do
+ subject { described_class.identifier_min_max_queries }
+
+ it { is_expected.to be_a Hash }
+ end
+
+ describe '.measurement_identifier_values' do
+ let(:expected_count) { described_class.identifiers.size }
+
+ subject { described_class.measurement_identifier_values.count }
+
+ it { is_expected.to eq(expected_count) }
+ end
- context 'when the `store_ci_pipeline_counts_by_status` feature flag is on' do
- let(:expected_count) { Analytics::InstanceStatistics::Measurement.identifiers.size }
+ describe '.find_latest_or_fallback' do
+ subject(:count) { described_class.find_latest_or_fallback(:pipelines_skipped).count }
- before do
- stub_feature_flags(store_ci_pipeline_counts_by_status: true)
+ context 'with instance statistics' do
+ let!(:measurement) { create(:instance_statistics_measurement, :pipelines_skipped_count) }
+
+ it 'returns the latest stored measurement' do
+ expect(count).to eq measurement.count
end
+ end
- it { is_expected.to eq(expected_count) }
+ context 'without instance statistics' do
+ it 'returns the realtime query of the measurement' do
+ expect(count).to eq 0
+ end
end
end
end
diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb
index d080b298e2f..6a0f2290b4c 100644
--- a/spec/models/application_record_spec.rb
+++ b/spec/models/application_record_spec.rb
@@ -67,7 +67,8 @@ RSpec.describe ApplicationRecord do
end
it 'raises a validation error if the record was not persisted' do
- expect { Suggestion.find_or_create_by!(note: nil) }.to raise_error(ActiveRecord::RecordInvalid)
+ expect { Suggestion.safe_find_or_create_by!(note: nil) }
+ .to raise_error(ActiveRecord::RecordInvalid)
end
it 'passes a block to find_or_create_by' do
@@ -75,6 +76,14 @@ RSpec.describe ApplicationRecord do
Suggestion.safe_find_or_create_by!(suggestion_attributes, &block)
end.to yield_with_args(an_object_having_attributes(suggestion_attributes))
end
+
+ it 'raises a record not found error in case of attributes mismatch' do
+ suggestion = Suggestion.safe_find_or_create_by!(suggestion_attributes)
+ attributes = suggestion_attributes.merge(outdated: !suggestion.outdated)
+
+ expect { Suggestion.safe_find_or_create_by!(attributes) }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index fb702d10a42..efe62a1d086 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -72,6 +72,7 @@ RSpec.describe ApplicationSetting do
it { is_expected.not_to allow_value(nil).for(:push_event_activities_limit) }
it { is_expected.to validate_numericality_of(:container_registry_delete_tags_service_timeout).only_integer.is_greater_than_or_equal_to(0) }
+ it { is_expected.to validate_numericality_of(:container_registry_expiration_policies_worker_capacity).only_integer.is_greater_than_or_equal_to(0) }
it { is_expected.to validate_numericality_of(:snippet_size_limit).only_integer.is_greater_than(0) }
it { is_expected.to validate_numericality_of(:wiki_page_max_content_bytes).only_integer.is_greater_than_or_equal_to(1024) }
@@ -647,6 +648,37 @@ RSpec.describe ApplicationSetting do
end
end
end
+
+ describe '#ci_jwt_signing_key' do
+ it { is_expected.not_to allow_value('').for(:ci_jwt_signing_key) }
+ it { is_expected.not_to allow_value('invalid RSA key').for(:ci_jwt_signing_key) }
+ it { is_expected.to allow_value(nil).for(:ci_jwt_signing_key) }
+ it { is_expected.to allow_value(OpenSSL::PKey::RSA.new(1024).to_pem).for(:ci_jwt_signing_key) }
+
+ it 'is encrypted' do
+ subject.ci_jwt_signing_key = OpenSSL::PKey::RSA.new(1024).to_pem
+
+ aggregate_failures do
+ expect(subject.encrypted_ci_jwt_signing_key).to be_present
+ expect(subject.encrypted_ci_jwt_signing_key_iv).to be_present
+ expect(subject.encrypted_ci_jwt_signing_key).not_to eq(subject.ci_jwt_signing_key)
+ end
+ end
+ end
+
+ describe '#cloud_license_auth_token' do
+ it { is_expected.to allow_value(nil).for(:cloud_license_auth_token) }
+
+ it 'is encrypted' do
+ subject.cloud_license_auth_token = 'token-from-customers-dot'
+
+ aggregate_failures do
+ expect(subject.encrypted_cloud_license_auth_token).to be_present
+ expect(subject.encrypted_cloud_license_auth_token_iv).to be_present
+ expect(subject.encrypted_cloud_license_auth_token).not_to eq(subject.cloud_license_auth_token)
+ end
+ end
+ end
end
context 'static objects external storage' do
diff --git a/spec/models/authentication_event_spec.rb b/spec/models/authentication_event_spec.rb
index 483d45c08be..83598fa6765 100644
--- a/spec/models/authentication_event_spec.rb
+++ b/spec/models/authentication_event_spec.rb
@@ -37,15 +37,11 @@ RSpec.describe AuthenticationEvent do
describe '.providers' do
before do
- create(:authentication_event, provider: :ldapmain)
- create(:authentication_event, provider: :google_oauth2)
- create(:authentication_event, provider: :standard)
- create(:authentication_event, provider: :standard)
- create(:authentication_event, provider: :standard)
+ allow(Devise).to receive(:omniauth_providers).and_return(%w(ldapmain google_oauth2))
end
it 'returns an array of distinct providers' do
- expect(described_class.providers).to match_array %w(ldapmain google_oauth2 standard)
+ expect(described_class.providers).to match_array %w(ldapmain google_oauth2 standard two-factor two-factor-via-u2f-device two-factor-via-webauthn-device)
end
end
end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index fc463c6af52..c4d17905637 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -161,6 +161,12 @@ RSpec.describe BroadcastMessage do
expect(subject.call('/group/issues/test').length).to eq(1)
end
+
+ it "does not return message if the target path is set but no current path is provided" do
+ create(:broadcast_message, target_path: "*/issues/*", broadcast_type: broadcast_type)
+
+ expect(subject.call.length).to eq(0)
+ end
end
describe '.current', :use_clean_rails_memory_store_caching do
diff --git a/spec/models/bulk_imports/tracker_spec.rb b/spec/models/bulk_imports/tracker_spec.rb
new file mode 100644
index 00000000000..8eb5a6c27dd
--- /dev/null
+++ b/spec/models/bulk_imports/tracker_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Tracker, type: :model do
+ describe 'associations' do
+ it { is_expected.to belong_to(:entity).required }
+ end
+
+ describe 'validations' do
+ before do
+ create(:bulk_import_tracker)
+ end
+
+ it { is_expected.to validate_presence_of(:relation) }
+ it { is_expected.to validate_uniqueness_of(:relation).scoped_to(:bulk_import_entity_id) }
+
+ context 'when has_next_page is true' do
+ it "validates presence of `next_page`" do
+ tracker = build(:bulk_import_tracker, has_next_page: true)
+
+ expect(tracker).not_to be_valid
+ expect(tracker.errors).to include(:next_page)
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index c464e176c17..51e82061d97 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -55,6 +55,17 @@ RSpec.describe Ci::Bridge do
expect(bridge.scoped_variables_hash.keys).to include(*variables)
end
+
+ context 'when bridge has dependency which has dotenv variable' do
+ let(:test) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
+ let(:bridge) { create(:ci_bridge, pipeline: pipeline, stage_idx: 1, options: { dependencies: [test.name] }) }
+
+ let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: test) }
+
+ it 'includes inherited variable' do
+ expect(bridge.scoped_variables_hash).to include(job_variable.key => job_variable.value)
+ end
+ end
end
describe 'state machine transitions' do
@@ -357,4 +368,53 @@ RSpec.describe Ci::Bridge do
it { is_expected.to be_falsey }
end
end
+
+ describe '#dependency_variables' do
+ subject { bridge.dependency_variables }
+
+ shared_context 'when ci_bridge_dependency_variables is disabled' do
+ before do
+ stub_feature_flags(ci_bridge_dependency_variables: false)
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when downloading from previous stages' do
+ let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) }
+ let!(:bridge) { create(:ci_bridge, pipeline: pipeline, stage_idx: 1) }
+
+ let!(:job_variable_1) { create(:ci_job_variable, :dotenv_source, job: prepare1) }
+ let!(:job_variable_2) { create(:ci_job_variable, job: prepare1) }
+
+ it 'inherits only dependent variables' do
+ expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value)
+ end
+
+ it_behaves_like 'when ci_bridge_dependency_variables is disabled'
+ end
+
+ context 'when using needs' do
+ let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) }
+ let!(:prepare2) { create(:ci_build, name: 'prepare2', pipeline: pipeline, stage_idx: 0) }
+ let!(:prepare3) { create(:ci_build, name: 'prepare3', pipeline: pipeline, stage_idx: 0) }
+ let!(:bridge) do
+ create(:ci_bridge, pipeline: pipeline,
+ stage_idx: 1,
+ scheduling_type: 'dag',
+ needs_attributes: [{ name: 'prepare1', artifacts: true },
+ { name: 'prepare2', artifacts: false }])
+ end
+
+ let!(:job_variable_1) { create(:ci_job_variable, :dotenv_source, job: prepare1) }
+ let!(:job_variable_2) { create(:ci_job_variable, :dotenv_source, job: prepare2) }
+ let!(:job_variable_3) { create(:ci_job_variable, :dotenv_source, job: prepare3) }
+
+ it 'inherits only needs with artifacts variables' do
+ expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value)
+ end
+
+ it_behaves_like 'when ci_bridge_dependency_variables is disabled'
+ end
+ end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index f1d51324bbf..5ff9b4dd493 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -2464,7 +2464,7 @@ RSpec.describe Ci::Build do
end
before do
- allow(Gitlab::Ci::Jwt).to receive(:for_build).with(build).and_return('ci.job.jwt')
+ allow(Gitlab::Ci::Jwt).to receive(:for_build).and_return('ci.job.jwt')
build.set_token('my-token')
build.yaml_variables = []
end
@@ -2482,12 +2482,17 @@ RSpec.describe Ci::Build do
end
context 'when CI_JOB_JWT generation fails' do
- it 'CI_JOB_JWT is not included' do
- expect(Gitlab::Ci::Jwt).to receive(:for_build).and_raise(OpenSSL::PKey::RSAError, 'Neither PUB key nor PRIV key: not enough data')
- expect(Gitlab::ErrorTracking).to receive(:track_exception)
-
- expect { subject }.not_to raise_error
- expect(subject.pluck(:key)).not_to include('CI_JOB_JWT')
+ [
+ OpenSSL::PKey::RSAError,
+ Gitlab::Ci::Jwt::NoSigningKeyError
+ ].each do |reason_to_fail|
+ it 'CI_JOB_JWT is not included' do
+ expect(Gitlab::Ci::Jwt).to receive(:for_build).and_raise(reason_to_fail)
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+
+ expect { subject }.not_to raise_error
+ expect(subject.pluck(:key)).not_to include('CI_JOB_JWT')
+ end
end
end
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index 871f279db08..dce7b1d30ca 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -100,15 +100,15 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
subject { described_class.all_stores }
it 'returns a correctly ordered array' do
- is_expected.to eq(%w[redis database fog])
+ is_expected.to eq(%i[redis database fog])
end
it 'returns redis store as the lowest precedence' do
- expect(subject.first).to eq('redis')
+ expect(subject.first).to eq(:redis)
end
it 'returns fog store as the highest precedence' do
- expect(subject.last).to eq('fog')
+ expect(subject.last).to eq(:fog)
end
end
@@ -135,32 +135,40 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
context 'when data_store is fog' do
let(:data_store) { :fog }
- context 'when legacy Fog is enabled' do
- before do
- stub_feature_flags(ci_trace_new_fog_store: false)
- build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog')
- end
+ before do
+ build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog')
+ end
- it { is_expected.to eq('Sample data in fog') }
+ it { is_expected.to eq('Sample data in fog') }
- it 'returns a LegacyFog store' do
- expect(described_class.get_store_class(data_store)).to be_a(Ci::BuildTraceChunks::LegacyFog)
- end
+ it 'returns a new Fog store' do
+ expect(described_class.get_store_class(data_store)).to be_a(Ci::BuildTraceChunks::Fog)
end
+ end
+ end
- context 'when new Fog is enabled' do
- before do
- stub_feature_flags(ci_trace_new_fog_store: true)
- build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog')
- end
+ describe '#get_store_class' do
+ using RSpec::Parameterized::TableSyntax
- it { is_expected.to eq('Sample data in fog') }
+ where(:data_store, :expected_store) do
+ :redis | Ci::BuildTraceChunks::Redis
+ :database | Ci::BuildTraceChunks::Database
+ :fog | Ci::BuildTraceChunks::Fog
+ end
- it 'returns a new Fog store' do
- expect(described_class.get_store_class(data_store)).to be_a(Ci::BuildTraceChunks::Fog)
+ with_them do
+ context "with store" do
+ it 'returns an instance of the right class' do
+ expect(expected_store).to receive(:new).twice.and_call_original
+ expect(described_class.get_store_class(data_store.to_s)).to be_a(expected_store)
+ expect(described_class.get_store_class(data_store.to_sym)).to be_a(expected_store)
end
end
end
+
+ it 'raises an error' do
+ expect { described_class.get_store_class('unknown') }.to raise_error('Unknown store type: unknown')
+ end
end
describe '#append' do
@@ -614,23 +622,19 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
context 'when the chunk is being locked by a different worker' do
let(:metrics) { spy('metrics') }
- it 'does not raise an exception' do
- lock_chunk do
- expect { build_trace_chunk.persist_data! }.not_to raise_error
- end
- end
-
it 'increments stalled chunk trace metric' do
allow(build_trace_chunk)
.to receive(:metrics)
.and_return(metrics)
- lock_chunk { build_trace_chunk.persist_data! }
+ expect do
+ subject
- expect(metrics)
- .to have_received(:increment_trace_operation)
- .with(operation: :stalled)
- .once
+ expect(metrics)
+ .to have_received(:increment_trace_operation)
+ .with(operation: :stalled)
+ .once
+ end.to raise_error(described_class::FailedToPersistDataError)
end
def lock_chunk(&block)
diff --git a/spec/models/ci/build_trace_chunks/legacy_fog_spec.rb b/spec/models/ci/build_trace_chunks/legacy_fog_spec.rb
deleted file mode 100644
index ca4b414b992..00000000000
--- a/spec/models/ci/build_trace_chunks/legacy_fog_spec.rb
+++ /dev/null
@@ -1,164 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Ci::BuildTraceChunks::LegacyFog do
- let(:data_store) { described_class.new }
-
- before do
- stub_artifacts_object_storage
- end
-
- describe '#available?' do
- subject { data_store.available? }
-
- context 'when object storage is enabled' do
- it { is_expected.to be_truthy }
- end
-
- context 'when object storage is disabled' do
- before do
- stub_artifacts_object_storage(enabled: false)
- end
-
- it { is_expected.to be_falsy }
- end
- end
-
- describe '#data' do
- subject { data_store.data(model) }
-
- context 'when data exists' do
- let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
-
- it 'returns the data' do
- is_expected.to eq('sample data in fog')
- end
- end
-
- context 'when data does not exist' do
- let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
-
- it 'returns nil' do
- expect(data_store.data(model)).to be_nil
- end
- end
- end
-
- describe '#set_data' do
- let(:new_data) { 'abc123' }
-
- context 'when data exists' do
- let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
-
- it 'overwrites data' do
- expect(data_store.data(model)).to eq('sample data in fog')
-
- data_store.set_data(model, new_data)
-
- expect(data_store.data(model)).to eq new_data
- end
- end
-
- context 'when data does not exist' do
- let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
-
- it 'sets new data' do
- expect(data_store.data(model)).to be_nil
-
- data_store.set_data(model, new_data)
-
- expect(data_store.data(model)).to eq new_data
- end
- end
- end
-
- describe '#delete_data' do
- subject { data_store.delete_data(model) }
-
- context 'when data exists' do
- let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
-
- it 'deletes data' do
- expect(data_store.data(model)).to eq('sample data in fog')
-
- subject
-
- expect(data_store.data(model)).to be_nil
- end
- end
-
- context 'when data does not exist' do
- let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
-
- it 'does nothing' do
- expect(data_store.data(model)).to be_nil
-
- subject
-
- expect(data_store.data(model)).to be_nil
- end
- end
- end
-
- describe '#size' do
- context 'when data exists' do
- let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'üabcd') }
-
- it 'returns data bytesize correctly' do
- expect(data_store.size(model)).to eq 6
- end
- end
-
- context 'when data does not exist' do
- let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
-
- it 'returns zero' do
- expect(data_store.size(model)).to be_zero
- end
- end
- end
-
- describe '#keys' do
- subject { data_store.keys(relation) }
-
- let(:build) { create(:ci_build) }
- let(:relation) { build.trace_chunks }
-
- before do
- create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 0, build: build)
- create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 1, build: build)
- end
-
- it 'returns keys' do
- is_expected.to eq([[build.id, 0], [build.id, 1]])
- end
- end
-
- describe '#delete_keys' do
- subject { data_store.delete_keys(keys) }
-
- let(:build) { create(:ci_build) }
- let(:relation) { build.trace_chunks }
- let(:keys) { data_store.keys(relation) }
-
- before do
- create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 0, build: build)
- create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 1, build: build)
- end
-
- it 'deletes multiple data' do
- ::Fog::Storage.new(JobArtifactUploader.object_store_credentials).tap do |connection|
- expect(connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/0.log")[:body]).to be_present
- expect(connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/1.log")[:body]).to be_present
- end
-
- subject
-
- ::Fog::Storage.new(JobArtifactUploader.object_store_credentials).tap do |connection|
- expect { connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/0.log")[:body] }.to raise_error(Excon::Error::NotFound)
- expect { connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/1.log")[:body] }.to raise_error(Excon::Error::NotFound)
- end
- end
- end
-end
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 326366666cb..f16396d62c9 100644
--- a/spec/models/ci/daily_build_group_report_result_spec.rb
+++ b/spec/models/ci/daily_build_group_report_result_spec.rb
@@ -81,4 +81,81 @@ RSpec.describe Ci::DailyBuildGroupReportResult do
end
end
end
+
+ describe 'scopes' do
+ let_it_be(:project) { create(:project) }
+ let(:recent_build_group_report_result) { create(:ci_daily_build_group_report_result, project: project) }
+ let(:old_build_group_report_result) do
+ create(:ci_daily_build_group_report_result, date: 1.week.ago, project: project)
+ end
+
+ describe '.by_projects' do
+ subject { described_class.by_projects([project.id]) }
+
+ it 'returns records by projects' do
+ expect(subject).to contain_exactly(recent_build_group_report_result, old_build_group_report_result)
+ end
+ end
+
+ describe '.with_coverage' do
+ subject { described_class.with_coverage }
+
+ it 'returns data with coverage' do
+ expect(subject).to contain_exactly(recent_build_group_report_result, old_build_group_report_result)
+ end
+ end
+
+ describe '.with_default_branch' do
+ subject(:coverages) { described_class.with_default_branch }
+
+ context 'when coverage for the default branch exist' do
+ let!(:recent_build_group_report_result) { create(:ci_daily_build_group_report_result, project: project) }
+ let!(:coverage_feature_branch) { create(:ci_daily_build_group_report_result, :on_feature_branch, project: project) }
+
+ it 'returns coverage with the default branch' do
+ expect(coverages).to contain_exactly(recent_build_group_report_result)
+ end
+ end
+
+ context 'when coverage for the default branch does not exist' do
+ it 'returns an empty collection' do
+ expect(coverages).to be_empty
+ end
+ end
+ end
+
+ describe '.by_date' do
+ subject(:coverages) { described_class.by_date(start_date) }
+
+ let!(:coverage_1) { create(:ci_daily_build_group_report_result, date: 1.week.ago) }
+
+ context 'when project has several coverage' do
+ let!(:coverage_2) { create(:ci_daily_build_group_report_result, date: 2.weeks.ago) }
+ let(:start_date) { 1.week.ago.to_date.to_s }
+
+ it 'returns the coverage from the start_date' do
+ expect(coverages).to contain_exactly(coverage_1)
+ end
+ end
+
+ context 'when start_date is over 90 days' do
+ let!(:coverage_2) { create(:ci_daily_build_group_report_result, date: 90.days.ago) }
+ let!(:coverage_3) { create(:ci_daily_build_group_report_result, date: 91.days.ago) }
+ let(:start_date) { 1.year.ago.to_date.to_s }
+
+ it 'returns the coverage in the last 90 days' do
+ expect(coverages).to contain_exactly(coverage_1, coverage_2)
+ end
+ end
+
+ context 'when start_date is not a string' do
+ let!(:coverage_2) { create(:ci_daily_build_group_report_result, date: 90.days.ago) }
+ let(:start_date) { 1.week.ago }
+
+ it 'returns the coverage in the last 90 days' do
+ expect(coverages).to contain_exactly(coverage_1, coverage_2)
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 88d08f1ec45..1ca370dc950 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -625,7 +625,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
- describe "coverage" do
+ describe '#coverage' do
let(:project) { create(:project, build_coverage_regex: "/.*/") }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
@@ -1972,6 +1972,32 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '.latest_running_for_ref' do
+ include_context 'with some outdated pipelines'
+
+ let!(:latest_running_pipeline) do
+ create_pipeline(:running, 'ref', 'D', project)
+ end
+
+ it 'returns the latest running pipeline' do
+ expect(described_class.latest_running_for_ref('ref'))
+ .to eq(latest_running_pipeline)
+ end
+ end
+
+ describe '.latest_failed_for_ref' do
+ include_context 'with some outdated pipelines'
+
+ let!(:latest_failed_pipeline) do
+ create_pipeline(:failed, 'ref', 'D', project)
+ end
+
+ it 'returns the latest failed pipeline' do
+ expect(described_class.latest_failed_for_ref('ref'))
+ .to eq(latest_failed_pipeline)
+ end
+ end
+
describe '.latest_successful_for_sha' do
include_context 'with some outdated pipelines'
diff --git a/spec/models/ci/test_case_failure_spec.rb b/spec/models/ci/test_case_failure_spec.rb
new file mode 100644
index 00000000000..34f89b663ed
--- /dev/null
+++ b/spec/models/ci/test_case_failure_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::TestCaseFailure do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:build) }
+ it { is_expected.to belong_to(:test_case) }
+ end
+
+ describe 'validations' do
+ subject { build(:ci_test_case_failure) }
+
+ it { is_expected.to validate_presence_of(:test_case) }
+ it { is_expected.to validate_presence_of(:build) }
+ it { is_expected.to validate_presence_of(:failed_at) }
+ end
+
+ describe '.recent_failures_count' do
+ let_it_be(:project) { create(:project) }
+
+ subject(:recent_failures) do
+ described_class.recent_failures_count(
+ project: project,
+ test_case_keys: test_case_keys
+ )
+ end
+
+ context 'when test case failures are within the date range and are for the test case keys' do
+ let(:tc_1) { create(:ci_test_case, project: project) }
+ let(:tc_2) { create(:ci_test_case, project: project) }
+ let(:test_case_keys) { [tc_1.key_hash, tc_2.key_hash] }
+
+ before do
+ create_list(:ci_test_case_failure, 3, test_case: tc_1, failed_at: 1.day.ago)
+ create_list(:ci_test_case_failure, 2, test_case: tc_2, failed_at: 3.days.ago)
+ end
+
+ it 'returns the number of failures for each test case key hash for the past 14 days by default' do
+ expect(recent_failures).to eq(
+ tc_1.key_hash => 3,
+ tc_2.key_hash => 2
+ )
+ end
+ end
+
+ context 'when test case failures are within the date range but are not for the test case keys' do
+ let(:tc) { create(:ci_test_case, project: project) }
+ let(:test_case_keys) { ['some-other-key-hash'] }
+
+ before do
+ create(:ci_test_case_failure, test_case: tc, failed_at: 1.day.ago)
+ end
+
+ it 'excludes them from the count' do
+ expect(recent_failures[tc.key_hash]).to be_nil
+ end
+ end
+
+ context 'when test case failures are not within the date range but are for the test case keys' do
+ let(:tc) { create(:ci_test_case, project: project) }
+ let(:test_case_keys) { [tc.key_hash] }
+
+ before do
+ create(:ci_test_case_failure, test_case: tc, failed_at: 15.days.ago)
+ end
+
+ it 'excludes them from the count' do
+ expect(recent_failures[tc.key_hash]).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/test_case_spec.rb b/spec/models/ci/test_case_spec.rb
new file mode 100644
index 00000000000..45311e285a6
--- /dev/null
+++ b/spec/models/ci/test_case_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::TestCase do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:test_case_failures) }
+ end
+
+ describe 'validations' do
+ subject { build(:ci_test_case) }
+
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:key_hash) }
+ end
+
+ describe '.find_or_create_by_batch' do
+ it 'finds or creates records for the given test case keys', :aggregate_failures do
+ project = create(:project)
+ existing_tc = create(:ci_test_case, project: project)
+ new_key = Digest::SHA256.hexdigest(SecureRandom.hex)
+ keys = [existing_tc.key_hash, new_key]
+
+ result = described_class.find_or_create_by_batch(project, keys)
+
+ expect(result.map(&:key_hash)).to match_array([existing_tc.key_hash, new_key])
+ expect(result).to all(be_persisted)
+ end
+ end
+end
diff --git a/spec/models/clusters/agent_token_spec.rb b/spec/models/clusters/agent_token_spec.rb
index ad9dd11b24e..9110fdeda52 100644
--- a/spec/models/clusters/agent_token_spec.rb
+++ b/spec/models/clusters/agent_token_spec.rb
@@ -14,5 +14,10 @@ RSpec.describe Clusters::AgentToken do
expect(agent_token.token).to be_present
end
+
+ it 'is at least 50 characters' do
+ agent_token = create(:cluster_agent_token)
+ expect(agent_token.token.length).to be >= 50
+ end
end
end
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
index 7ca7f533a27..3044260a000 100644
--- a/spec/models/clusters/applications/cert_manager_spec.rb
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Clusters::Applications::CertManager do
subject { cert_manager.install_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with cert_manager arguments' do
expect(subject.name).to eq('certmanager')
@@ -90,7 +90,7 @@ RSpec.describe Clusters::Applications::CertManager do
describe '#uninstall_command' do
subject { cert_manager.uninstall_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) }
it 'is initialized with cert_manager arguments' do
expect(subject.name).to eq('certmanager')
diff --git a/spec/models/clusters/applications/crossplane_spec.rb b/spec/models/clusters/applications/crossplane_spec.rb
index a41c5f6586b..7082576028b 100644
--- a/spec/models/clusters/applications/crossplane_spec.rb
+++ b/spec/models/clusters/applications/crossplane_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Clusters::Applications::Crossplane do
subject { crossplane.install_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with crossplane arguments' do
expect(subject.name).to eq('crossplane')
diff --git a/spec/models/clusters/applications/elastic_stack_spec.rb b/spec/models/clusters/applications/elastic_stack_spec.rb
index 62123ffa542..74cacd486b0 100644
--- a/spec/models/clusters/applications/elastic_stack_spec.rb
+++ b/spec/models/clusters/applications/elastic_stack_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
subject { elastic_stack.install_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with elastic stack arguments' do
expect(subject.name).to eq('elastic-stack')
@@ -57,7 +57,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
it 'includes a preinstall script' do
expect(subject.preinstall).not_to be_empty
- expect(subject.preinstall.first).to include("delete")
+ expect(subject.preinstall.first).to include("helm uninstall")
end
end
@@ -69,7 +69,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
it 'includes a preinstall script' do
expect(subject.preinstall).not_to be_empty
- expect(subject.preinstall.first).to include("delete")
+ expect(subject.preinstall.first).to include("helm uninstall")
end
end
@@ -123,7 +123,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
subject { elastic_stack.uninstall_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) }
it 'is initialized with elastic stack arguments' do
expect(subject.name).to eq('elastic-stack')
diff --git a/spec/models/clusters/applications/fluentd_spec.rb b/spec/models/clusters/applications/fluentd_spec.rb
index 3bda3e99ec1..ccdf6b0e40d 100644
--- a/spec/models/clusters/applications/fluentd_spec.rb
+++ b/spec/models/clusters/applications/fluentd_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe Clusters::Applications::Fluentd do
describe '#install_command' do
subject { fluentd.install_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with fluentd arguments' do
expect(subject.name).to eq('fluentd')
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index 6d2ecaa6d47..ad1ebd4966a 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe Clusters::Applications::Helm do
subject { application.issue_client_cert }
it 'returns a new cert' do
- is_expected.to be_kind_of(Gitlab::Kubernetes::Helm::Certificate)
+ is_expected.to be_kind_of(Gitlab::Kubernetes::Helm::V2::Certificate)
expect(subject.cert_string).not_to eq(application.ca_cert)
expect(subject.key_string).not_to eq(application.ca_key)
end
@@ -67,7 +67,7 @@ RSpec.describe Clusters::Applications::Helm do
subject { helm.install_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InitCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V2::InitCommand) }
it 'is initialized with 1 arguments' do
expect(subject.name).to eq('helm')
@@ -104,7 +104,7 @@ RSpec.describe Clusters::Applications::Helm do
subject { helm.uninstall_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::ResetCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V2::ResetCommand) }
it 'has name' do
expect(subject.name).to eq('helm')
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 196d57aff7b..1bc1a4343aa 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -131,7 +131,7 @@ RSpec.describe Clusters::Applications::Ingress do
describe '#install_command' do
subject { ingress.install_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with ingress arguments' do
expect(subject.name).to eq('ingress')
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 3cf24f1a9ef..e7de2d24334 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -52,7 +52,7 @@ RSpec.describe Clusters::Applications::Jupyter do
subject { jupyter.install_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with 4 arguments' do
expect(subject.name).to eq('jupyter')
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index b14161ce8e6..41b4ec86233 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -119,7 +119,7 @@ RSpec.describe Clusters::Applications::Knative do
shared_examples 'a command' do
it 'is an instance of Helm::InstallCommand' do
- expect(subject).to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand)
+ expect(subject).to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand)
end
it 'is initialized with knative arguments' do
@@ -171,7 +171,7 @@ RSpec.describe Clusters::Applications::Knative do
describe '#uninstall_command' do
subject { knative.uninstall_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) }
it "removes knative deployed services before uninstallation" do
2.times do |i|
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index b450900bee6..032de6aa7c2 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -148,7 +148,7 @@ RSpec.describe Clusters::Applications::Prometheus do
subject { prometheus.install_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with 3 arguments' do
expect(subject.name).to eq('prometheus')
@@ -195,7 +195,7 @@ RSpec.describe Clusters::Applications::Prometheus do
subject { prometheus.uninstall_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) }
it 'has the application name' do
expect(subject.name).to eq('prometheus')
@@ -236,7 +236,7 @@ RSpec.describe Clusters::Applications::Prometheus do
let(:prometheus) { build(:clusters_applications_prometheus) }
let(:values) { prometheus.values }
- it { is_expected.to be_an_instance_of(::Gitlab::Kubernetes::Helm::PatchCommand) }
+ it { is_expected.to be_an_instance_of(::Gitlab::Kubernetes::Helm::V3::PatchCommand) }
it 'is initialized with 3 arguments' do
expect(patch_command.name).to eq('prometheus')
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index ef916c73e0b..43e2eab3b9d 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe Clusters::Applications::Runner do
subject { gitlab_runner.install_command }
- it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with 4 arguments' do
expect(subject.name).to eq('runner')
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index dd9b96f39ad..ed74a841044 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -540,6 +540,27 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
end
+
+ describe 'helm_major_version can only be 2 or 3' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:helm_major_version, :expect_valid) do
+ 2 | true
+ 3 | true
+ 4 | false
+ -1 | false
+ end
+
+ with_them do
+ let(:cluster) { build(:cluster, helm_major_version: helm_major_version) }
+
+ it { is_expected.to eq(expect_valid) }
+ end
+ end
+ end
+
+ it 'has default helm_major_version 3' do
+ expect(create(:cluster).helm_major_version).to eq(3)
end
describe '.ancestor_clusters_for_clusterable' do
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 877188097fd..9824eb91bc7 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -493,104 +493,49 @@ RSpec.describe CommitStatus do
end
end
- context 'with the one_dimensional_matrix feature flag disabled' do
- describe '#group_name' do
- before do
- stub_feature_flags(one_dimensional_matrix: false)
- end
-
- let(:commit_status) do
- build(:commit_status, pipeline: pipeline, stage: 'test')
- end
-
- subject { commit_status.group_name }
-
- tests = {
- 'rspec:windows' => 'rspec:windows',
- 'rspec:windows 0' => 'rspec:windows 0',
- 'rspec:windows 0 test' => 'rspec:windows 0 test',
- 'rspec:windows 0 1' => 'rspec:windows',
- 'rspec:windows 0 1 name' => 'rspec:windows name',
- 'rspec:windows 0/1' => 'rspec:windows',
- 'rspec:windows 0/1 name' => 'rspec:windows name',
- 'rspec:windows 0:1' => 'rspec:windows',
- 'rspec:windows 0:1 name' => 'rspec:windows name',
- 'rspec:windows 10000 20000' => 'rspec:windows',
- 'rspec:windows 0 : / 1' => 'rspec:windows',
- 'rspec:windows 0 : / 1 name' => 'rspec:windows name',
- '0 1 name ruby' => 'name ruby',
- '0 :/ 1 name ruby' => 'name ruby',
- 'rspec: [aws]' => 'rspec: [aws]',
- 'rspec: [aws] 0/1' => 'rspec: [aws]',
- 'rspec: [aws, max memory]' => 'rspec',
- 'rspec:linux: [aws, max memory, data]' => 'rspec:linux',
- 'rspec: [inception: [something, other thing], value]' => 'rspec',
- 'rspec:windows 0/1: [name, other]' => 'rspec:windows',
- 'rspec:windows: [name, other] 0/1' => 'rspec:windows',
- 'rspec:windows: [name, 0/1] 0/1' => 'rspec:windows',
- 'rspec:windows: [0/1, name]' => 'rspec:windows',
- 'rspec:windows: [, ]' => 'rspec:windows',
- 'rspec:windows: [name]' => 'rspec:windows: [name]',
- 'rspec:windows: [name,other]' => 'rspec:windows: [name,other]'
- }
-
- tests.each do |name, group_name|
- it "'#{name}' puts in '#{group_name}'" do
- commit_status.name = name
-
- is_expected.to eq(group_name)
- end
- end
- end
- end
+ describe '#group_name' do
+ using RSpec::Parameterized::TableSyntax
- context 'with one_dimensional_matrix feature flag enabled' do
- describe '#group_name' do
- before do
- stub_feature_flags(one_dimensional_matrix: true)
- end
+ let(:commit_status) do
+ build(:commit_status, pipeline: pipeline, stage: 'test')
+ end
+
+ subject { commit_status.group_name }
+
+ where(:name, :group_name) do
+ 'rspec:windows' | 'rspec:windows'
+ 'rspec:windows 0' | 'rspec:windows 0'
+ 'rspec:windows 0 test' | 'rspec:windows 0 test'
+ 'rspec:windows 0 1' | 'rspec:windows'
+ 'rspec:windows 0 1 name' | 'rspec:windows name'
+ 'rspec:windows 0/1' | 'rspec:windows'
+ 'rspec:windows 0/1 name' | 'rspec:windows name'
+ 'rspec:windows 0:1' | 'rspec:windows'
+ 'rspec:windows 0:1 name' | 'rspec:windows name'
+ 'rspec:windows 10000 20000' | 'rspec:windows'
+ 'rspec:windows 0 : / 1' | 'rspec:windows'
+ 'rspec:windows 0 : / 1 name' | 'rspec:windows name'
+ '0 1 name ruby' | 'name ruby'
+ '0 :/ 1 name ruby' | 'name ruby'
+ 'rspec: [aws]' | 'rspec'
+ 'rspec: [aws] 0/1' | 'rspec'
+ 'rspec: [aws, max memory]' | 'rspec'
+ 'rspec:linux: [aws, max memory, data]' | 'rspec:linux'
+ 'rspec: [inception: [something, other thing], value]' | 'rspec'
+ 'rspec:windows 0/1: [name, other]' | 'rspec:windows'
+ 'rspec:windows: [name, other] 0/1' | 'rspec:windows'
+ 'rspec:windows: [name, 0/1] 0/1' | 'rspec:windows'
+ 'rspec:windows: [0/1, name]' | 'rspec:windows'
+ 'rspec:windows: [, ]' | 'rspec:windows'
+ 'rspec:windows: [name]' | 'rspec:windows'
+ 'rspec:windows: [name,other]' | 'rspec:windows'
+ end
+
+ with_them do
+ it "#{params[:name]} puts in #{params[:group_name]}" do
+ commit_status.name = name
- let(:commit_status) do
- build(:commit_status, pipeline: pipeline, stage: 'test')
- end
-
- subject { commit_status.group_name }
-
- tests = {
- 'rspec:windows' => 'rspec:windows',
- 'rspec:windows 0' => 'rspec:windows 0',
- 'rspec:windows 0 test' => 'rspec:windows 0 test',
- 'rspec:windows 0 1' => 'rspec:windows',
- 'rspec:windows 0 1 name' => 'rspec:windows name',
- 'rspec:windows 0/1' => 'rspec:windows',
- 'rspec:windows 0/1 name' => 'rspec:windows name',
- 'rspec:windows 0:1' => 'rspec:windows',
- 'rspec:windows 0:1 name' => 'rspec:windows name',
- 'rspec:windows 10000 20000' => 'rspec:windows',
- 'rspec:windows 0 : / 1' => 'rspec:windows',
- 'rspec:windows 0 : / 1 name' => 'rspec:windows name',
- '0 1 name ruby' => 'name ruby',
- '0 :/ 1 name ruby' => 'name ruby',
- 'rspec: [aws]' => 'rspec',
- 'rspec: [aws] 0/1' => 'rspec',
- 'rspec: [aws, max memory]' => 'rspec',
- 'rspec:linux: [aws, max memory, data]' => 'rspec:linux',
- 'rspec: [inception: [something, other thing], value]' => 'rspec',
- 'rspec:windows 0/1: [name, other]' => 'rspec:windows',
- 'rspec:windows: [name, other] 0/1' => 'rspec:windows',
- 'rspec:windows: [name, 0/1] 0/1' => 'rspec:windows',
- 'rspec:windows: [0/1, name]' => 'rspec:windows',
- 'rspec:windows: [, ]' => 'rspec:windows',
- 'rspec:windows: [name]' => 'rspec:windows',
- 'rspec:windows: [name,other]' => 'rspec:windows'
- }
-
- tests.each do |name, group_name|
- it "'#{name}' puts in '#{group_name}'" do
- commit_status.name = name
-
- is_expected.to eq(group_name)
- end
+ is_expected.to eq(group_name)
end
end
end
diff --git a/spec/models/concerns/atomic_internal_id_spec.rb b/spec/models/concerns/atomic_internal_id_spec.rb
index 8c3537f1dcc..5ee3c012dc9 100644
--- a/spec/models/concerns/atomic_internal_id_spec.rb
+++ b/spec/models/concerns/atomic_internal_id_spec.rb
@@ -86,4 +86,20 @@ RSpec.describe AtomicInternalId do
expect { subject }.to change { milestone.iid }.from(nil).to(iid.to_i)
end
end
+
+ describe '.with_project_iid_supply' do
+ let(:iid) { 100 }
+
+ it 'wraps generate and track_greatest in a concurrency-safe lock' do
+ expect_next_instance_of(InternalId::InternalIdGenerator) do |g|
+ expect(g).to receive(:with_lock).and_call_original
+ expect(g.record).to receive(:last_value).and_return(iid)
+ expect(g).to receive(:track_greatest).with(iid + 4)
+ end
+
+ ::Milestone.with_project_iid_supply(milestone.project) do |supply|
+ 4.times { supply.next_value }
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/from_union_spec.rb b/spec/models/concerns/from_union_spec.rb
index bd2893090a8..4f4d948fe48 100644
--- a/spec/models/concerns/from_union_spec.rb
+++ b/spec/models/concerns/from_union_spec.rb
@@ -3,13 +3,5 @@
require 'spec_helper'
RSpec.describe FromUnion do
- [true, false].each do |sql_set_operator|
- context "when sql-set-operators feature flag is #{sql_set_operator}" do
- before do
- stub_feature_flags(sql_set_operators: sql_set_operator)
- end
-
- it_behaves_like 'from set operator', Gitlab::SQL::Union
- end
- end
+ it_behaves_like 'from set operator', Gitlab::SQL::Union
end
diff --git a/spec/models/concerns/optionally_search_spec.rb b/spec/models/concerns/optionally_search_spec.rb
index c8e2e6da51f..8067ad50322 100644
--- a/spec/models/concerns/optionally_search_spec.rb
+++ b/spec/models/concerns/optionally_search_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe OptionallySearch do
it 'delegates to the search method' do
expect(model)
.to receive(:search)
- .with('foo', {})
+ .with('foo')
.and_call_original
expect(model.optionally_search('foo')).to eq(['foo', {}])
diff --git a/spec/models/container_expiration_policy_spec.rb b/spec/models/container_expiration_policy_spec.rb
index 1d9dbe8a867..32ec5ed161a 100644
--- a/spec/models/container_expiration_policy_spec.rb
+++ b/spec/models/container_expiration_policy_spec.rb
@@ -38,10 +38,43 @@ RSpec.describe ContainerExpirationPolicy, type: :model do
it { is_expected.not_to allow_value('foo').for(:keep_n) }
end
+ describe '#disable!' do
+ let_it_be(:policy) { create(:container_expiration_policy) }
+
+ subject { policy.disable! }
+
+ it 'disables the container expiration policy' do
+ expect { subject }.to change { policy.reload.enabled }.from(true).to(false)
+ end
+ end
+
+ describe '#policy_params' do
+ let_it_be(:policy) { create(:container_expiration_policy) }
+
+ let(:expected) do
+ {
+ 'older_than' => policy.older_than,
+ 'keep_n' => policy.keep_n,
+ 'name_regex' => policy.name_regex,
+ 'name_regex_keep' => policy.name_regex_keep
+ }
+ end
+
+ subject { policy.policy_params }
+
+ it { is_expected.to eq(expected) }
+ end
+
context 'with a set of regexps' do
+ let_it_be(:container_expiration_policy) { create(:container_expiration_policy) }
+
+ subject { container_expiration_policy }
+
valid_regexps = %w[master .* v.+ v10.1.* (?:v.+|master|release)]
invalid_regexps = ['[', '(?:v.+|master|release']
+ it { is_expected.to validate_presence_of(:name_regex) }
+
valid_regexps.each do |valid_regexp|
it { is_expected.to allow_value(valid_regexp).for(:name_regex) }
it { is_expected.to allow_value(valid_regexp).for(:name_regex_keep) }
@@ -57,6 +90,8 @@ RSpec.describe ContainerExpirationPolicy, type: :model do
subject { container_expiration_policy }
+ it { is_expected.not_to validate_presence_of(:name_regex) }
+
valid_regexps.each do |valid_regexp|
it { is_expected.to allow_value(valid_regexp).for(:name_regex) }
it { is_expected.to allow_value(valid_regexp).for(:name_regex_keep) }
@@ -104,25 +139,15 @@ RSpec.describe ContainerExpirationPolicy, type: :model do
end
end
- describe '.executable' do
- subject { described_class.executable }
+ describe '.with_container_repositories' do
+ subject { described_class.with_container_repositories }
- let_it_be(:policy1) { create(:container_expiration_policy, :runnable) }
+ let_it_be(:policy1) { create(:container_expiration_policy) }
let_it_be(:container_repository1) { create(:container_repository, project: policy1.project) }
- let_it_be(:policy2) { create(:container_expiration_policy, :runnable) }
+ let_it_be(:policy2) { create(:container_expiration_policy) }
let_it_be(:container_repository2) { create(:container_repository, project: policy2.project) }
- let_it_be(:policy3) { create(:container_expiration_policy, :runnable) }
+ let_it_be(:policy3) { create(:container_expiration_policy) }
it { is_expected.to contain_exactly(policy1, policy2) }
end
-
- describe '#disable!' do
- let_it_be(:container_expiration_policy) { create(:container_expiration_policy) }
-
- subject { container_expiration_policy.disable! }
-
- it 'disables the container expiration policy' do
- expect { subject }.to change { container_expiration_policy.reload.enabled }.from(true).to(false)
- end
- end
end
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 2a7aaed5204..2adceb1c960 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -352,4 +352,20 @@ RSpec.describe ContainerRepository do
it { is_expected.to contain_exactly(repository) }
end
+
+ describe '.for_project_id' do
+ subject { described_class.for_project_id(project.id) }
+
+ it { is_expected.to contain_exactly(repository) }
+ end
+
+ describe '.waiting_for_cleanup' do
+ let_it_be(:repository_cleanup_scheduled) { create(:container_repository, :cleanup_scheduled) }
+ let_it_be(:repository_cleanup_unfinished) { create(:container_repository, :cleanup_unfinished) }
+ let_it_be(:repository_cleanup_ongoing) { create(:container_repository, :cleanup_ongoing) }
+
+ subject { described_class.waiting_for_cleanup }
+
+ it { is_expected.to contain_exactly(repository_cleanup_scheduled, repository_cleanup_unfinished) }
+ end
end
diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb
index 836c4139107..62380299ea0 100644
--- a/spec/models/custom_emoji_spec.rb
+++ b/spec/models/custom_emoji_spec.rb
@@ -13,20 +13,28 @@ RSpec.describe CustomEmoji do
describe 'exclusion of duplicated emoji' do
let(:emoji_name) { Gitlab::Emoji.emojis_names.sample }
+ let(:group) { create(:group, :private) }
it 'disallows emoji names of built-in emoji' do
- new_emoji = build(:custom_emoji, name: emoji_name)
+ new_emoji = build(:custom_emoji, name: emoji_name, group: group)
expect(new_emoji).not_to be_valid
expect(new_emoji.errors.messages).to eq(name: ["#{emoji_name} is already being used for another emoji"])
end
it 'disallows duplicate custom emoji names within namespace' do
- old_emoji = create(:custom_emoji)
- new_emoji = build(:custom_emoji, name: old_emoji.name, namespace: old_emoji.namespace)
+ old_emoji = create(:custom_emoji, group: group)
+ new_emoji = build(:custom_emoji, name: old_emoji.name, namespace: old_emoji.namespace, group: group)
expect(new_emoji).not_to be_valid
expect(new_emoji.errors.messages).to eq(name: ["has already been taken"])
end
+
+ it 'disallows non http and https file value' do
+ emoji = build(:custom_emoji, name: 'new-name', group: group, file: 'ftp://some-url.in')
+
+ expect(emoji).not_to be_valid
+ expect(emoji.errors.messages).to eq(file: ["is blocked: Only allowed schemes are http, https"])
+ end
end
end
diff --git a/spec/models/dependency_proxy/blob_spec.rb b/spec/models/dependency_proxy/blob_spec.rb
new file mode 100644
index 00000000000..7c8a1eb95e8
--- /dev/null
+++ b/spec/models/dependency_proxy/blob_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe DependencyProxy::Blob, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:group) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:group) }
+ it { is_expected.to validate_presence_of(:file) }
+ it { is_expected.to validate_presence_of(:file_name) }
+ end
+
+ describe '.total_size' do
+ it 'returns 0 if no files' do
+ expect(described_class.total_size).to eq(0)
+ end
+
+ it 'returns a correct sum of all files sizes' do
+ create(:dependency_proxy_blob, size: 10)
+ create(:dependency_proxy_blob, size: 20)
+
+ expect(described_class.total_size).to eq(30)
+ end
+ end
+
+ describe '.find_or_build' do
+ let!(:blob) { create(:dependency_proxy_blob) }
+
+ it 'builds new instance if not found' do
+ expect(described_class.find_or_build('foo.gz')).not_to be_persisted
+ end
+
+ it 'finds an existing blob' do
+ expect(described_class.find_or_build(blob.file_name)).to eq(blob)
+ end
+ end
+
+ describe 'file is being stored' do
+ subject { create(:dependency_proxy_blob) }
+
+ context 'when existing object has local store' do
+ it_behaves_like 'mounted file in local store'
+ end
+
+ context 'when direct upload is enabled' do
+ before do
+ stub_dependency_proxy_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'mounted file in object store'
+ end
+ end
+end
diff --git a/spec/models/dependency_proxy/group_setting_spec.rb b/spec/models/dependency_proxy/group_setting_spec.rb
new file mode 100644
index 00000000000..c4c4a877d50
--- /dev/null
+++ b/spec/models/dependency_proxy/group_setting_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe DependencyProxy::GroupSetting, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:group) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:group) }
+ end
+end
diff --git a/spec/models/dependency_proxy/registry_spec.rb b/spec/models/dependency_proxy/registry_spec.rb
new file mode 100644
index 00000000000..5bfa75a2eed
--- /dev/null
+++ b/spec/models/dependency_proxy/registry_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe DependencyProxy::Registry, type: :model do
+ let(:tag) { '2.3.5-alpine' }
+ let(:blob_sha) { '40bd001563085fc35165329ea1ff5c5ecbdbbeef' }
+
+ context 'image name without namespace' do
+ let(:image) { 'ruby' }
+
+ describe '#auth_url' do
+ it 'returns a correct auth url' do
+ expect(described_class.auth_url(image))
+ .to eq('https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/ruby:pull')
+ end
+ end
+
+ describe '#manifest_url' do
+ it 'returns a correct manifest url' do
+ expect(described_class.manifest_url(image, tag))
+ .to eq('https://registry-1.docker.io/v2/library/ruby/manifests/2.3.5-alpine')
+ end
+ end
+
+ describe '#blob_url' do
+ it 'returns a correct blob url' do
+ expect(described_class.blob_url(image, blob_sha))
+ .to eq('https://registry-1.docker.io/v2/library/ruby/blobs/40bd001563085fc35165329ea1ff5c5ecbdbbeef')
+ end
+ end
+ end
+
+ context 'image name with namespace' do
+ let(:image) { 'foo/ruby' }
+
+ describe '#auth_url' do
+ it 'returns a correct auth url' do
+ expect(described_class.auth_url(image))
+ .to eq('https://auth.docker.io/token?service=registry.docker.io&scope=repository:foo/ruby:pull')
+ end
+ end
+
+ describe '#manifest_url' do
+ it 'returns a correct manifest url' do
+ expect(described_class.manifest_url(image, tag))
+ .to eq('https://registry-1.docker.io/v2/foo/ruby/manifests/2.3.5-alpine')
+ end
+ end
+
+ describe '#blob_url' do
+ it 'returns a correct blob url' do
+ expect(described_class.blob_url(image, blob_sha))
+ .to eq('https://registry-1.docker.io/v2/foo/ruby/blobs/40bd001563085fc35165329ea1ff5c5ecbdbbeef')
+ end
+ end
+ end
+end
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 00114a94b56..d4ccaa6a10e 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe DeployKey, :mailer do
describe "Associations" do
it { is_expected.to have_many(:deploy_keys_projects) }
it { is_expected.to have_many(:projects) }
+ it { is_expected.to have_many(:protected_branch_push_access_levels) }
end
describe 'notification' do
@@ -40,4 +41,56 @@ RSpec.describe DeployKey, :mailer do
end
end
end
+
+ describe '.with_write_access_for_project' do
+ let_it_be(:project) { create(:project, :private) }
+
+ subject { described_class.with_write_access_for_project(project) }
+
+ context 'when no project is passed in' do
+ let(:project) { nil }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when a project is passed in' do
+ let_it_be(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project) }
+ let_it_be(:deploy_key) { deploy_keys_project.deploy_key }
+
+ it 'only returns deploy keys with write access' do
+ create(:deploy_keys_project, project: project)
+
+ is_expected.to contain_exactly(deploy_key)
+ end
+
+ it 'returns deploy keys only for this project' do
+ other_project = create(:project)
+ create(:deploy_keys_project, :write_access, project: other_project)
+
+ is_expected.to contain_exactly(deploy_key)
+ end
+
+ context 'and a specific deploy key is passed in' do
+ subject { described_class.with_write_access_for_project(project, deploy_key: specific_deploy_key) }
+
+ context 'and this deploy key is not linked to the project' do
+ let(:specific_deploy_key) { create(:deploy_key) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'and this deploy key has not write access to the project' do
+ let(:specific_deploy_key) { create(:deploy_key, deploy_keys_projects: [create(:deploy_keys_project, project: project)]) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'and this deploy key has write access to the project' do
+ let(:specific_deploy_key) { create(:deploy_key, deploy_keys_projects: [create(:deploy_keys_project, :write_access, project: project)]) }
+
+ it { is_expected.to contain_exactly(specific_deploy_key) }
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 7dd4d3129de..ccc2c64e02c 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -13,21 +13,6 @@ RSpec.describe DeployKeysProject do
it { is_expected.to validate_presence_of(:deploy_key) }
end
- describe '.with_deploy_keys' do
- subject(:scoped_query) { described_class.with_deploy_keys.last }
-
- it 'includes deploy_keys in query' do
- project = create(:project)
- create(:deploy_keys_project, project: project, deploy_key: create(:deploy_key))
-
- includes_query_count = ActiveRecord::QueryRecorder.new { scoped_query }.count
- deploy_key_query_count = ActiveRecord::QueryRecorder.new { scoped_query.deploy_key }.count
-
- expect(includes_query_count).to eq(2)
- expect(deploy_key_query_count).to eq(0)
- end
- end
-
describe "Destroying" do
let(:project) { create(:project) }
subject { create(:deploy_keys_project, project: project) }
diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb
index 60a3e3fc0e2..c7e1d5fc0d5 100644
--- a/spec/models/deploy_token_spec.rb
+++ b/spec/models/deploy_token_spec.rb
@@ -124,6 +124,39 @@ RSpec.describe DeployToken do
end
end
+ # override the default PolicyActor implementation that always returns false
+ describe "#deactivated?" do
+ context "when it has been revoked" do
+ it 'returns true' do
+ deploy_token.revoke!
+
+ expect(deploy_token.deactivated?).to be_truthy
+ end
+ end
+
+ context "when it hasn't been revoked and is not expired" do
+ it 'returns false' do
+ expect(deploy_token.deactivated?).to be_falsy
+ end
+ end
+
+ context "when it hasn't been revoked and is expired" do
+ it 'returns false' do
+ deploy_token.update_attribute(:expires_at, Date.today - 5.days)
+
+ expect(deploy_token.deactivated?).to be_truthy
+ end
+ end
+
+ context "when it hasn't been revoked and has no expiry" do
+ let(:deploy_token) { create(:deploy_token, expires_at: nil) }
+
+ it 'returns false' do
+ expect(deploy_token.deactivated?).to be_falsy
+ end
+ end
+ end
+
describe '#username' do
context 'persisted records' do
it 'returns a default username if none is set' do
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 3e855584c38..9afacd518af 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -114,14 +114,6 @@ RSpec.describe Deployment do
deployment.run!
end
- it 'does not execute Deployments::ExecuteHooksWorker when feature is disabled' do
- stub_feature_flags(ci_send_deployment_hook_when_start: false)
- expect(Deployments::ExecuteHooksWorker)
- .not_to receive(:perform_async).with(deployment.id)
-
- deployment.run!
- end
-
it 'executes Deployments::DropOlderDeploymentsWorker asynchronously' do
expect(Deployments::DropOlderDeploymentsWorker)
.to receive(:perform_async).once.with(deployment.id)
diff --git a/spec/models/design_management/design_at_version_spec.rb b/spec/models/design_management/design_at_version_spec.rb
index 220de80a52a..a7cf6a9652b 100644
--- a/spec/models/design_management/design_at_version_spec.rb
+++ b/spec/models/design_management/design_at_version_spec.rb
@@ -185,7 +185,7 @@ RSpec.describe DesignManagement::DesignAtVersion do
end
describe 'validations' do
- subject(:design_at_version) { build(:design_at_version) }
+ subject(:design_at_version) { build_stubbed(:design_at_version) }
it { is_expected.to be_valid }
diff --git a/spec/models/design_management/design_spec.rb b/spec/models/design_management/design_spec.rb
index 2ce9f00a056..d3ce2f2d48f 100644
--- a/spec/models/design_management/design_spec.rb
+++ b/spec/models/design_management/design_spec.rb
@@ -11,6 +11,14 @@ RSpec.describe DesignManagement::Design do
let_it_be(:design3) { create(:design, :with_versions, issue: issue, versions_count: 1) }
let_it_be(:deleted_design) { create(:design, :with_versions, deleted: true) }
+ it_behaves_like 'AtomicInternalId', validate_presence: true do
+ let(:internal_id_attribute) { :iid }
+ let(:instance) { build(:design, issue: issue) }
+ let(:scope) { :project }
+ let(:scope_attrs) { { project: instance.project } }
+ let(:usage) { :design_management_designs }
+ end
+
it_behaves_like 'a class that supports relative positioning' do
let_it_be(:relative_parent) { create(:issue) }
@@ -23,8 +31,20 @@ RSpec.describe DesignManagement::Design do
it { is_expected.to belong_to(:issue) }
it { is_expected.to have_many(:actions) }
it { is_expected.to have_many(:versions) }
+ it { is_expected.to have_many(:authors) }
it { is_expected.to have_many(:notes).dependent(:delete_all) }
it { is_expected.to have_many(:user_mentions) }
+
+ describe '#authors' do
+ it 'returns unique version authors', :aggregate_failures do
+ author = create(:user)
+ create_list(:design_version, 2, designs: [design1], author: author)
+ version_authors = design1.versions.map(&:author)
+
+ expect(version_authors).to contain_exactly(issue.author, author, author)
+ expect(design1.authors).to contain_exactly(issue.author, author)
+ end
+ end
end
describe 'validations' do
@@ -326,6 +346,38 @@ RSpec.describe DesignManagement::Design do
end
end
+ describe '#participants' do
+ let_it_be_with_refind(:design) { create(:design, issue: issue) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:version_author) { create(:user) }
+ let_it_be(:note_author) { create(:user) }
+ let_it_be(:mentioned_user) { create(:user) }
+ let_it_be(:design_version) { create(:design_version, :committed, designs: [design], author: version_author) }
+ let_it_be(:note) do
+ create(:diff_note_on_design,
+ noteable: design,
+ issue: issue,
+ project: issue.project,
+ author: note_author,
+ note: mentioned_user.to_reference
+ )
+ end
+
+ subject { design.participants(current_user) }
+
+ it { is_expected.to be_empty }
+
+ context 'when participants can read the project' do
+ before do
+ design.project.add_guest(version_author)
+ design.project.add_guest(note_author)
+ design.project.add_guest(mentioned_user)
+ end
+
+ it { is_expected.to contain_exactly(version_author, note_author, mentioned_user) }
+ end
+ end
+
describe "#new_design?" do
let(:design) { design1 }
diff --git a/spec/models/design_management/version_spec.rb b/spec/models/design_management/version_spec.rb
index cd52f4129dc..e004ad024bc 100644
--- a/spec/models/design_management/version_spec.rb
+++ b/spec/models/design_management/version_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe DesignManagement::Version do
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:sha) }
it { is_expected.to validate_presence_of(:designs) }
- it { is_expected.to validate_presence_of(:issue_id) }
+ it { is_expected.to validate_presence_of(:issue) }
it { is_expected.to validate_uniqueness_of(:sha).scoped_to(:issue_id).case_insensitive }
end
diff --git a/spec/models/diff_viewer/image_spec.rb b/spec/models/diff_viewer/image_spec.rb
new file mode 100644
index 00000000000..e959a7d5eb2
--- /dev/null
+++ b/spec/models/diff_viewer/image_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe DiffViewer::Image do
+ describe '.can_render?' do
+ let(:diff_file) { double(Gitlab::Diff::File) }
+ let(:blob) { double(Gitlab::Git::Blob, binary_in_repo?: true, extension: 'png') }
+
+ subject { described_class.can_render?(diff_file, verify_binary: false) }
+
+ it 'returns false if both old and new blob are absent' do
+ allow(diff_file).to receive(:old_blob) { nil }
+ allow(diff_file).to receive(:new_blob) { nil }
+
+ is_expected.to be_falsy
+ end
+
+ it 'returns true if the old blob is present' do
+ allow(diff_file).to receive(:old_blob) { blob }
+ allow(diff_file).to receive(:new_blob) { nil }
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns true if the new blob is present' do
+ allow(diff_file).to receive(:old_blob) { nil }
+ allow(diff_file).to receive(:new_blob) { blob }
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns true if both old and new blobs are present' do
+ allow(diff_file).to receive(:old_blob) { blob }
+ allow(diff_file).to receive(:new_blob) { blob }
+
+ is_expected.to be_truthy
+ end
+ end
+end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 06d3e9da286..179f2a1b0e0 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -982,6 +982,22 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
end
end
+ describe '#has_running_deployments?' do
+ subject { environment.has_running_deployments? }
+
+ it 'return false when no deployments exist' do
+ is_expected.to eq(false)
+ end
+
+ context 'when deployment is running on the environment' do
+ let!(:deployment) { create(:deployment, :running, environment: environment) }
+
+ it 'return true' do
+ is_expected.to eq(true)
+ end
+ end
+ end
+
describe '#metrics' do
let(:project) { create(:prometheus_project) }
diff --git a/spec/models/experiment_spec.rb b/spec/models/experiment_spec.rb
index 64cd2da4621..587f410c9be 100644
--- a/spec/models/experiment_spec.rb
+++ b/spec/models/experiment_spec.rb
@@ -7,32 +7,6 @@ RSpec.describe Experiment do
describe 'associations' do
it { is_expected.to have_many(:experiment_users) }
- it { is_expected.to have_many(:users) }
- it { is_expected.to have_many(:control_group_users) }
- it { is_expected.to have_many(:experimental_group_users) }
-
- describe 'control_group_users and experimental_group_users' do
- let(:experiment) { create(:experiment) }
- let(:control_group_user) { build(:user) }
- let(:experimental_group_user) { build(:user) }
-
- before do
- experiment.control_group_users << control_group_user
- experiment.experimental_group_users << experimental_group_user
- end
-
- describe 'control_group_users' do
- subject { experiment.control_group_users }
-
- it { is_expected.to contain_exactly(control_group_user) }
- end
-
- describe 'experimental_group_users' do
- subject { experiment.experimental_group_users }
-
- it { is_expected.to contain_exactly(experimental_group_user) }
- end
- end
end
describe 'validations' do
@@ -42,71 +16,83 @@ RSpec.describe Experiment do
end
describe '.add_user' do
- let(:name) { :experiment_key }
- let(:user) { build(:user) }
+ let_it_be(:experiment_name) { :experiment_key }
+ let_it_be(:user) { 'a user' }
+ let_it_be(:group) { 'a group' }
- let!(:experiment) { create(:experiment, name: name) }
+ subject(:add_user) { described_class.add_user(experiment_name, group, user) }
- subject { described_class.add_user(name, :control, user) }
-
- describe 'creating a new experiment record' do
- context 'an experiment with the provided name already exists' do
- it 'does not create a new experiment record' do
- expect { subject }.not_to change(Experiment, :count)
+ context 'when an experiment with the provided name does not exist' do
+ it 'creates a new experiment record' do
+ allow_next_instance_of(described_class) do |experiment|
+ allow(experiment).to receive(:record_user_and_group).with(user, group)
end
+ expect { add_user }.to change(described_class, :count).by(1)
end
- context 'an experiment with the provided name does not exist yet' do
- let(:experiment) { nil }
-
- it 'creates a new experiment record' do
- expect { subject }.to change(Experiment, :count).by(1)
+ it 'forwards the user and group_type to the instance' do
+ expect_next_instance_of(described_class) do |experiment|
+ expect(experiment).to receive(:record_user_and_group).with(user, group)
end
+ add_user
end
end
- describe 'creating a new experiment_user record' do
- context 'an experiment_user record for this experiment already exists' do
- before do
- subject
- end
+ context 'when an experiment with the provided name already exists' do
+ let_it_be(:experiment) { create(:experiment, name: experiment_name) }
- it 'does not create a new experiment_user record' do
- expect { subject }.not_to change(ExperimentUser, :count)
+ it 'does not create a new experiment record' do
+ allow_next_found_instance_of(described_class) do |experiment|
+ allow(experiment).to receive(:record_user_and_group).with(user, group)
end
+ expect { add_user }.not_to change(described_class, :count)
end
- context 'an experiment_user record for this experiment does not exist yet' do
- it 'creates a new experiment_user record' do
- expect { subject }.to change(ExperimentUser, :count).by(1)
- end
-
- it 'assigns the correct group_type to the experiment_user' do
- expect { subject }.to change { experiment.control_group_users.count }.by(1)
+ it 'forwards the user and group_type to the instance' do
+ expect_next_found_instance_of(described_class) do |experiment|
+ expect(experiment).to receive(:record_user_and_group).with(user, group)
end
+ add_user
end
end
end
- describe '#add_control_user' do
- let(:experiment) { create(:experiment) }
- let(:user) { build(:user) }
+ describe '#record_user_and_group' do
+ let_it_be(:experiment) { create(:experiment) }
+ let_it_be(:user) { create(:user) }
- subject { experiment.add_control_user(user) }
+ let(:group) { :control }
- it 'creates a new experiment_user record and assigns the correct group_type' do
- expect { subject }.to change { experiment.control_group_users.count }.by(1)
+ subject(:record_user_and_group) { experiment.record_user_and_group(user, group) }
+
+ context 'when an experiment_user does not yet exist for the given user' do
+ it 'creates a new experiment_user record' do
+ expect { record_user_and_group }.to change(ExperimentUser, :count).by(1)
+ end
+
+ it 'assigns the correct group_type to the experiment_user' do
+ record_user_and_group
+ expect(ExperimentUser.last.group_type).to eq('control')
+ end
end
- end
- describe '#add_experimental_user' do
- let(:experiment) { create(:experiment) }
- let(:user) { build(:user) }
+ context 'when an experiment_user already exists for the given user' do
+ before do
+ # Create an existing experiment_user for this experiment and the :control group
+ experiment.record_user_and_group(user, :control)
+ end
+
+ it 'does not create a new experiment_user record' do
+ expect { record_user_and_group }.not_to change(ExperimentUser, :count)
+ end
- subject { experiment.add_experimental_user(user) }
+ context 'but the group_type has changed' do
+ let(:group) { :experimental }
- it 'creates a new experiment_user record and assigns the correct group_type' do
- expect { subject }.to change { experiment.experimental_group_users.count }.by(1)
+ it 'updates the existing experiment_user record' do
+ expect { record_user_and_group }.to change { ExperimentUser.last.group_type }
+ end
+ end
end
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index cc29e20710a..dd1faf999b3 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -28,6 +28,8 @@ RSpec.describe Group do
it { is_expected.to have_many(:iterations) }
it { is_expected.to have_many(:group_deploy_keys) }
it { is_expected.to have_many(:services) }
+ it { is_expected.to have_one(:dependency_proxy_setting) }
+ it { is_expected.to have_many(:dependency_proxy_blobs) }
describe '#members & #requesters' do
let(:requester) { create(:user) }
@@ -308,8 +310,10 @@ RSpec.describe Group do
end
describe 'scopes' do
- let!(:private_group) { create(:group, :private) }
- let!(:internal_group) { create(:group, :internal) }
+ let_it_be(:private_group) { create(:group, :private) }
+ let_it_be(:internal_group) { create(:group, :internal) }
+ let_it_be(:user1) { create(:user) }
+ let_it_be(:user2) { create(:user) }
describe 'public_only' do
subject { described_class.public_only.to_a }
@@ -328,6 +332,27 @@ RSpec.describe Group do
it { is_expected.to match_array([private_group, internal_group]) }
end
+
+ describe 'for_authorized_group_members' do
+ let_it_be(:group_member1) { create(:group_member, source: private_group, user_id: user1.id, access_level: Gitlab::Access::OWNER) }
+
+ it do
+ result = described_class.for_authorized_group_members([user1.id, user2.id])
+
+ expect(result).to match_array([private_group])
+ end
+ end
+
+ describe 'for_authorized_project_members' do
+ let_it_be(:project) { create(:project, group: internal_group) }
+ let_it_be(:project_member1) { create(:project_member, source: project, user_id: user1.id, access_level: Gitlab::Access::DEVELOPER) }
+
+ it do
+ result = described_class.for_authorized_project_members([user1.id, user2.id])
+
+ expect(result).to match_array([internal_group])
+ end
+ end
end
describe '#to_reference' do
@@ -944,23 +969,72 @@ RSpec.describe Group do
context 'expanded group members' do
let(:indirect_user) { create(:user) }
- it 'enables two_factor_requirement for subgroup member' do
- subgroup = create(:group, :nested, parent: group)
- subgroup.add_user(indirect_user, GroupMember::OWNER)
+ context 'two_factor_requirement is enabled' do
+ context 'two_factor_requirement is also enabled for ancestor group' do
+ it 'enables two_factor_requirement for subgroup member' do
+ subgroup = create(:group, :nested, parent: group)
+ subgroup.add_user(indirect_user, GroupMember::OWNER)
- group.update!(require_two_factor_authentication: true)
+ group.update!(require_two_factor_authentication: true)
+
+ expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_truthy
+ end
+ end
+
+ context 'two_factor_requirement is disabled for ancestor group' do
+ it 'enables two_factor_requirement for subgroup member' do
+ subgroup = create(:group, :nested, parent: group, require_two_factor_authentication: true)
+ subgroup.add_user(indirect_user, GroupMember::OWNER)
+
+ group.update!(require_two_factor_authentication: false)
+
+ expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_truthy
+ end
+
+ it 'enable two_factor_requirement for ancestor group member' do
+ ancestor_group = create(:group)
+ ancestor_group.add_user(indirect_user, GroupMember::OWNER)
+ group.update!(parent: ancestor_group)
+
+ group.update!(require_two_factor_authentication: true)
- expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_truthy
+ expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_truthy
+ end
+ end
end
- it 'does not enable two_factor_requirement for ancestor group member' do
- ancestor_group = create(:group)
- ancestor_group.add_user(indirect_user, GroupMember::OWNER)
- group.update!(parent: ancestor_group)
+ context 'two_factor_requirement is disabled' do
+ context 'two_factor_requirement is enabled for ancestor group' do
+ it 'enables two_factor_requirement for subgroup member' do
+ subgroup = create(:group, :nested, parent: group)
+ subgroup.add_user(indirect_user, GroupMember::OWNER)
- group.update!(require_two_factor_authentication: true)
+ group.update!(require_two_factor_authentication: true)
+
+ expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_truthy
+ end
+ end
+
+ context 'two_factor_requirement is also disabled for ancestor group' do
+ it 'disables two_factor_requirement for subgroup member' do
+ subgroup = create(:group, :nested, parent: group)
+ subgroup.add_user(indirect_user, GroupMember::OWNER)
- expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_falsey
+ group.update!(require_two_factor_authentication: false)
+
+ expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_falsey
+ end
+
+ it 'disables two_factor_requirement for ancestor group member' do
+ ancestor_group = create(:group, require_two_factor_authentication: false)
+ indirect_user.update!(require_two_factor_authentication_from_group: true)
+ ancestor_group.add_user(indirect_user, GroupMember::OWNER)
+
+ group.update!(require_two_factor_authentication: false)
+
+ expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_falsey
+ end
+ end
end
end
@@ -1591,4 +1665,47 @@ RSpec.describe Group do
end
end
end
+
+ describe 'has_project_with_service_desk_enabled?' do
+ let_it_be(:group) { create(:group, :private) }
+
+ subject { group.has_project_with_service_desk_enabled? }
+
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(true)
+ end
+
+ context 'when service desk is enabled' do
+ context 'for top level group' do
+ let_it_be(:project) { create(:project, group: group, service_desk_enabled: true) }
+
+ it { is_expected.to eq(true) }
+
+ context 'when service desk is not supported' do
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ context 'for subgroup project' do
+ let_it_be(:subgroup) { create(:group, :private, parent: group)}
+ let_it_be(:project) { create(:project, group: subgroup, service_desk_enabled: true) }
+
+ it { is_expected.to eq(true) }
+ end
+ end
+
+ context 'when none of group child projects has service desk enabled' do
+ let_it_be(:project) { create(:project, group: group, service_desk_enabled: false) }
+
+ before do
+ project.update(service_desk_enabled: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
end
diff --git a/spec/models/instance_metadata_spec.rb b/spec/models/instance_metadata_spec.rb
new file mode 100644
index 00000000000..1835dc8a9af
--- /dev/null
+++ b/spec/models/instance_metadata_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe InstanceMetadata do
+ it 'has the correct properties' do
+ expect(subject).to have_attributes(
+ version: Gitlab::VERSION,
+ revision: Gitlab.revision
+ )
+ end
+end
diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb
index 751e8724872..07f62b9de55 100644
--- a/spec/models/internal_id_spec.rb
+++ b/spec/models/internal_id_spec.rb
@@ -6,8 +6,9 @@ RSpec.describe InternalId do
let(:project) { create(:project) }
let(:usage) { :issues }
let(:issue) { build(:issue, project: project) }
+ let(:id_subject) { issue }
let(:scope) { { project: project } }
- let(:init) { ->(s) { s.project.issues.size } }
+ let(:init) { ->(issue, scope) { issue&.project&.issues&.size || Issue.where(**scope).count } }
it_behaves_like 'having unique enum values'
@@ -39,7 +40,7 @@ RSpec.describe InternalId do
end
describe '.generate_next' do
- subject { described_class.generate_next(issue, scope, usage, init) }
+ subject { described_class.generate_next(id_subject, scope, usage, init) }
context 'in the absence of a record' do
it 'creates a record if not yet present' do
@@ -88,6 +89,14 @@ RSpec.describe InternalId do
expect(normalized).to eq((0..seq.size - 1).to_a)
end
+
+ context 'there are no instances to pass in' do
+ let(:id_subject) { Issue }
+
+ it 'accepts classes instead' do
+ expect(subject).to eq(1)
+ end
+ end
end
describe '.reset' do
@@ -130,7 +139,7 @@ RSpec.describe InternalId do
describe '.track_greatest' do
let(:value) { 9001 }
- subject { described_class.track_greatest(issue, scope, usage, value, init) }
+ subject { described_class.track_greatest(id_subject, scope, usage, value, init) }
context 'in the absence of a record' do
it 'creates a record if not yet present' do
@@ -166,6 +175,14 @@ RSpec.describe InternalId do
expect(subject).to eq 10_001
end
end
+
+ context 'there are no instances to pass in' do
+ let(:id_subject) { Issue }
+
+ it 'accepts classes instead' do
+ expect(subject).to eq(value)
+ end
+ end
end
describe '#increment_and_save!' do
diff --git a/spec/models/issue_link_spec.rb b/spec/models/issue_link_spec.rb
index 00791d4a48b..ef41108ebea 100644
--- a/spec/models/issue_link_spec.rb
+++ b/spec/models/issue_link_spec.rb
@@ -27,7 +27,14 @@ RSpec.describe IssueLink do
.with_message(/already related/)
end
- context 'self relation' do
+ it 'is not valid if an opposite link already exists' do
+ issue_link = build(:issue_link, source: subject.target, target: subject.source)
+
+ expect(issue_link).to be_invalid
+ expect(issue_link.errors[:source]).to include('is already related to this issue')
+ end
+
+ context 'when it relates to itself' do
let(:issue) { create :issue }
context 'cannot be validated' do
diff --git a/spec/models/issues/csv_import_spec.rb b/spec/models/issues/csv_import_spec.rb
new file mode 100644
index 00000000000..2911a79e505
--- /dev/null
+++ b/spec/models/issues/csv_import_spec.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Issues::CsvImport, type: :model do
+ describe 'associations' do
+ it { is_expected.to belong_to(:project).required }
+ it { is_expected.to belong_to(:user).required }
+ end
+end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 1e14864676c..3d33a39d353 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -108,7 +108,7 @@ RSpec.describe Key, :mailer do
expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid
end
- where(:factory, :chars, :expected_sections) do
+ where(:factory, :characters, :expected_sections) do
[
[:key, ["\n", "\r\n"], 3],
[:key, [' ', ' '], 3],
@@ -122,7 +122,7 @@ RSpec.describe Key, :mailer do
let!(:original_fingerprint_sha256) { key.fingerprint_sha256 }
it 'accepts a key with blank space characters after stripping them' do
- modified_key = key.key.insert(100, chars.first).insert(40, chars.last)
+ modified_key = key.key.insert(100, characters.first).insert(40, characters.last)
_, content = modified_key.split
key.update!(key: modified_key)
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 118b1492cd6..1a791820f1b 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -252,12 +252,17 @@ RSpec.describe Member do
end
describe '.last_ten_days_excluding_today' do
- let_it_be(:created_today) { create(:group_member, created_at: Date.today.beginning_of_day) }
- let_it_be(:created_yesterday) { create(:group_member, created_at: 1.day.ago) }
- let_it_be(:created_eleven_days_ago) { create(:group_member, created_at: 11.days.ago) }
+ let_it_be(:now) { Time.current }
+ let_it_be(:created_today) { create(:group_member, created_at: now.beginning_of_day) }
+ let_it_be(:created_yesterday) { create(:group_member, created_at: now - 1.day) }
+ let_it_be(:created_eleven_days_ago) { create(:group_member, created_at: now - 11.days) }
subject { described_class.last_ten_days_excluding_today }
+ before do
+ travel_to now
+ end
+
it { is_expected.to include(created_yesterday) }
it { is_expected.not_to include(created_today, created_eleven_days_ago) }
end
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 9af620e70a5..2b24e2d6455 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -4,9 +4,10 @@ require 'spec_helper'
RSpec.describe GroupMember do
context 'scopes' do
+ let_it_be(:user_1) { create(:user) }
+ let_it_be(:user_2) { create(:user) }
+
it 'counts users by group ID' do
- user_1 = create(:user)
- user_2 = create(:user)
group_1 = create(:group)
group_2 = create(:group)
@@ -25,6 +26,15 @@ RSpec.describe GroupMember do
expect(described_class.of_ldap_type).to eq([group_member])
end
end
+
+ describe '.with_user' do
+ it 'returns requested user' do
+ group_member = create(:group_member, user: user_2)
+ create(:group_member, user: user_1)
+
+ expect(described_class.with_user(user_2)).to eq([group_member])
+ end
+ end
end
describe '.access_level_roles' do
diff --git a/spec/models/merge_request/cleanup_schedule_spec.rb b/spec/models/merge_request/cleanup_schedule_spec.rb
new file mode 100644
index 00000000000..925d287088b
--- /dev/null
+++ b/spec/models/merge_request/cleanup_schedule_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequest::CleanupSchedule do
+ describe 'associations' do
+ it { is_expected.to belong_to(:merge_request) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:scheduled_at) }
+ end
+
+ describe '.scheduled_merge_request_ids' do
+ let_it_be(:mr_cleanup_schedule_1) { create(:merge_request_cleanup_schedule, scheduled_at: 2.days.ago) }
+ let_it_be(:mr_cleanup_schedule_2) { create(:merge_request_cleanup_schedule, scheduled_at: 1.day.ago) }
+ let_it_be(:mr_cleanup_schedule_3) { create(:merge_request_cleanup_schedule, scheduled_at: 1.day.ago, completed_at: Time.current) }
+ let_it_be(:mr_cleanup_schedule_4) { create(:merge_request_cleanup_schedule, scheduled_at: 4.days.ago) }
+ let_it_be(:mr_cleanup_schedule_5) { create(:merge_request_cleanup_schedule, scheduled_at: 3.days.ago) }
+ let_it_be(:mr_cleanup_schedule_6) { create(:merge_request_cleanup_schedule, scheduled_at: 1.day.from_now) }
+ let_it_be(:mr_cleanup_schedule_7) { create(:merge_request_cleanup_schedule, scheduled_at: 5.days.ago) }
+
+ it 'only includes incomplete schedule within the specified limit' do
+ expect(described_class.scheduled_merge_request_ids(4)).to eq([
+ mr_cleanup_schedule_2.merge_request_id,
+ mr_cleanup_schedule_1.merge_request_id,
+ mr_cleanup_schedule_5.merge_request_id,
+ mr_cleanup_schedule_4.merge_request_id
+ ])
+ end
+ end
+end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index ddb3ffdda2f..9574c57e46c 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -30,6 +30,7 @@ RSpec.describe MergeRequest, factory_default: :keep do
it { is_expected.to have_many(:resource_state_events) }
it { is_expected.to have_many(:draft_notes) }
it { is_expected.to have_many(:reviews).inverse_of(:merge_request) }
+ it { is_expected.to have_one(:cleanup_schedule).inverse_of(:merge_request) }
context 'for forks' do
let!(:project) { create(:project) }
@@ -79,6 +80,18 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
+ describe '.with_jira_issue_keys' do
+ let_it_be(:mr_with_jira_title) { create(:merge_request, :unique_branches, title: 'Fix TEST-123') }
+ let_it_be(:mr_with_jira_description) { create(:merge_request, :unique_branches, description: 'this closes TEST-321') }
+ let_it_be(:mr_without_jira_reference) { create(:merge_request, :unique_branches) }
+
+ subject { described_class.with_jira_issue_keys }
+
+ it { is_expected.to contain_exactly(mr_with_jira_title, mr_with_jira_description) }
+
+ it { is_expected.not_to include(mr_without_jira_reference) }
+ end
+
describe '#squash_in_progress?' do
let(:repo_path) do
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
diff --git a/spec/models/namespace/root_storage_statistics_spec.rb b/spec/models/namespace/root_storage_statistics_spec.rb
index 92a8d17a2a8..b725d2366a1 100644
--- a/spec/models/namespace/root_storage_statistics_spec.rb
+++ b/spec/models/namespace/root_storage_statistics_spec.rb
@@ -45,6 +45,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
total_storage_size = stat1.storage_size + stat2.storage_size
total_snippets_size = stat1.snippets_size + stat2.snippets_size
total_pipeline_artifacts_size = stat1.pipeline_artifacts_size + stat2.pipeline_artifacts_size
+ total_uploads_size = stat1.uploads_size + stat2.uploads_size
expect(root_storage_statistics.repository_size).to eq(total_repository_size)
expect(root_storage_statistics.wiki_size).to eq(total_wiki_size)
@@ -54,6 +55,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
expect(root_storage_statistics.storage_size).to eq(total_storage_size)
expect(root_storage_statistics.snippets_size).to eq(total_snippets_size)
expect(root_storage_statistics.pipeline_artifacts_size).to eq(total_pipeline_artifacts_size)
+ expect(root_storage_statistics.uploads_size).to eq(total_uploads_size)
end
it 'works when there are no projects' do
diff --git a/spec/models/namespace_setting_spec.rb b/spec/models/namespace_setting_spec.rb
index c6e8d5b129c..59b7510051f 100644
--- a/spec/models/namespace_setting_spec.rb
+++ b/spec/models/namespace_setting_spec.rb
@@ -36,13 +36,10 @@ RSpec.describe NamespaceSetting, type: :model do
context "when an empty string" do
before do
- namespace_settings.default_branch_name = ''
+ namespace_settings.default_branch_name = ""
end
- it "returns an error" do
- expect(namespace_settings.valid?).to be_falsey
- expect(namespace_settings.errors.full_messages).not_to be_empty
- end
+ it_behaves_like "doesn't return an error"
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 91b18f346c6..85f9005052e 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -147,30 +147,45 @@ RSpec.describe Namespace do
end
describe '.search' do
- let(:namespace) { create(:namespace) }
+ let_it_be(:first_namespace) { build(:namespace, name: 'my first namespace', path: 'old-path').tap(&:save!) }
+ let_it_be(:parent_namespace) { build(:namespace, name: 'my parent namespace', path: 'parent-path').tap(&:save!) }
+ let_it_be(:second_namespace) { build(:namespace, name: 'my second namespace', path: 'new-path', parent: parent_namespace).tap(&:save!) }
+ let_it_be(:project_with_same_path) { create(:project, id: second_namespace.id, path: first_namespace.path) }
it 'returns namespaces with a matching name' do
- expect(described_class.search(namespace.name)).to eq([namespace])
+ expect(described_class.search('my first namespace')).to eq([first_namespace])
end
it 'returns namespaces with a partially matching name' do
- expect(described_class.search(namespace.name[0..2])).to eq([namespace])
+ expect(described_class.search('first')).to eq([first_namespace])
end
it 'returns namespaces with a matching name regardless of the casing' do
- expect(described_class.search(namespace.name.upcase)).to eq([namespace])
+ expect(described_class.search('MY FIRST NAMESPACE')).to eq([first_namespace])
end
it 'returns namespaces with a matching path' do
- expect(described_class.search(namespace.path)).to eq([namespace])
+ expect(described_class.search('old-path')).to eq([first_namespace])
end
it 'returns namespaces with a partially matching path' do
- expect(described_class.search(namespace.path[0..2])).to eq([namespace])
+ expect(described_class.search('old')).to eq([first_namespace])
end
it 'returns namespaces with a matching path regardless of the casing' do
- expect(described_class.search(namespace.path.upcase)).to eq([namespace])
+ expect(described_class.search('OLD-PATH')).to eq([first_namespace])
+ end
+
+ it 'returns namespaces with a matching route path' do
+ expect(described_class.search('parent-path/new-path', include_parents: true)).to eq([second_namespace])
+ end
+
+ it 'returns namespaces with a partially matching route path' do
+ expect(described_class.search('parent-path/new', include_parents: true)).to eq([second_namespace])
+ end
+
+ it 'returns namespaces with a matching route path regardless of the casing' do
+ expect(described_class.search('PARENT-PATH/NEW-PATH', include_parents: true)).to eq([second_namespace])
end
end
@@ -672,7 +687,7 @@ RSpec.describe Namespace do
let!(:project) { create(:project_empty_repo, namespace: namespace) }
it 'has no repositories base directories to remove' do
- allow(GitlabShellWorker).to receive(:perform_in)
+ expect(GitlabShellWorker).not_to receive(:perform_in)
expect(File.exist?(path_in_dir)).to be(false)
@@ -855,8 +870,8 @@ RSpec.describe Namespace do
end
describe '#all_projects' do
- shared_examples 'all projects for a namespace' do
- let(:namespace) { create(:namespace) }
+ shared_examples 'all projects for a group' do
+ let(:namespace) { create(:group) }
let(:child) { create(:group, parent: namespace) }
let!(:project1) { create(:project_empty_repo, namespace: namespace) }
let!(:project2) { create(:project_empty_repo, namespace: child) }
@@ -865,30 +880,34 @@ RSpec.describe Namespace do
it { expect(child.all_projects.to_a).to match_array([project2]) }
end
- shared_examples 'all project examples' do
- include_examples 'all projects for a namespace'
+ shared_examples 'all projects for personal namespace' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:user_namespace) { create(:namespace, owner: user) }
+ let_it_be(:project) { create(:project, namespace: user_namespace) }
+
+ it { expect(user_namespace.all_projects.to_a).to match_array([project]) }
+ end
+ context 'with recursive approach' do
context 'when namespace is a group' do
- let_it_be(:namespace) { create(:group) }
+ include_examples 'all projects for a group'
+
+ it 'queries for the namespace and its descendants' do
+ expect(Project).to receive(:where).with(namespace: [namespace, child])
- include_examples 'all projects for a namespace'
+ namespace.all_projects
+ end
end
context 'when namespace is a user namespace' do
- let_it_be(:user) { create(:user) }
- let_it_be(:user_namespace) { create(:namespace, owner: user) }
- let_it_be(:project) { create(:project, namespace: user_namespace) }
+ include_examples 'all projects for personal namespace'
- it { expect(user_namespace.all_projects.to_a).to match_array([project]) }
- end
- end
+ it 'only queries for the namespace itself' do
+ expect(Project).to receive(:where).with(namespace: user_namespace)
- context 'with recursive approach' do
- before do
- stub_feature_flags(recursive_approach_for_all_projects: true)
+ user_namespace.all_projects
+ end
end
-
- include_examples 'all project examples'
end
context 'with route path wildcard approach' do
@@ -896,7 +915,13 @@ RSpec.describe Namespace do
stub_feature_flags(recursive_approach_for_all_projects: false)
end
- include_examples 'all project examples'
+ context 'when namespace is a group' do
+ include_examples 'all projects for a group'
+ end
+
+ context 'when namespace is a user namespace' do
+ include_examples 'all projects for personal namespace'
+ end
end
end
@@ -1246,24 +1271,6 @@ RSpec.describe Namespace do
expect(virtual_domain.lookup_paths).not_to be_empty
end
end
-
- it 'preloads project_feature and route' do
- project2 = create(:project, namespace: namespace)
- project3 = create(:project, namespace: namespace)
-
- project.mark_pages_as_deployed
- project2.mark_pages_as_deployed
- project3.mark_pages_as_deployed
-
- virtual_domain = namespace.pages_virtual_domain
-
- queries = ActiveRecord::QueryRecorder.new { virtual_domain.lookup_paths }
-
- # 1 to load projects
- # 1 to preload project features
- # 1 to load routes
- expect(queries.count).to eq(3)
- end
end
end
diff --git a/spec/models/operations/feature_flag_spec.rb b/spec/models/operations/feature_flag_spec.rb
index b4e941f2856..93dd7d4f0bb 100644
--- a/spec/models/operations/feature_flag_spec.rb
+++ b/spec/models/operations/feature_flag_spec.rb
@@ -261,4 +261,38 @@ RSpec.describe Operations::FeatureFlag do
expect(flags.map(&:id)).to eq([feature_flag.id, feature_flag_b.id])
end
end
+
+ describe '#hook_attrs' do
+ it 'includes expected attributes' do
+ hook_attrs = {
+ id: subject.id,
+ name: subject.name,
+ description: subject.description,
+ active: subject.active
+ }
+ expect(subject.hook_attrs).to eq(hook_attrs)
+ end
+ end
+
+ describe "#execute_hooks" do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:feature_flag) { create(:operations_feature_flag, project: project) }
+
+ it 'does not execute the hook when feature_flag event is disabled' do
+ create(:project_hook, project: project, feature_flag_events: false)
+ expect(WebHookWorker).not_to receive(:perform_async)
+
+ feature_flag.execute_hooks(user)
+ feature_flag.touch
+ end
+
+ it 'executes hook when feature_flag event is enabled' do
+ hook = create(:project_hook, project: project, feature_flag_events: true)
+ expect(WebHookWorker).to receive(:perform_async).with(hook.id, an_instance_of(Hash), 'feature_flag_hooks')
+
+ feature_flag.execute_hooks(user)
+ feature_flag.touch
+ end
+ end
end
diff --git a/spec/models/operations/feature_flags/user_list_spec.rb b/spec/models/operations/feature_flags/user_list_spec.rb
index 020416aa7bc..3a48d3389a3 100644
--- a/spec/models/operations/feature_flags/user_list_spec.rb
+++ b/spec/models/operations/feature_flags/user_list_spec.rb
@@ -92,6 +92,25 @@ RSpec.describe Operations::FeatureFlags::UserList do
end
end
+ describe '.for_name_like' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user_list_one) { create(:operations_feature_flag_user_list, project: project, name: 'one') }
+ let_it_be(:user_list_two) { create(:operations_feature_flag_user_list, project: project, name: 'list_two') }
+ let_it_be(:user_list_three) { create(:operations_feature_flag_user_list, project: project, name: 'list_three') }
+
+ it 'returns a found name' do
+ lists = project.operations_feature_flags_user_lists.for_name_like('list')
+
+ expect(lists).to contain_exactly(user_list_two, user_list_three)
+ end
+
+ it 'returns an empty array when no lists match the query' do
+ lists = project.operations_feature_flags_user_lists.for_name_like('no match')
+
+ expect(lists).to be_empty
+ end
+ end
+
it_behaves_like 'AtomicInternalId' do
let(:internal_id_attribute) { :iid }
let(:instance) { build(:operations_feature_flag_user_list) }
diff --git a/spec/models/packages/build_info_spec.rb b/spec/models/packages/build_info_spec.rb
new file mode 100644
index 00000000000..a4369c56fe2
--- /dev/null
+++ b/spec/models/packages/build_info_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::BuildInfo, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ it { is_expected.to belong_to(:pipeline) }
+ end
+end
diff --git a/spec/models/packages/package_file_build_info_spec.rb b/spec/models/packages/package_file_build_info_spec.rb
new file mode 100644
index 00000000000..18d6e720bf8
--- /dev/null
+++ b/spec/models/packages/package_file_build_info_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::PackageFileBuildInfo, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package_file) }
+ it { is_expected.to belong_to(:pipeline) }
+ end
+end
diff --git a/spec/models/packages/package_file_spec.rb b/spec/models/packages/package_file_spec.rb
index 6b992dbc2a5..ef09fb037e9 100644
--- a/spec/models/packages/package_file_spec.rb
+++ b/spec/models/packages/package_file_spec.rb
@@ -5,6 +5,8 @@ RSpec.describe Packages::PackageFile, type: :model do
describe 'relationships' do
it { is_expected.to belong_to(:package) }
it { is_expected.to have_one(:conan_file_metadatum) }
+ it { is_expected.to have_many(:package_file_build_infos).inverse_of(:package_file) }
+ it { is_expected.to have_many(:pipelines).through(:package_file_build_infos) }
end
describe 'validations' do
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index 41a731b87e9..705a1991845 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -10,6 +10,8 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to have_many(:package_files).dependent(:destroy) }
it { is_expected.to have_many(:dependency_links).inverse_of(:package) }
it { is_expected.to have_many(:tags).inverse_of(:package) }
+ it { is_expected.to have_many(:build_infos).inverse_of(:package) }
+ it { is_expected.to have_many(:pipelines).through(:build_infos) }
it { is_expected.to have_one(:conan_metadatum).inverse_of(:package) }
it { is_expected.to have_one(:maven_metadatum).inverse_of(:package) }
it { is_expected.to have_one(:nuget_metadatum).inverse_of(:package) }
@@ -171,6 +173,13 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
end
+ context 'composer package' do
+ it_behaves_like 'validating version to be SemVer compliant for', :composer_package
+
+ it { is_expected.to allow_value('dev-master').for(:version) }
+ it { is_expected.to allow_value('2.x-dev').for(:version) }
+ end
+
context 'maven package' do
subject { build_stubbed(:maven_package) }
@@ -573,7 +582,7 @@ RSpec.describe Packages::Package, type: :model do
end
describe '#pipeline' do
- let_it_be(:package) { create(:maven_package) }
+ let_it_be_with_refind(:package) { create(:maven_package) }
context 'package without pipeline' do
it 'returns nil if there is no pipeline' do
@@ -585,7 +594,7 @@ RSpec.describe Packages::Package, type: :model do
let_it_be(:pipeline) { create(:ci_pipeline) }
before do
- package.create_build_info!(pipeline: pipeline)
+ package.build_infos.create!(pipeline: pipeline)
end
it 'returns the pipeline' do
@@ -630,4 +639,23 @@ RSpec.describe Packages::Package, type: :model do
end
end
end
+
+ describe '#original_build_info' do
+ let_it_be_with_refind(:package) { create(:npm_package) }
+
+ context 'without build_infos' do
+ it 'returns nil' do
+ expect(package.original_build_info).to be_nil
+ end
+ end
+
+ context 'with build_infos' do
+ let_it_be(:first_build_info) { create(:package_build_info, :with_pipeline, package: package) }
+ let_it_be(:second_build_info) { create(:package_build_info, :with_pipeline, package: package) }
+
+ it 'returns the first build info' do
+ expect(package.original_build_info).to eq(first_build_info)
+ end
+ end
+ end
end
diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb
index cb1938a0113..f8ebc237577 100644
--- a/spec/models/pages/lookup_path_spec.rb
+++ b/spec/models/pages/lookup_path_spec.rb
@@ -3,15 +3,14 @@
require 'spec_helper'
RSpec.describe Pages::LookupPath do
- let_it_be(:project) do
- create(:project, :pages_private, pages_https_only: true)
- end
+ let(:project) { create(:project, :pages_private, pages_https_only: true) }
subject(:lookup_path) { described_class.new(project) }
before do
stub_pages_setting(access_control: true, external_https: ["1.1.1.1:443"])
stub_artifacts_object_storage
+ stub_pages_object_storage(::Pages::DeploymentUploader)
end
describe '#project_id' do
@@ -47,18 +46,78 @@ RSpec.describe Pages::LookupPath do
end
describe '#source' do
- shared_examples 'uses disk storage' do
- it 'sets the source type to "file"' do
- expect(lookup_path.source[:type]).to eq('file')
- end
+ let(:source) { lookup_path.source }
- it 'sets the source path to the project full path suffixed with "public/' do
- expect(lookup_path.source[:path]).to eq(project.full_path + "/public/")
+ shared_examples 'uses disk storage' do
+ it 'uses disk storage', :aggregate_failures do
+ expect(source[:type]).to eq('file')
+ expect(source[:path]).to eq(project.full_path + "/public/")
end
end
include_examples 'uses disk storage'
+ context 'when there is pages deployment' do
+ let(:deployment) { create(:pages_deployment, project: project) }
+
+ before do
+ project.mark_pages_as_deployed
+ project.pages_metadatum.update!(pages_deployment: deployment)
+ end
+
+ it 'uses deployment from object storage' do
+ Timecop.freeze do
+ expect(source).to(
+ eq({
+ type: 'zip',
+ path: deployment.file.url(expire_at: 1.day.from_now),
+ global_id: "gid://gitlab/PagesDeployment/#{deployment.id}",
+ sha256: deployment.file_sha256,
+ file_size: deployment.size,
+ file_count: deployment.file_count
+ })
+ )
+ end
+ end
+
+ context 'when deployment is in the local storage' do
+ before do
+ deployment.file.migrate!(::ObjectStorage::Store::LOCAL)
+ end
+
+ it 'uses file protocol' do
+ Timecop.freeze do
+ expect(source).to(
+ eq({
+ type: 'zip',
+ path: 'file://' + deployment.file.path,
+ global_id: "gid://gitlab/PagesDeployment/#{deployment.id}",
+ sha256: deployment.file_sha256,
+ file_size: deployment.size,
+ file_count: deployment.file_count
+ })
+ )
+ end
+ end
+
+ context 'when pages_serve_with_zip_file_protocol feature flag is disabled' do
+ before do
+ stub_feature_flags(pages_serve_with_zip_file_protocol: false)
+ end
+
+ include_examples 'uses disk storage'
+ end
+ end
+
+ context 'when pages_serve_from_deployments feature flag is disabled' do
+ before do
+ stub_feature_flags(pages_serve_from_deployments: false)
+ end
+
+ include_examples 'uses disk storage'
+ end
+ end
+
context 'when artifact_id from build job is present in pages metadata' do
let(:artifacts_archive) { create(:ci_job_artifact, :zip, :remote_store, project: project) }
@@ -66,26 +125,51 @@ RSpec.describe Pages::LookupPath do
project.mark_pages_as_deployed(artifacts_archive: artifacts_archive)
end
- it 'sets the source type to "zip"' do
- expect(lookup_path.source[:type]).to eq('zip')
- end
-
- it 'sets the source path to the artifacts archive URL' do
+ it 'uses artifacts object storage' do
Timecop.freeze do
- expect(lookup_path.source[:path]).to eq(artifacts_archive.file.url(expire_at: 1.day.from_now))
- expect(lookup_path.source[:path]).to include("Expires=86400")
+ expect(source).to(
+ eq({
+ type: 'zip',
+ path: artifacts_archive.file.url(expire_at: 1.day.from_now),
+ global_id: "gid://gitlab/Ci::JobArtifact/#{artifacts_archive.id}",
+ sha256: artifacts_archive.file_sha256,
+ file_size: artifacts_archive.size,
+ file_count: nil
+ })
+ )
end
end
context 'when artifact is not uploaded to object storage' do
let(:artifacts_archive) { create(:ci_job_artifact, :zip) }
- include_examples 'uses disk storage'
+ it 'uses file protocol', :aggregate_failures do
+ Timecop.freeze do
+ expect(source).to(
+ eq({
+ type: 'zip',
+ path: 'file://' + artifacts_archive.file.path,
+ global_id: "gid://gitlab/Ci::JobArtifact/#{artifacts_archive.id}",
+ sha256: artifacts_archive.file_sha256,
+ file_size: artifacts_archive.size,
+ file_count: nil
+ })
+ )
+ end
+ end
+
+ context 'when pages_serve_with_zip_file_protocol feature flag is disabled' do
+ before do
+ stub_feature_flags(pages_serve_with_zip_file_protocol: false)
+ end
+
+ include_examples 'uses disk storage'
+ end
end
context 'when feature flag is disabled' do
before do
- stub_feature_flags(pages_artifacts_archive: false)
+ stub_feature_flags(pages_serve_from_artifacts_archive: false)
end
include_examples 'uses disk storage'
diff --git a/spec/models/pages_deployment_spec.rb b/spec/models/pages_deployment_spec.rb
index 5d26ade740e..e83cbc15004 100644
--- a/spec/models/pages_deployment_spec.rb
+++ b/spec/models/pages_deployment_spec.rb
@@ -10,8 +10,15 @@ RSpec.describe PagesDeployment do
describe 'validations' do
it { is_expected.to validate_presence_of(:file) }
+
it { is_expected.to validate_presence_of(:size) }
it { is_expected.to validate_numericality_of(:size).only_integer.is_greater_than(0) }
+
+ it { is_expected.to validate_presence_of(:file_count) }
+ it { is_expected.to validate_numericality_of(:file_count).only_integer.is_greater_than_or_equal_to(0) }
+
+ it { is_expected.to validate_presence_of(:file_sha256) }
+
it { is_expected.to validate_inclusion_of(:file_store).in_array(ObjectStorage::SUPPORTED_STORES) }
it 'is valid when created from the factory' do
@@ -20,14 +27,26 @@ RSpec.describe PagesDeployment do
end
describe 'default for file_store' do
+ let(:project) { create(:project) }
+ let(:deployment) do
+ filepath = Rails.root.join("spec/fixtures/pages.zip")
+
+ described_class.create!(
+ project: project,
+ file: fixture_file_upload(filepath),
+ file_sha256: Digest::SHA256.file(filepath).hexdigest,
+ file_count: 3
+ )
+ end
+
it 'uses local store when object storage is not enabled' do
- expect(build(:pages_deployment).file_store).to eq(ObjectStorage::Store::LOCAL)
+ expect(deployment.file_store).to eq(ObjectStorage::Store::LOCAL)
end
it 'uses remote store when object storage is enabled' do
stub_pages_object_storage(::Pages::DeploymentUploader)
- expect(build(:pages_deployment).file_store).to eq(ObjectStorage::Store::REMOTE)
+ expect(deployment.file_store).to eq(ObjectStorage::Store::REMOTE)
end
end
@@ -35,4 +54,17 @@ RSpec.describe PagesDeployment do
deployment = create(:pages_deployment)
expect(deployment.size).to eq(deployment.file.size)
end
+
+ describe '.older_than' do
+ it 'returns deployments with lower id' do
+ old_deployments = create_list(:pages_deployment, 2)
+
+ deployment = create(:pages_deployment)
+
+ # new deployment
+ create(:pages_deployment)
+
+ expect(PagesDeployment.older_than(deployment.id)).to eq(old_deployments)
+ end
+ end
end
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index 9e80d0e0886..67ecbe13c1a 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -31,6 +31,18 @@ RSpec.describe PersonalAccessToken do
expect(described_class.for_user(user_1)).to contain_exactly(token_of_user_1)
end
end
+
+ describe '.for_users' do
+ it 'returns personal access tokens for the specified users only' do
+ user_1 = create(:user)
+ user_2 = create(:user)
+ token_of_user_1 = create(:personal_access_token, user: user_1)
+ token_of_user_2 = create(:personal_access_token, user: user_2)
+ create_list(:personal_access_token, 3)
+
+ expect(described_class.for_users([user_1, user_2])).to contain_exactly(token_of_user_1, token_of_user_2)
+ end
+ end
end
describe ".active?" do
diff --git a/spec/models/personal_snippet_spec.rb b/spec/models/personal_snippet_spec.rb
index 234f6e4b4b5..212605445ff 100644
--- a/spec/models/personal_snippet_spec.rb
+++ b/spec/models/personal_snippet_spec.rb
@@ -20,9 +20,8 @@ RSpec.describe PersonalSnippet do
it_behaves_like 'model with repository' do
let_it_be(:container) { create(:personal_snippet, :repository) }
let(:stubbed_container) { build_stubbed(:personal_snippet) }
- let(:expected_full_path) { "@snippets/#{container.id}" }
+ let(:expected_full_path) { "snippets/#{container.id}" }
let(:expected_web_url_path) { "-/snippets/#{container.id}" }
- let(:expected_repo_url_path) { "snippets/#{container.id}" }
end
describe '#parent_user' do
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index 3bcbf6b9e1b..3d1c87771f3 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -36,8 +36,7 @@ RSpec.describe ProjectSnippet do
it_behaves_like 'model with repository' do
let_it_be(:container) { create(:project_snippet, :repository) }
let(:stubbed_container) { build_stubbed(:project_snippet) }
- let(:expected_full_path) { "#{container.project.full_path}/@snippets/#{container.id}" }
+ let(:expected_full_path) { "#{container.project.full_path}/snippets/#{container.id}" }
let(:expected_web_url_path) { "#{container.project.full_path}/-/snippets/#{container.id}" }
- let(:expected_repo_url_path) { "#{container.project.full_path}/snippets/#{container.id}" }
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 53a213891e9..c8b96963d5d 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2,12 +2,14 @@
require 'spec_helper'
-RSpec.describe Project do
+RSpec.describe Project, factory_default: :keep do
include ProjectForksHelper
include GitHelpers
include ExternalAuthorizationServiceHelpers
using RSpec::Parameterized::TableSyntax
+ let_it_be(:namespace) { create_default(:namespace) }
+
it_behaves_like 'having unique enum values'
describe 'associations' do
@@ -3003,14 +3005,23 @@ RSpec.describe Project do
describe '#set_repository_read_only!' do
let(:project) { create(:project) }
- it 'returns true when there is no existing git transfer in progress' do
- expect(project.set_repository_read_only!).to be_truthy
+ it 'makes the repository read-only' do
+ expect { project.set_repository_read_only! }
+ .to change(project, :repository_read_only?)
+ .from(false)
+ .to(true)
end
- it 'returns false when there is an existing git transfer in progress' do
+ it 'raises an error if the project is already read-only' do
+ project.set_repository_read_only!
+
+ expect { project.set_repository_read_only! }.to raise_error(described_class::RepositoryReadOnlyError, /already read-only/)
+ end
+
+ it 'raises an error when there is an existing git transfer in progress' do
allow(project).to receive(:git_transfer_in_progress?) { true }
- expect(project.set_repository_read_only!).to be_falsey
+ expect { project.set_repository_read_only! }.to raise_error(described_class::RepositoryReadOnlyError, /in progress/)
end
end
@@ -3657,7 +3668,7 @@ RSpec.describe Project do
let(:project) { create(:project) }
before do
- project.namespace_id = 7
+ project.namespace_id = project.namespace_id + 1
end
it { expect(project.parent_changed?).to be_truthy }
@@ -3985,8 +3996,16 @@ RSpec.describe Project do
context 'when feature is private' do
let(:project) { create(:project, :public, :merge_requests_private) }
- it 'returns projects with the project feature private' do
- is_expected.to include(project)
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it 'returns projects with the project feature private' do
+ is_expected.to include(project)
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it 'does not return projects with the project feature private' do
+ is_expected.not_to include(project)
+ end
end
end
end
@@ -4009,7 +4028,7 @@ RSpec.describe Project do
end
end
- describe '.filter_by_feature_visibility', :enable_admin_mode do
+ describe '.filter_by_feature_visibility' do
include_context 'ProjectPolicyTable context'
include ProjectHelpers
using RSpec::Parameterized::TableSyntax
@@ -4021,12 +4040,13 @@ RSpec.describe Project do
context 'reporter level access' do
let(:feature) { MergeRequest }
- where(:project_level, :feature_access_level, :membership, :expected_count) do
+ where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_reporter_feature_access
end
with_them do
it "respects visibility" do
+ enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
expected_objects = expected_count == 1 ? [project] : []
@@ -4041,12 +4061,13 @@ RSpec.describe Project do
context 'issues' do
let(:feature) { Issue }
- where(:project_level, :feature_access_level, :membership, :expected_count) do
+ where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "respects visibility" do
+ enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
expected_objects = expected_count == 1 ? [project] : []
@@ -4061,12 +4082,13 @@ RSpec.describe Project do
context 'wiki' do
let(:feature) { :wiki }
- where(:project_level, :feature_access_level, :membership, :expected_count) do
+ where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "respects visibility" do
+ enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
expected_objects = expected_count == 1 ? [project] : []
@@ -4081,12 +4103,13 @@ RSpec.describe Project do
context 'code' do
let(:feature) { :repository }
- where(:project_level, :feature_access_level, :membership, :expected_count) do
+ where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access_and_non_private_project_only
end
with_them do
it "respects visibility" do
+ enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
expected_objects = expected_count == 1 ? [project] : []
@@ -4208,6 +4231,27 @@ RSpec.describe Project do
expect { project.destroy }.not_to raise_error
end
+
+ context 'when there is an old pages deployment' do
+ let!(:old_deployment_from_another_project) { create(:pages_deployment) }
+ let!(:old_deployment) { create(:pages_deployment, project: project) }
+
+ it 'schedules a destruction of pages deployments' do
+ expect(DestroyPagesDeploymentsWorker).to(
+ receive(:perform_async).with(project.id)
+ )
+
+ project.remove_pages
+ end
+
+ it 'removes pages deployments', :sidekiq_inline do
+ expect do
+ project.remove_pages
+ end.to change { PagesDeployment.count }.by(-1)
+
+ expect(PagesDeployment.find_by_id(old_deployment.id)).to be_nil
+ end
+ end
end
describe '#remove_export' do
@@ -5507,15 +5551,16 @@ RSpec.describe Project do
end
describe '#find_or_initialize_services' do
- it 'returns only enabled services' do
+ before do
allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover teamcity])
- allow(Service).to receive(:project_specific_services_names).and_return(%w[asana])
allow(subject).to receive(:disabled_services).and_return(%w[prometheus])
+ end
+ it 'returns only enabled services' do
services = subject.find_or_initialize_services
- expect(services.count).to eq(3)
- expect(services.map(&:title)).to eq(['Asana', 'JetBrains TeamCity CI', 'Pushover'])
+ expect(services.count).to eq(2)
+ expect(services.map(&:title)).to eq(['JetBrains TeamCity CI', 'Pushover'])
end
end
@@ -5895,6 +5940,26 @@ RSpec.describe Project do
end
end
+ describe '#update_pages_deployment!' do
+ let(:project) { create(:project) }
+ let(:deployment) { create(:pages_deployment, project: project) }
+
+ it "creates new metadata record if none exists yet and sets deployment" do
+ project.pages_metadatum.destroy!
+ project.reload
+
+ project.update_pages_deployment!(deployment)
+
+ expect(project.pages_metadatum.pages_deployment).to eq(deployment)
+ end
+
+ it "updates the existing metadara record with deployment" do
+ expect do
+ project.update_pages_deployment!(deployment)
+ end.to change { project.pages_metadatum.reload.pages_deployment }.from(nil).to(deployment)
+ end
+ end
+
describe '#has_pool_repsitory?' do
it 'returns false when it does not have a pool repository' do
subject = create(:project, :repository)
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 9f40dbb3401..2d283766edb 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -34,7 +34,8 @@ RSpec.describe ProjectStatistics do
lfs_objects_size: 2.exabytes,
build_artifacts_size: 1.exabyte,
snippets_size: 1.exabyte,
- pipeline_artifacts_size: 1.exabyte - 1
+ pipeline_artifacts_size: 512.petabytes - 1,
+ uploads_size: 512.petabytes
)
statistics.reload
@@ -46,7 +47,8 @@ RSpec.describe ProjectStatistics do
expect(statistics.build_artifacts_size).to eq(1.exabyte)
expect(statistics.storage_size).to eq(8.exabytes - 1)
expect(statistics.snippets_size).to eq(1.exabyte)
- expect(statistics.pipeline_artifacts_size).to eq(1.exabyte - 1)
+ expect(statistics.pipeline_artifacts_size).to eq(512.petabytes - 1)
+ expect(statistics.uploads_size).to eq(512.petabytes)
end
end
@@ -57,6 +59,7 @@ RSpec.describe ProjectStatistics do
statistics.lfs_objects_size = 3
statistics.build_artifacts_size = 4
statistics.snippets_size = 5
+ statistics.uploads_size = 3
expect(statistics.total_repository_size).to eq 5
end
@@ -98,6 +101,7 @@ RSpec.describe ProjectStatistics do
allow(statistics).to receive(:update_lfs_objects_size)
allow(statistics).to receive(:update_snippets_size)
allow(statistics).to receive(:update_storage_size)
+ allow(statistics).to receive(:update_uploads_size)
end
context "without arguments" do
@@ -111,6 +115,7 @@ RSpec.describe ProjectStatistics do
expect(statistics).to have_received(:update_wiki_size)
expect(statistics).to have_received(:update_lfs_objects_size)
expect(statistics).to have_received(:update_snippets_size)
+ expect(statistics).to have_received(:update_uploads_size)
end
end
@@ -125,6 +130,7 @@ RSpec.describe ProjectStatistics do
expect(statistics).not_to have_received(:update_repository_size)
expect(statistics).not_to have_received(:update_wiki_size)
expect(statistics).not_to have_received(:update_snippets_size)
+ expect(statistics).not_to have_received(:update_uploads_size)
end
end
@@ -139,10 +145,12 @@ RSpec.describe ProjectStatistics do
expect(statistics).to have_received(:update_repository_size)
expect(statistics).to have_received(:update_wiki_size)
expect(statistics).to have_received(:update_snippets_size)
+ expect(statistics).to have_received(:update_uploads_size)
expect(statistics.repository_size).to eq(0)
expect(statistics.commit_count).to eq(0)
expect(statistics.wiki_size).to eq(0)
expect(statistics.snippets_size).to eq(0)
+ expect(statistics.uploads_size).to eq(0)
end
end
@@ -163,10 +171,12 @@ RSpec.describe ProjectStatistics do
expect(statistics).to have_received(:update_repository_size)
expect(statistics).to have_received(:update_wiki_size)
expect(statistics).to have_received(:update_snippets_size)
+ expect(statistics).to have_received(:update_uploads_size)
expect(statistics.repository_size).to eq(0)
expect(statistics.commit_count).to eq(0)
expect(statistics.wiki_size).to eq(0)
expect(statistics.snippets_size).to eq(0)
+ expect(statistics.uploads_size).to eq(0)
end
end
@@ -211,6 +221,7 @@ RSpec.describe ProjectStatistics do
expect(statistics).not_to receive(:update_wiki_size)
expect(statistics).not_to receive(:update_lfs_objects_size)
expect(statistics).not_to receive(:update_snippets_size)
+ expect(statistics).not_to receive(:update_uploads_size)
expect(statistics).not_to receive(:save!)
expect(Namespaces::ScheduleAggregationWorker)
.not_to receive(:perform_async)
@@ -295,6 +306,26 @@ RSpec.describe ProjectStatistics do
end
end
+ describe '#update_uploads_size' do
+ let!(:upload1) { create(:upload, model: project, size: 1.megabyte) }
+ let!(:upload2) { create(:upload, model: project, size: 2.megabytes) }
+
+ it 'stores the size of related uploaded files' do
+ expect(statistics.update_uploads_size).to eq(3.megabytes)
+ end
+
+ context 'with feature flag disabled' do
+ before do
+ statistics.update_columns(uploads_size: 0)
+ stub_feature_flags(count_uploads_size_in_storage_stats: false)
+ end
+
+ it 'does not store the size of related uploaded files' do
+ expect(statistics.update_uploads_size).to eq(0)
+ end
+ end
+ end
+
describe '#update_storage_size' do
it "sums all storage counters" do
statistics.update!(
@@ -302,12 +333,13 @@ RSpec.describe ProjectStatistics do
wiki_size: 4,
lfs_objects_size: 3,
snippets_size: 2,
- pipeline_artifacts_size: 3
+ pipeline_artifacts_size: 3,
+ uploads_size: 5
)
statistics.reload
- expect(statistics.storage_size).to eq 14
+ expect(statistics.storage_size).to eq 19
end
it 'works during wiki_size backfill' do
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
index 77fe9814c86..0aba51ea567 100644
--- a/spec/models/protected_branch/push_access_level_spec.rb
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -4,4 +4,34 @@ require 'spec_helper'
RSpec.describe ProtectedBranch::PushAccessLevel do
it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MAINTAINER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:deploy_key) }
+ end
+
+ describe 'validations' do
+ it 'is not valid when a record exists with the same access level' do
+ protected_branch = create(:protected_branch)
+ create(:protected_branch_push_access_level, protected_branch: protected_branch)
+ level = build(:protected_branch_push_access_level, protected_branch: protected_branch)
+
+ expect(level).to be_invalid
+ end
+
+ it 'is not valid when a record exists with the same access level' do
+ protected_branch = create(:protected_branch)
+ deploy_key = create(:deploy_key, projects: [protected_branch.project])
+ create(:protected_branch_push_access_level, protected_branch: protected_branch, deploy_key: deploy_key)
+ level = build(:protected_branch_push_access_level, protected_branch: protected_branch, deploy_key: deploy_key)
+
+ expect(level).to be_invalid
+ end
+
+ it 'checks that a deploy key is enabled for the same project as the protected branch\'s' do
+ level = build(:protected_branch_push_access_level, deploy_key: create(:deploy_key))
+
+ expect { level.save! }.to raise_error
+ expect(level.errors.full_messages).to contain_exactly('Deploy key is not enabled for this project')
+ end
+ end
end
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index 0f1637016d6..eb81db95cd3 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -62,6 +62,15 @@ RSpec.describe Route do
end
end
+ describe '.for_routable_type' do
+ let!(:nested_group) { create(:group, path: 'foo', name: 'foo', parent: group) }
+ let!(:project) { create(:project, path: 'other-project') }
+
+ it 'returns correct routes' do
+ expect(described_class.for_routable_type(Project.name)).to match_array([project.route])
+ end
+ end
+
describe '#rename_descendants' do
let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) }
let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) }
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index db3cf19a03f..402c1a3d19b 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Service do
+ using RSpec::Parameterized::TableSyntax
+
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
@@ -15,8 +17,6 @@ RSpec.describe Service do
end
describe 'validations' do
- using RSpec::Parameterized::TableSyntax
-
it { is_expected.to validate_presence_of(:type) }
where(:project_id, :group_id, :template, :instance, :valid) do
@@ -208,27 +208,27 @@ RSpec.describe Service do
end
end
- describe '.find_or_initialize_integration' do
+ describe '.find_or_initialize_non_project_specific_integration' do
let!(:service1) { create(:jira_service, project_id: nil, group_id: group.id) }
let!(:service2) { create(:jira_service) }
it 'returns the right service' do
- expect(Service.find_or_initialize_integration('jira', group_id: group)).to eq(service1)
+ expect(Service.find_or_initialize_non_project_specific_integration('jira', group_id: group)).to eq(service1)
end
it 'does not create a new service' do
- expect { Service.find_or_initialize_integration('redmine', group_id: group) }.not_to change { Service.count }
+ expect { Service.find_or_initialize_non_project_specific_integration('redmine', group_id: group) }.not_to change { Service.count }
end
end
- describe '.find_or_initialize_all' do
+ describe '.find_or_initialize_all_non_project_specific' do
shared_examples 'service instances' do
it 'returns the available service instances' do
- expect(Service.find_or_initialize_all(Service.for_instance).pluck(:type)).to match_array(Service.available_services_types)
+ expect(Service.find_or_initialize_all_non_project_specific(Service.for_instance).pluck(:type)).to match_array(Service.available_services_types(include_project_specific: false))
end
it 'does not create service instances' do
- expect { Service.find_or_initialize_all(Service.for_instance) }.not_to change { Service.count }
+ expect { Service.find_or_initialize_all_non_project_specific(Service.for_instance) }.not_to change { Service.count }
end
end
@@ -237,7 +237,7 @@ RSpec.describe Service do
context 'with all existing instances' do
before do
Service.insert_all(
- Service.available_services_types.map { |type| { instance: true, type: type } }
+ Service.available_services_types(include_project_specific: false).map { |type| { instance: true, type: type } }
)
end
@@ -265,13 +265,13 @@ RSpec.describe Service do
describe 'template' do
shared_examples 'retrieves service templates' do
it 'returns the available service templates' do
- expect(Service.find_or_create_templates.pluck(:type)).to match_array(Service.available_services_types)
+ expect(Service.find_or_create_templates.pluck(:type)).to match_array(Service.available_services_types(include_project_specific: false))
end
end
describe '.find_or_create_templates' do
it 'creates service templates' do
- expect { Service.find_or_create_templates }.to change { Service.count }.from(0).to(Service.available_services_names.size)
+ expect { Service.find_or_create_templates }.to change { Service.count }.from(0).to(Service.available_services_names(include_project_specific: false).size)
end
it_behaves_like 'retrieves service templates'
@@ -279,7 +279,7 @@ RSpec.describe Service do
context 'with all existing templates' do
before do
Service.insert_all(
- Service.available_services_types.map { |type| { template: true, type: type } }
+ Service.available_services_types(include_project_specific: false).map { |type| { template: true, type: type } }
)
end
@@ -305,7 +305,7 @@ RSpec.describe Service do
end
it 'creates the rest of the service templates' do
- expect { Service.find_or_create_templates }.to change { Service.count }.from(1).to(Service.available_services_names.size)
+ expect { Service.find_or_create_templates }.to change { Service.count }.from(1).to(Service.available_services_names(include_project_specific: false).size)
end
it_behaves_like 'retrieves service templates'
@@ -599,6 +599,23 @@ RSpec.describe Service do
end
end
+ describe '.inherited_descendants_from_self_or_ancestors_from' do
+ let_it_be(:subgroup1) { create(:group, parent: group) }
+ let_it_be(:subgroup2) { create(:group, parent: group) }
+ let_it_be(:project1) { create(:project, group: subgroup1) }
+ let_it_be(:project2) { create(:project, group: subgroup2) }
+ let_it_be(:group_integration) { create(:prometheus_service, group: group, project: nil) }
+ let_it_be(:subgroup_integration1) { create(:prometheus_service, group: subgroup1, project: nil, inherit_from_id: group_integration.id) }
+ let_it_be(:subgroup_integration2) { create(:prometheus_service, group: subgroup2, project: nil) }
+ let_it_be(:project_integration1) { create(:prometheus_service, group: nil, project: project1, inherit_from_id: group_integration.id) }
+ let_it_be(:project_integration2) { create(:prometheus_service, group: nil, project: project2, inherit_from_id: subgroup_integration2.id) }
+
+ it 'returns the groups and projects inheriting from integration ancestors', :aggregate_failures do
+ expect(described_class.inherited_descendants_from_self_or_ancestors_from(group_integration)).to eq([subgroup_integration1, project_integration1])
+ expect(described_class.inherited_descendants_from_self_or_ancestors_from(subgroup_integration2)).to eq([project_integration2])
+ end
+ end
+
describe "{property}_changed?" do
let(:service) do
BambooService.create(
@@ -826,5 +843,78 @@ RSpec.describe Service do
service.log_error(test_message, additional_argument: 'some argument')
end
+
+ context 'when project is nil' do
+ let(:project) { nil }
+ let(:arguments) do
+ {
+ service_class: service.class.name,
+ project_path: nil,
+ project_id: nil,
+ message: test_message,
+ additional_argument: 'some argument'
+ }
+ end
+
+ it 'logs info messages using json logger' do
+ expect(Gitlab::JsonLogger).to receive(:info).with(arguments)
+
+ service.log_info(test_message, additional_argument: 'some argument')
+ end
+ end
+ end
+
+ describe '#external_issue_tracker?' do
+ where(:category, :active, :result) do
+ :issue_tracker | true | true
+ :issue_tracker | false | false
+ :common | true | false
+ end
+
+ with_them do
+ it 'returns the right result' do
+ expect(build(:service, category: category, active: active).external_issue_tracker?).to eq(result)
+ end
+ end
+ end
+
+ describe '#external_wiki?' do
+ where(:type, :active, :result) do
+ 'ExternalWikiService' | true | true
+ 'ExternalWikiService' | false | false
+ 'SlackService' | true | false
+ end
+
+ with_them do
+ it 'returns the right result' do
+ expect(build(:service, type: type, active: active).external_wiki?).to eq(result)
+ end
+ end
+ end
+
+ describe '.available_services_names' do
+ it 'calls the right methods' do
+ expect(described_class).to receive(:services_names).and_call_original
+ expect(described_class).to receive(:dev_services_names).and_call_original
+ expect(described_class).to receive(:project_specific_services_names).and_call_original
+
+ described_class.available_services_names
+ end
+
+ it 'does not call project_specific_services_names with include_project_specific false' do
+ expect(described_class).to receive(:services_names).and_call_original
+ expect(described_class).to receive(:dev_services_names).and_call_original
+ expect(described_class).not_to receive(:project_specific_services_names)
+
+ described_class.available_services_names(include_project_specific: false)
+ end
+
+ it 'does not call dev_services_names with include_dev false' do
+ expect(described_class).to receive(:services_names).and_call_original
+ expect(described_class).not_to receive(:dev_services_names)
+ expect(described_class).to receive(:project_specific_services_names).and_call_original
+
+ described_class.available_services_names(include_dev: false)
+ end
end
end
diff --git a/spec/models/terraform/state_spec.rb b/spec/models/terraform/state_spec.rb
index 608c5bdf03a..ca8fe4cca3b 100644
--- a/spec/models/terraform/state_spec.rb
+++ b/spec/models/terraform/state_spec.rb
@@ -97,10 +97,11 @@ RSpec.describe Terraform::State do
end
describe '#update_file!' do
- let(:version) { 3 }
- let(:data) { Hash[terraform_version: '0.12.21'].to_json }
+ let_it_be(:build) { create(:ci_build) }
+ let_it_be(:version) { 3 }
+ let_it_be(:data) { Hash[terraform_version: '0.12.21'].to_json }
- subject { terraform_state.update_file!(CarrierWaveStringFile.new(data), version: version) }
+ subject { terraform_state.update_file!(CarrierWaveStringFile.new(data), version: version, build: build) }
context 'versioning is enabled' do
let(:terraform_state) { create(:terraform_state) }
@@ -109,6 +110,7 @@ RSpec.describe Terraform::State do
expect { subject }.to change { Terraform::StateVersion.count }
expect(terraform_state.latest_version.version).to eq(version)
+ expect(terraform_state.latest_version.build).to eq(build)
expect(terraform_state.latest_version.file.read).to eq(data)
end
end
diff --git a/spec/models/terraform/state_version_spec.rb b/spec/models/terraform/state_version_spec.rb
index cc5ea87159d..97ac77d5e7b 100644
--- a/spec/models/terraform/state_version_spec.rb
+++ b/spec/models/terraform/state_version_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Terraform::StateVersion do
it { is_expected.to belong_to(:terraform_state).required }
it { is_expected.to belong_to(:created_by_user).class_name('User').optional }
+ it { is_expected.to belong_to(:build).class_name('Ci::Build').optional }
describe 'scopes' do
describe '.ordered_by_version_desc' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 64bff5d00aa..19a6a3ce3c4 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -319,7 +319,7 @@ RSpec.describe User do
expect(subject).to validate_presence_of(:username)
end
- it 'rejects blacklisted names' do
+ it 'rejects denied names' do
user = build(:user, username: 'dashboard')
expect(user).not_to be_valid
@@ -442,9 +442,9 @@ RSpec.describe User do
end
describe 'email' do
- context 'when no signup domains whitelisted' do
+ context 'when no signup domains allowed' do
before do
- allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return([])
+ allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return([])
end
it 'accepts any email' do
@@ -455,7 +455,7 @@ RSpec.describe User do
context 'bad regex' do
before do
- allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['([a-zA-Z0-9]+)+\.com'])
+ allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return(['([a-zA-Z0-9]+)+\.com'])
end
it 'does not hang on evil input' do
@@ -467,9 +467,9 @@ RSpec.describe User do
end
end
- context 'when a signup domain is whitelisted and subdomains are allowed' do
+ context 'when a signup domain is allowed and subdomains are allowed' do
before do
- allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['example.com', '*.example.com'])
+ allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return(['example.com', '*.example.com'])
end
it 'accepts info@example.com' do
@@ -488,9 +488,9 @@ RSpec.describe User do
end
end
- context 'when a signup domain is whitelisted and subdomains are not allowed' do
+ context 'when a signup domain is allowed and subdomains are not allowed' do
before do
- allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['example.com'])
+ allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return(['example.com'])
end
it 'accepts info@example.com' do
@@ -514,15 +514,15 @@ RSpec.describe User do
end
end
- context 'domain blacklist' do
+ context 'domain denylist' do
before do
- allow_any_instance_of(ApplicationSetting).to receive(:domain_blacklist_enabled?).and_return(true)
- allow_any_instance_of(ApplicationSetting).to receive(:domain_blacklist).and_return(['example.com'])
+ allow_any_instance_of(ApplicationSetting).to receive(:domain_denylist_enabled?).and_return(true)
+ allow_any_instance_of(ApplicationSetting).to receive(:domain_denylist).and_return(['example.com'])
end
context 'bad regex' do
before do
- allow_any_instance_of(ApplicationSetting).to receive(:domain_blacklist).and_return(['([a-zA-Z0-9]+)+\.com'])
+ allow_any_instance_of(ApplicationSetting).to receive(:domain_denylist).and_return(['([a-zA-Z0-9]+)+\.com'])
end
it 'does not hang on evil input' do
@@ -534,7 +534,7 @@ RSpec.describe User do
end
end
- context 'when a signup domain is blacklisted' do
+ context 'when a signup domain is denied' do
it 'accepts info@test.com' do
user = build(:user, email: 'info@test.com')
expect(user).to be_valid
@@ -551,13 +551,13 @@ RSpec.describe User do
end
end
- context 'when a signup domain is blacklisted but a wildcard subdomain is allowed' do
+ context 'when a signup domain is denied but a wildcard subdomain is allowed' do
before do
- allow_any_instance_of(ApplicationSetting).to receive(:domain_blacklist).and_return(['test.example.com'])
- allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['*.example.com'])
+ allow_any_instance_of(ApplicationSetting).to receive(:domain_denylist).and_return(['test.example.com'])
+ allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return(['*.example.com'])
end
- it 'gives priority to whitelist and allow info@test.example.com' do
+ it 'gives priority to allowlist and allow info@test.example.com' do
user = build(:user, email: 'info@test.example.com')
expect(user).to be_valid
end
@@ -565,7 +565,7 @@ RSpec.describe User do
context 'with both lists containing a domain' do
before do
- allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['test.com'])
+ allow_any_instance_of(ApplicationSetting).to receive(:domain_allowlist).and_return(['test.com'])
end
it 'accepts info@test.com' do
@@ -1740,6 +1740,16 @@ RSpec.describe User do
end
end
+ describe '.instance_access_request_approvers_to_be_notified' do
+ let_it_be(:admin_list) { create_list(:user, 12, :admin, :with_sign_ins) }
+
+ it 'returns up to the ten most recently active instance admins' do
+ active_admins_in_recent_sign_in_desc_order = User.admins.active.order_recent_sign_in.limit(10)
+
+ expect(User.instance_access_request_approvers_to_be_notified).to eq(active_admins_in_recent_sign_in_desc_order)
+ end
+ end
+
describe '.filter_items' do
let(:user) { double }
@@ -2906,6 +2916,34 @@ RSpec.describe User do
subject { user.authorized_groups }
it { is_expected.to contain_exactly private_group, project_group }
+
+ context 'with shared memberships' do
+ let!(:shared_group) { create(:group) }
+ let!(:other_group) { create(:group) }
+
+ before do
+ create(:group_group_link, shared_group: shared_group, shared_with_group: private_group)
+ create(:group_group_link, shared_group: private_group, shared_with_group: other_group)
+ end
+
+ context 'when shared_group_membership_auth is enabled' do
+ before do
+ stub_feature_flags(shared_group_membership_auth: user)
+ end
+
+ it { is_expected.to include shared_group }
+ it { is_expected.not_to include other_group }
+ end
+
+ context 'when shared_group_membership_auth is disabled' do
+ before do
+ stub_feature_flags(shared_group_membership_auth: false)
+ end
+
+ it { is_expected.not_to include shared_group }
+ it { is_expected.not_to include other_group }
+ end
+ end
end
describe '#membership_groups' do
@@ -3637,9 +3675,9 @@ RSpec.describe User do
end
end
- context 'when a domain whitelist is in place' do
+ context 'when a domain allowlist is in place' do
before do
- stub_application_setting(domain_whitelist: ['gitlab.com'])
+ stub_application_setting(domain_allowlist: ['gitlab.com'])
end
it 'creates a ghost user' do