summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-13 12:10:13 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-13 12:10:13 +0000
commit70ce746bd011b101605e6d84f141d1f0c3175831 (patch)
tree1b76e654f61579b79b34116fa6bb06db2f511d94 /spec
parent61ca90e0b462bfe69e0b400dfe403bd95281b90f (diff)
downloadgitlab-ce-70ce746bd011b101605e6d84f141d1f0c3175831.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/helpers/projects_helper_spec.rb20
-rw-r--r--spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb66
-rw-r--r--spec/models/concerns/cascading_namespace_setting_attribute_spec.rb347
-rw-r--r--spec/models/namespace/package_setting_spec.rb9
-rw-r--r--spec/models/namespace_setting_spec.rb4
-rw-r--r--spec/models/namespace_spec.rb3
-rw-r--r--spec/models/project_spec.rb3
-rw-r--r--spec/requests/api/maven_packages_spec.rb4
-rw-r--r--spec/services/merge_requests/update_service_spec.rb12
-rw-r--r--spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb5
-rw-r--r--spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb355
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb2
14 files changed, 471 insertions, 366 deletions
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index a3826ec8eb8..07c2d50f70a 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -1336,4 +1336,24 @@ RSpec.describe ProjectsHelper do
)
end
end
+
+ describe '#localized_project_human_access' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:key, :localized_project_human_access) do
+ Gitlab::Access::NO_ACCESS | _('No access')
+ Gitlab::Access::MINIMAL_ACCESS | _("Minimal Access")
+ Gitlab::Access::GUEST | _('Guest')
+ Gitlab::Access::REPORTER | _('Reporter')
+ Gitlab::Access::DEVELOPER | _('Developer')
+ Gitlab::Access::MAINTAINER | _('Maintainer')
+ Gitlab::Access::OWNER | _('Owner')
+ end
+
+ with_them do
+ it 'with correct key' do
+ expect(helper.localized_project_human_access(key)).to eq(localized_project_human_access)
+ end
+ end
+ end
end
diff --git a/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb
index aa4b0a137cd..66cf06cde20 100644
--- a/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb
+++ b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb
@@ -7,12 +7,23 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
describe '#redirect_registry_request' do
using RSpec::Parameterized::TableSyntax
+ include_context 'dependency proxy helpers context'
- let_it_be(:project) { create(:project) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be_with_reload(:package_setting) { create(:namespace_package_setting, namespace: group) }
+ let(:target) { project }
let(:options) { {} }
- subject { helper.redirect_registry_request(forward_to_registry, package_type, options) { helper.fallback } }
+ subject do
+ helper.redirect_registry_request(
+ forward_to_registry: forward_to_registry,
+ package_type: package_type,
+ target: target,
+ options: options
+ ) { helper.fallback }
+ end
before do
allow(helper).to receive(:options).and_return(for: described_class)
@@ -42,32 +53,57 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
%i[maven npm pypi].each do |forwardable_package_type|
context "with #{forwardable_package_type} packages" do
- include_context 'dependency proxy helpers context'
-
let(:package_type) { forwardable_package_type }
- let(:options) { { project: project } }
- where(:application_setting, :forward_to_registry, :example_name) do
- true | true | 'executing redirect'
- true | false | 'executing fallback'
- false | true | 'executing fallback'
- false | false | 'executing fallback'
+ where(:application_setting, :group_setting, :forward_to_registry, :example_name) do
+ true | nil | true | 'executing redirect'
+ true | nil | false | 'executing fallback'
+ false | nil | true | 'executing fallback'
+ false | nil | false | 'executing fallback'
+ true | false | true | 'executing fallback'
+ true | false | false | 'executing fallback'
+ false | true | true | 'executing redirect'
+ false | true | false | 'executing fallback'
end
with_them do
before do
- allow_fetch_application_setting(attribute: "#{forwardable_package_type}_package_requests_forwarding", return_value: application_setting)
+ allow_fetch_cascade_application_setting(attribute: "#{forwardable_package_type}_package_requests_forwarding", return_value: application_setting)
+ package_setting.update!("#{forwardable_package_type}_package_requests_forwarding" => group_setting)
end
it_behaves_like params[:example_name]
end
end
+ context 'when cascade_package_forwarding_settings is disabled' do
+ let(:package_type) { forwardable_package_type }
+ let(:forward_to_registry) { true }
+
+ before do
+ stub_feature_flags(cascade_package_forwarding_settings: false)
+ allow_fetch_cascade_application_setting(attribute: "#{forwardable_package_type}_package_requests_forwarding", return_value: true)
+ package_setting.update!("#{forwardable_package_type}_package_requests_forwarding" => false)
+ end
+
+ it_behaves_like 'executing redirect'
+ end
+
+ context 'when no target is present' do
+ let(:package_type) { forwardable_package_type }
+ let(:forward_to_registry) { true }
+ let(:target) { nil }
+
+ before do
+ allow_fetch_cascade_application_setting(attribute: "#{forwardable_package_type}_package_requests_forwarding", return_value: true)
+ package_setting.update!("#{forwardable_package_type}_package_requests_forwarding" => false)
+ end
+
+ it_behaves_like 'executing redirect'
+ end
+
context 'when maven_central_request_forwarding is disabled' do
let(:package_type) { :maven }
- let(:options) { { project: project } }
-
- include_context 'dependency proxy helpers context'
where(:application_setting, :forward_to_registry) do
true | true
@@ -79,7 +115,7 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
with_them do
before do
stub_feature_flags(maven_central_request_forwarding: false)
- allow_fetch_application_setting(attribute: "maven_package_requests_forwarding", return_value: application_setting)
+ allow_fetch_cascade_application_setting(attribute: "maven_package_requests_forwarding", return_value: application_setting)
end
it_behaves_like 'executing fallback'
diff --git a/spec/models/concerns/cascading_namespace_setting_attribute_spec.rb b/spec/models/concerns/cascading_namespace_setting_attribute_spec.rb
deleted file mode 100644
index 6be6e3f048f..00000000000
--- a/spec/models/concerns/cascading_namespace_setting_attribute_spec.rb
+++ /dev/null
@@ -1,347 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe NamespaceSetting, 'CascadingNamespaceSettingAttribute' do
- let(:group) { create(:group) }
- let(:subgroup) { create(:group, parent: group) }
-
- def group_settings
- group.namespace_settings
- end
-
- def subgroup_settings
- subgroup.namespace_settings
- end
-
- describe '#delayed_project_removal' do
- subject(:delayed_project_removal) { subgroup_settings.delayed_project_removal }
-
- context 'when there is no parent' do
- context 'and the value is not nil' do
- before do
- group_settings.update!(delayed_project_removal: true)
- end
-
- it 'returns the local value' do
- expect(group_settings.delayed_project_removal).to eq(true)
- end
- end
-
- context 'and the value is nil' do
- before do
- group_settings.update!(delayed_project_removal: nil)
- stub_application_setting(delayed_project_removal: false)
- end
-
- it 'returns the application settings value' do
- expect(group_settings.delayed_project_removal).to eq(false)
- end
- end
- end
-
- context 'when parent does not lock the attribute' do
- context 'and value is not nil' do
- before do
- group_settings.update!(delayed_project_removal: false)
- end
-
- it 'returns local setting when present' do
- subgroup_settings.update!(delayed_project_removal: true)
-
- expect(delayed_project_removal).to eq(true)
- end
-
- it 'returns the parent value when local value is nil' do
- subgroup_settings.update!(delayed_project_removal: nil)
-
- expect(delayed_project_removal).to eq(false)
- end
-
- it 'returns the correct dirty value' do
- subgroup_settings.delayed_project_removal = true
-
- expect(delayed_project_removal).to eq(true)
- end
-
- it 'does not return the application setting value when parent value is false' do
- stub_application_setting(delayed_project_removal: true)
-
- expect(delayed_project_removal).to eq(false)
- end
- end
-
- context 'and the value is nil' do
- before do
- group_settings.update!(delayed_project_removal: nil, lock_delayed_project_removal: false)
- subgroup_settings.update!(delayed_project_removal: nil)
-
- subgroup_settings.clear_memoization(:delayed_project_removal)
- end
-
- it 'cascades to the application settings value' do
- expect(delayed_project_removal).to eq(false)
- end
- end
-
- context 'when multiple ancestors set a value' do
- let(:third_level_subgroup) { create(:group, parent: subgroup) }
-
- before do
- group_settings.update!(delayed_project_removal: true)
- subgroup_settings.update!(delayed_project_removal: false)
- end
-
- it 'returns the closest ancestor value' do
- expect(third_level_subgroup.namespace_settings.delayed_project_removal).to eq(false)
- end
- end
- end
-
- context 'when parent locks the attribute' do
- before do
- subgroup_settings.update!(delayed_project_removal: true)
- group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false)
-
- subgroup_settings.clear_memoization(:delayed_project_removal)
- subgroup_settings.clear_memoization(:delayed_project_removal_locked_ancestor)
- end
-
- it 'returns the parent value' do
- expect(delayed_project_removal).to eq(false)
- end
-
- it 'does not allow the local value to be saved' do
- subgroup_settings.delayed_project_removal = nil
-
- expect { subgroup_settings.save! }
- .to raise_error(ActiveRecord::RecordInvalid, /Delayed project removal cannot be changed because it is locked by an ancestor/)
- end
- end
-
- context 'when the application settings locks the attribute' do
- before do
- subgroup_settings.update!(delayed_project_removal: true)
- stub_application_setting(lock_delayed_project_removal: true, delayed_project_removal: true)
- end
-
- it 'returns the application setting value' do
- expect(delayed_project_removal).to eq(true)
- end
-
- it 'does not allow the local value to be saved' do
- subgroup_settings.delayed_project_removal = false
-
- expect { subgroup_settings.save! }
- .to raise_error(ActiveRecord::RecordInvalid, /Delayed project removal cannot be changed because it is locked by an ancestor/)
- end
- end
-
- context 'when parent locked the attribute then the application settings locks it' do
- before do
- subgroup_settings.update!(delayed_project_removal: true)
- group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false)
- stub_application_setting(lock_delayed_project_removal: true, delayed_project_removal: true)
-
- subgroup_settings.clear_memoization(:delayed_project_removal)
- subgroup_settings.clear_memoization(:delayed_project_removal_locked_ancestor)
- end
-
- it 'returns the application setting value' do
- expect(delayed_project_removal).to eq(true)
- end
- end
- end
-
- describe '#delayed_project_removal?' do
- before do
- subgroup_settings.update!(delayed_project_removal: true)
- group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false)
-
- subgroup_settings.clear_memoization(:delayed_project_removal)
- subgroup_settings.clear_memoization(:delayed_project_removal_locked_ancestor)
- end
-
- it 'aliases the method when the attribute is a boolean' do
- expect(subgroup_settings.delayed_project_removal?).to eq(subgroup_settings.delayed_project_removal)
- end
- end
-
- describe '#delayed_project_removal=' do
- before do
- subgroup_settings.update!(delayed_project_removal: nil)
- group_settings.update!(delayed_project_removal: true)
- end
-
- it 'does not save the value locally when it matches the cascaded value' do
- subgroup_settings.update!(delayed_project_removal: true)
-
- expect(subgroup_settings.read_attribute(:delayed_project_removal)).to eq(nil)
- end
- end
-
- describe '#delayed_project_removal_locked?' do
- shared_examples 'not locked' do
- it 'is not locked by an ancestor' do
- expect(subgroup_settings.delayed_project_removal_locked_by_ancestor?).to eq(false)
- end
-
- it 'is not locked by application setting' do
- expect(subgroup_settings.delayed_project_removal_locked_by_application_setting?).to eq(false)
- end
-
- it 'does not return a locked namespace' do
- expect(subgroup_settings.delayed_project_removal_locked_ancestor).to be_nil
- end
- end
-
- context 'when attribute is locked by self' do
- before do
- subgroup_settings.update!(lock_delayed_project_removal: true)
- end
-
- it 'is not locked by default' do
- expect(subgroup_settings.delayed_project_removal_locked?).to eq(false)
- end
-
- it 'is locked when including self' do
- expect(subgroup_settings.delayed_project_removal_locked?(include_self: true)).to eq(true)
- end
- end
-
- context 'when parent does not lock the attribute' do
- it_behaves_like 'not locked'
- end
-
- context 'when parent locks the attribute' do
- before do
- group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false)
-
- subgroup_settings.clear_memoization(:delayed_project_removal)
- subgroup_settings.clear_memoization(:delayed_project_removal_locked_ancestor)
- end
-
- it 'is locked by an ancestor' do
- expect(subgroup_settings.delayed_project_removal_locked_by_ancestor?).to eq(true)
- end
-
- it 'is not locked by application setting' do
- expect(subgroup_settings.delayed_project_removal_locked_by_application_setting?).to eq(false)
- end
-
- it 'returns a locked namespace settings object' do
- expect(subgroup_settings.delayed_project_removal_locked_ancestor.namespace_id).to eq(group_settings.namespace_id)
- end
- end
-
- context 'when not locked by application settings' do
- before do
- stub_application_setting(lock_delayed_project_removal: false)
- end
-
- it_behaves_like 'not locked'
- end
-
- context 'when locked by application settings' do
- before do
- stub_application_setting(lock_delayed_project_removal: true)
- end
-
- it 'is not locked by an ancestor' do
- expect(subgroup_settings.delayed_project_removal_locked_by_ancestor?).to eq(false)
- end
-
- it 'is locked by application setting' do
- expect(subgroup_settings.delayed_project_removal_locked_by_application_setting?).to eq(true)
- end
-
- it 'does not return a locked namespace' do
- expect(subgroup_settings.delayed_project_removal_locked_ancestor).to be_nil
- end
- end
- end
-
- describe '#lock_delayed_project_removal=' do
- context 'when parent locks the attribute' do
- before do
- group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false)
-
- subgroup_settings.clear_memoization(:delayed_project_removal)
- subgroup_settings.clear_memoization(:delayed_project_removal_locked_ancestor)
- end
-
- it 'does not allow the attribute to be saved' do
- subgroup_settings.lock_delayed_project_removal = true
-
- expect { subgroup_settings.save! }
- .to raise_error(ActiveRecord::RecordInvalid, /Lock delayed project removal cannot be changed because it is locked by an ancestor/)
- end
- end
-
- context 'when parent does not lock the attribute' do
- before do
- group_settings.update!(lock_delayed_project_removal: false)
-
- subgroup_settings.lock_delayed_project_removal = true
- end
-
- it 'allows the lock to be set when the attribute is not nil' do
- subgroup_settings.delayed_project_removal = true
-
- expect(subgroup_settings.save).to eq(true)
- end
-
- it 'does not allow the lock to be saved when the attribute is nil' do
- subgroup_settings.delayed_project_removal = nil
-
- expect { subgroup_settings.save! }
- .to raise_error(ActiveRecord::RecordInvalid, /Delayed project removal cannot be nil when locking the attribute/)
- end
-
- it 'copies the cascaded value when locking the attribute if the local value is nil', :aggregate_failures do
- subgroup_settings.delayed_project_removal = nil
- subgroup_settings.lock_delayed_project_removal = true
-
- expect(subgroup_settings.read_attribute(:delayed_project_removal)).to eq(false)
- end
- end
-
- context 'when application settings locks the attribute' do
- before do
- stub_application_setting(lock_delayed_project_removal: true)
- end
-
- it 'does not allow the attribute to be saved' do
- subgroup_settings.lock_delayed_project_removal = true
-
- expect { subgroup_settings.save! }
- .to raise_error(ActiveRecord::RecordInvalid, /Lock delayed project removal cannot be changed because it is locked by an ancestor/)
- end
- end
-
- context 'when application_settings does not lock the attribute' do
- before do
- stub_application_setting(lock_delayed_project_removal: false)
- end
-
- it 'allows the attribute to be saved' do
- subgroup_settings.delayed_project_removal = true
- subgroup_settings.lock_delayed_project_removal = true
-
- expect(subgroup_settings.save).to eq(true)
- end
- end
- end
-
- describe 'after update callback' do
- before do
- subgroup_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false)
- end
-
- it 'clears descendant locks' do
- group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: true)
-
- expect(subgroup_settings.reload.lock_delayed_project_removal).to eq(false)
- end
- end
-end
diff --git a/spec/models/namespace/package_setting_spec.rb b/spec/models/namespace/package_setting_spec.rb
index 4308c8c06bc..2584fa597ad 100644
--- a/spec/models/namespace/package_setting_spec.rb
+++ b/spec/models/namespace/package_setting_spec.rb
@@ -85,4 +85,13 @@ RSpec.describe Namespace::PackageSetting do
end
end
end
+
+ describe 'package forwarding attributes' do
+ %i[maven_package_requests_forwarding
+ pypi_package_requests_forwarding
+ npm_package_requests_forwarding].each do |attribute|
+ it_behaves_like 'a cascading namespace setting boolean attribute', settings_attribute_name: attribute,
+ settings_association: :package_settings
+ end
+ end
end
diff --git a/spec/models/namespace_setting_spec.rb b/spec/models/namespace_setting_spec.rb
index 4ac248802b8..a4446bfedd1 100644
--- a/spec/models/namespace_setting_spec.rb
+++ b/spec/models/namespace_setting_spec.rb
@@ -177,4 +177,8 @@ RSpec.describe NamespaceSetting, type: :model do
end
end
end
+
+ describe '#delayed_project_removal' do
+ it_behaves_like 'a cascading namespace setting boolean attribute', settings_attribute_name: :delayed_project_removal
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 7bf0491d11e..c6d028af22d 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -362,6 +362,9 @@ RSpec.describe Namespace do
it { is_expected.to delegate_method(:name).to(:owner).with_prefix.allow_nil }
it { is_expected.to delegate_method(:avatar_url).to(:owner).allow_nil }
it { is_expected.to delegate_method(:prevent_sharing_groups_outside_hierarchy).to(:namespace_settings).allow_nil }
+ it { is_expected.to delegate_method(:maven_package_requests_forwarding).to(:package_settings) }
+ it { is_expected.to delegate_method(:pypi_package_requests_forwarding).to(:package_settings) }
+ it { is_expected.to delegate_method(:npm_package_requests_forwarding).to(:package_settings) }
it do
is_expected.to delegate_method(:prevent_sharing_groups_outside_hierarchy=).to(:namespace_settings)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 97e9c51e21e..767cda952b0 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -859,6 +859,9 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to delegate_method(:environments_access_level).to(:project_feature) }
it { is_expected.to delegate_method(:feature_flags_access_level).to(:project_feature) }
it { is_expected.to delegate_method(:releases_access_level).to(:project_feature) }
+ it { is_expected.to delegate_method(:maven_package_requests_forwarding).to(:namespace) }
+ it { is_expected.to delegate_method(:pypi_package_requests_forwarding).to(:namespace) }
+ it { is_expected.to delegate_method(:npm_package_requests_forwarding).to(:namespace) }
describe 'read project settings' do
%i(
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
index d7cc6991ef4..b7c73467e8e 100644
--- a/spec/requests/api/maven_packages_spec.rb
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -254,7 +254,7 @@ RSpec.describe API::MavenPackages do
let(:package_name) { package_in_project ? package_file.file_name : 'foo' }
before do
- allow_fetch_application_setting(attribute: 'maven_package_requests_forwarding', return_value: forward)
+ allow_fetch_cascade_application_setting(attribute: 'maven_package_requests_forwarding', return_value: forward)
end
it_behaves_like params[:shared_examples_name]
@@ -273,7 +273,7 @@ RSpec.describe API::MavenPackages do
before do
stub_feature_flags(maven_central_request_forwarding: false)
- allow_fetch_application_setting(attribute: 'maven_package_requests_forwarding', return_value: forward)
+ allow_fetch_cascade_application_setting(attribute: 'maven_package_requests_forwarding', return_value: forward)
end
it_behaves_like params[:shared_examples_name]
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 687065a6516..709b5c20c92 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -851,6 +851,12 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
should_not_email(non_subscriber)
end
+ it 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
+ expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(merge_request)
+
+ update_merge_request(title: 'New title')
+ end
+
context 'when removing through wip_event param' do
it 'removes Draft from the title' do
expect { update_merge_request({ wip_event: "ready" }) }
@@ -877,6 +883,12 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
should_not_email(non_subscriber)
end
+ it 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
+ expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(merge_request)
+
+ update_merge_request(title: 'Draft: New title')
+ end
+
context 'when adding through wip_event param' do
it 'adds Draft to the title' do
expect { update_merge_request({ wip_event: "draft" }) }
diff --git a/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb b/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb
index 7c8b6250d24..1963248142c 100644
--- a/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb
+++ b/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb
@@ -1,6 +1,11 @@
# frozen_string_literal: true
RSpec.shared_context 'dependency proxy helpers context' do
+ def allow_fetch_cascade_application_setting(attribute:, return_value:)
+ allow(Gitlab::CurrentSettings).to receive(:public_send).with(attribute.to_sym).and_return(return_value)
+ allow(Gitlab::CurrentSettings).to receive(:public_send).with("lock_#{attribute}").and_return(false)
+ end
+
def allow_fetch_application_setting(attribute:, return_value:)
attributes = double
allow(::Gitlab::CurrentSettings.current_application_settings).to receive(:attributes).and_return(attributes)
diff --git a/spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb b/spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb
new file mode 100644
index 00000000000..d7a5bc6cc41
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb
@@ -0,0 +1,355 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples 'a cascading namespace setting boolean attribute' do
+ |settings_association: :namespace_settings, settings_attribute_name:|
+ let_it_be_with_reload(:group) { create(:group) }
+ let_it_be_with_reload(:subgroup) { create(:group, parent: group) }
+ let(:group_settings) { group.send(settings_association) }
+ let(:subgroup_settings) { subgroup.send(settings_association) }
+
+ describe "##{settings_attribute_name}" do
+ subject(:cascading_attribute) { subgroup_settings.send(settings_attribute_name) }
+
+ before do
+ stub_application_setting(settings_attribute_name => false)
+ end
+
+ context 'when there is no parent' do
+ context 'and the value is not nil' do
+ before do
+ group_settings.update!(settings_attribute_name => true)
+ end
+
+ it 'returns the local value' do
+ expect(group_settings.send(settings_attribute_name)).to eq(true)
+ end
+ end
+
+ context 'and the value is nil' do
+ before do
+ group_settings.update!(settings_attribute_name => nil)
+ end
+
+ it 'returns the application settings value' do
+ expect(group_settings.send(settings_attribute_name)).to eq(false)
+ end
+ end
+ end
+
+ context 'when parent does not lock the attribute' do
+ context 'and value is not nil' do
+ before do
+ group_settings.update!(settings_attribute_name => false)
+ end
+
+ it 'returns local setting when present' do
+ subgroup_settings.update!(settings_attribute_name => true)
+
+ expect(cascading_attribute).to eq(true)
+ end
+
+ it 'returns the parent value when local value is nil' do
+ subgroup_settings.update!(settings_attribute_name => nil)
+
+ expect(cascading_attribute).to eq(false)
+ end
+
+ it 'returns the correct dirty value' do
+ subgroup_settings.send("#{settings_attribute_name}=", true)
+
+ expect(cascading_attribute).to eq(true)
+ end
+
+ it 'does not return the application setting value when parent value is false' do
+ stub_application_setting(settings_attribute_name => true)
+
+ expect(cascading_attribute).to eq(false)
+ end
+ end
+
+ context 'and the value is nil' do
+ before do
+ group_settings.update!(settings_attribute_name => nil, "lock_#{settings_attribute_name}".to_sym => false)
+ subgroup_settings.update!(settings_attribute_name => nil)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ end
+
+ it 'cascades to the application settings value' do
+ expect(cascading_attribute).to eq(false)
+ end
+ end
+
+ context 'when multiple ancestors set a value' do
+ let(:third_level_subgroup) { create(:group, parent: subgroup) }
+
+ before do
+ group_settings.update!(settings_attribute_name => true)
+ subgroup_settings.update!(settings_attribute_name => false)
+ end
+
+ it 'returns the closest ancestor value' do
+ expect(third_level_subgroup.send(settings_association).send(settings_attribute_name)).to eq(false)
+ end
+ end
+ end
+
+ context 'when parent locks the attribute' do
+ before do
+ subgroup_settings.update!(settings_attribute_name => true)
+ group_settings.update!("lock_#{settings_attribute_name}" => true, settings_attribute_name => false)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ subgroup_settings.clear_memoization("#{settings_attribute_name}_locked_ancestor")
+ end
+
+ it 'returns the parent value' do
+ expect(cascading_attribute).to eq(false)
+ end
+
+ it 'does not allow the local value to be saved' do
+ subgroup_settings.send("#{settings_attribute_name}=", nil)
+
+ expect { subgroup_settings.save! }
+ .to raise_error(ActiveRecord::RecordInvalid,
+ /cannot be changed because it is locked by an ancestor/)
+ end
+ end
+
+ context 'when the application settings locks the attribute' do
+ before do
+ subgroup_settings.update!(settings_attribute_name => true)
+ stub_application_setting("lock_#{settings_attribute_name}" => true, settings_attribute_name => true)
+ end
+
+ it 'returns the application setting value' do
+ expect(cascading_attribute).to eq(true)
+ end
+
+ it 'does not allow the local value to be saved' do
+ subgroup_settings.send("#{settings_attribute_name}=", false)
+
+ expect { subgroup_settings.save! }
+ .to raise_error(
+ ActiveRecord::RecordInvalid,
+ /cannot be changed because it is locked by an ancestor/
+ )
+ end
+ end
+
+ context 'when parent locked the attribute then the application settings locks it' do
+ before do
+ subgroup_settings.update!(settings_attribute_name => true)
+ group_settings.update!("lock_#{settings_attribute_name}" => true, settings_attribute_name => false)
+ stub_application_setting("lock_#{settings_attribute_name}" => true, settings_attribute_name => true)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ subgroup_settings.clear_memoization("#{settings_attribute_name}_locked_ancestor")
+ end
+
+ it 'returns the application setting value' do
+ expect(cascading_attribute).to eq(true)
+ end
+ end
+ end
+
+ describe "##{settings_attribute_name}?" do
+ before do
+ subgroup_settings.update!(settings_attribute_name => true)
+ group_settings.update!("lock_#{settings_attribute_name}" => true, settings_attribute_name => false)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ subgroup_settings.clear_memoization("#{settings_attribute_name}_locked_ancestor")
+ end
+
+ it 'aliases the method when the attribute is a boolean' do
+ expect(subgroup_settings.send("#{settings_attribute_name}?"))
+ .to eq(subgroup_settings.send(settings_attribute_name))
+ end
+ end
+
+ describe "##{settings_attribute_name}=" do
+ before do
+ subgroup_settings.update!(settings_attribute_name => nil)
+ group_settings.update!(settings_attribute_name => true)
+ end
+
+ it 'does not save the value locally when it matches the cascaded value' do
+ subgroup_settings.update!(settings_attribute_name => true)
+
+ expect(subgroup_settings.read_attribute(settings_attribute_name)).to eq(nil)
+ end
+ end
+
+ describe "##{settings_attribute_name}_locked?" do
+ shared_examples 'not locked' do
+ it 'is not locked by an ancestor' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_ancestor?")).to eq(false)
+ end
+
+ it 'is not locked by application setting' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_application_setting?")).to eq(false)
+ end
+
+ it 'does not return a locked namespace' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_ancestor")).to be_nil
+ end
+ end
+
+ context 'when attribute is locked by self' do
+ before do
+ subgroup_settings.update!("lock_#{settings_attribute_name}" => true)
+ end
+
+ it 'is not locked by default' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked?")).to eq(false)
+ end
+
+ it 'is locked when including self' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked?", include_self: true)).to eq(true)
+ end
+ end
+
+ context 'when parent does not lock the attribute' do
+ it_behaves_like 'not locked'
+ end
+
+ context 'when parent locks the attribute' do
+ before do
+ group_settings.update!("lock_#{settings_attribute_name}".to_sym => true, settings_attribute_name => false)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ subgroup_settings.clear_memoization("#{settings_attribute_name}_locked_ancestor")
+ end
+
+ it 'is locked by an ancestor' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_ancestor?")).to eq(true)
+ end
+
+ it 'is not locked by application setting' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_application_setting?")).to eq(false)
+ end
+
+ it 'returns a locked namespace settings object' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_ancestor").namespace_id)
+ .to eq(group_settings.namespace_id)
+ end
+ end
+
+ context 'when not locked by application settings' do
+ before do
+ stub_application_setting("lock_#{settings_attribute_name}" => false)
+ end
+
+ it_behaves_like 'not locked'
+ end
+
+ context 'when locked by application settings' do
+ before do
+ stub_application_setting("lock_#{settings_attribute_name}" => true)
+ end
+
+ it 'is not locked by an ancestor' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_ancestor?")).to eq(false)
+ end
+
+ it 'is locked by application setting' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_application_setting?")).to eq(true)
+ end
+
+ it 'does not return a locked namespace' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_ancestor")).to be_nil
+ end
+ end
+ end
+
+ describe "#lock_#{settings_attribute_name}=" do
+ context 'when parent locks the attribute' do
+ before do
+ group_settings.update!("lock_#{settings_attribute_name}".to_sym => true, settings_attribute_name => false)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ subgroup_settings.clear_memoization("#{settings_attribute_name}_locked_ancestor")
+ end
+
+ it 'does not allow the attribute to be saved' do
+ subgroup_settings.send("lock_#{settings_attribute_name}=", true)
+
+ expect { subgroup_settings.save! }
+ .to raise_error(ActiveRecord::RecordInvalid,
+ /cannot be changed because it is locked by an ancestor/)
+ end
+ end
+
+ context 'when parent does not lock the attribute' do
+ before do
+ group_settings.update!("lock_#{settings_attribute_name}" => false, settings_attribute_name => false)
+
+ subgroup_settings.send("lock_#{settings_attribute_name}=", true)
+ end
+
+ it 'allows the lock to be set when the attribute is not nil' do
+ subgroup_settings.send("#{settings_attribute_name}=", true)
+
+ expect(subgroup_settings.save).to eq(true)
+ end
+
+ it 'does not allow the lock to be saved when the attribute is nil' do
+ subgroup_settings.send("#{settings_attribute_name}=", nil)
+
+ expect { subgroup_settings.save! }
+ .to raise_error(ActiveRecord::RecordInvalid,
+ /cannot be nil when locking the attribute/)
+ end
+
+ it 'copies the cascaded value when locking the attribute if the local value is nil', :aggregate_failures do
+ subgroup_settings.send("#{settings_attribute_name}=", nil)
+ subgroup_settings.send("lock_#{settings_attribute_name}=", true)
+
+ expect(subgroup_settings.read_attribute(settings_attribute_name)).to eq(false)
+ end
+ end
+
+ context 'when application settings locks the attribute' do
+ before do
+ stub_application_setting("lock_#{settings_attribute_name}".to_sym => true)
+ end
+
+ it 'does not allow the attribute to be saved' do
+ subgroup_settings.send("lock_#{settings_attribute_name}=", true)
+
+ expect { subgroup_settings.save! }
+ .to raise_error(ActiveRecord::RecordInvalid,
+ /cannot be changed because it is locked by an ancestor/)
+ end
+ end
+
+ context 'when application_settings does not lock the attribute' do
+ before do
+ stub_application_setting("lock_#{settings_attribute_name}".to_sym => false)
+ end
+
+ it 'allows the attribute to be saved' do
+ subgroup_settings.send("#{settings_attribute_name}=", true)
+ subgroup_settings.send("lock_#{settings_attribute_name}=", true)
+
+ expect(subgroup_settings.save).to eq(true)
+ end
+ end
+ end
+
+ describe 'after update callback' do
+ before do
+ group_settings.update!("lock_#{settings_attribute_name}" => false, settings_attribute_name => false)
+ subgroup_settings.update!("lock_#{settings_attribute_name}" => true, settings_attribute_name => false)
+ end
+
+ it 'clears descendant locks' do
+ group_settings.update!("lock_#{settings_attribute_name}" => true, settings_attribute_name => true)
+
+ expect(subgroup_settings.reload.send("lock_#{settings_attribute_name}")).to eq(false)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index b651ffc8996..85ac2b5e1ea 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -260,7 +260,11 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
project.send("add_#{user_role}", user) if user_role
project.update!(visibility: visibility.to_s)
package.update!(name: package_name) unless package_name == 'non-existing-package'
- allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
+ if scope == :instance
+ allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
+ else
+ allow_fetch_cascade_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
+ end
end
example_name = "#{params[:expected_result]} metadata request"
diff --git a/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb
index 86b6975bf9f..1d79a61fbb0 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb
@@ -14,6 +14,7 @@ RSpec.shared_examples 'accept package tags request' do |status:|
before do
allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: false)
+ allow_fetch_cascade_application_setting(attribute: "npm_package_requests_forwarding", return_value: false)
end
context 'with valid package name' do
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
index f411b5699a9..11e19d8d067 100644
--- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -291,7 +291,7 @@ RSpec.shared_examples 'pypi simple API endpoint' do
end
before do
- allow_fetch_application_setting(attribute: "pypi_package_requests_forwarding", return_value: forward)
+ allow_fetch_cascade_application_setting(attribute: "pypi_package_requests_forwarding", return_value: forward)
end
it_behaves_like params[:shared_examples_name], :reporter, params[:expected_status]