diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-13 12:10:13 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-13 12:10:13 +0000 |
commit | 70ce746bd011b101605e6d84f141d1f0c3175831 (patch) | |
tree | 1b76e654f61579b79b34116fa6bb06db2f511d94 /spec | |
parent | 61ca90e0b462bfe69e0b400dfe403bd95281b90f (diff) | |
download | gitlab-ce-70ce746bd011b101605e6d84f141d1f0c3175831.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
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] |