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/support_specs')
-rw-r--r--spec/support_specs/ability_check_spec.rb148
-rw-r--r--spec/support_specs/helpers/packages/npm_spec.rb133
-rw-r--r--spec/support_specs/matchers/exceed_redis_call_limit_spec.rb59
3 files changed, 340 insertions, 0 deletions
diff --git a/spec/support_specs/ability_check_spec.rb b/spec/support_specs/ability_check_spec.rb
new file mode 100644
index 00000000000..ce841112d86
--- /dev/null
+++ b/spec/support_specs/ability_check_spec.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require 'declarative_policy'
+require 'request_store'
+require 'tempfile'
+
+require 'gitlab/safe_request_store'
+
+require_relative '../../app/models/ability'
+require_relative '../support/ability_check'
+
+RSpec.describe Support::AbilityCheck, feature_category: :system_access do # rubocop:disable RSpec/FilePath
+ let(:user) { :user }
+ let(:child) { Testing::Child.new }
+ let(:parent) { Testing::Parent.new(child) }
+
+ before do
+ # Usually done in spec/spec_helper.
+ described_class.inject(Ability.singleton_class)
+
+ stub_const('Testing::BasePolicy', Class.new(DeclarativePolicy::Base))
+
+ stub_const('Testing::Parent', Struct.new(:parent_of))
+ stub_const('Testing::ParentPolicy', Class.new(Testing::BasePolicy) do
+ delegate { @subject.parent_of }
+ condition(:is_adult) { @subject.is_a?(Testing::Parent) }
+ rule { is_adult }.enable :drink_coffee
+ end)
+
+ stub_const('Testing::Child', Class.new)
+ stub_const('Testing::ChildPolicy', Class.new(Testing::BasePolicy) do
+ condition(:always) { true }
+ rule { always }.enable :eat_ice
+ end)
+ end
+
+ def expect_no_deprecation_warning(&block)
+ expect(&block).not_to output.to_stderr
+ end
+
+ def expect_deprecation_warning(policy_class, ability, &block)
+ expect(&block)
+ .to output(/DEPRECATION WARNING: Ability :#{ability} in #{policy_class} not found./)
+ .to_stderr
+ end
+
+ def expect_allowed(user, ability, subject)
+ expect(Ability.allowed?(user, ability, subject))
+ end
+
+ shared_examples 'ability found' do
+ it 'policy ability is found' do
+ expect_no_deprecation_warning do
+ expect_allowed(user, ability, subject).to eq(true)
+ end
+ end
+ end
+
+ shared_examples 'ability not found' do |warning:|
+ description = 'policy ability is not found'
+ description += warning ? ' and emits a warning' : ' without warning'
+
+ it description do
+ check = -> { expect_allowed(user, ability, subject).to eq(false) }
+
+ if warning
+ expect_deprecation_warning(warning, ability, &check)
+ else
+ expect_no_deprecation_warning(&check)
+ end
+ end
+ end
+
+ shared_context 'with custom TODO YAML' do
+ let(:yaml_file) { Tempfile.new }
+
+ before do
+ yaml_file.write(yaml_content)
+ yaml_file.rewind
+
+ stub_const("#{described_class}::Checker::TODO_YAML", yaml_file.path)
+ described_class::Checker.clear_memoization(:todo_list)
+ end
+
+ after do
+ described_class::Checker.clear_memoization(:todo_list)
+ yaml_file.unlink
+ end
+ end
+
+ describe 'checking ability' do
+ context 'with valid direct ability' do
+ let(:subject) { parent }
+ let(:ability) { :drink_coffee }
+
+ include_examples 'ability found'
+
+ context 'with empty TODO yaml' do
+ let(:yaml_content) { nil }
+
+ include_context 'with custom TODO YAML'
+ include_examples 'ability found'
+ end
+
+ context 'with non-Hash TODO yaml' do
+ let(:yaml_content) { '[]' }
+
+ include_context 'with custom TODO YAML'
+ include_examples 'ability found'
+ end
+ end
+
+ context 'with unreachable ability' do
+ let(:subject) { child }
+ let(:ability) { :drink_coffee }
+
+ include_examples 'ability not found', warning: 'Testing::ChildPolicy'
+
+ context 'when ignored in TODO YAML' do
+ let(:yaml_content) do
+ <<~YAML
+ Testing::ChildPolicy:
+ - #{ability}
+ YAML
+ end
+
+ include_context 'with custom TODO YAML'
+ include_examples 'ability not found', warning: false
+ end
+ end
+
+ context 'with unknown ability' do
+ let(:subject) { parent }
+ let(:ability) { :unknown }
+
+ include_examples 'ability not found', warning: 'Testing::ParentPolicy'
+ end
+
+ context 'with delegated ability' do
+ let(:subject) { parent }
+ let(:ability) { :eat_ice }
+
+ include_examples 'ability found'
+ end
+ end
+end
diff --git a/spec/support_specs/helpers/packages/npm_spec.rb b/spec/support_specs/helpers/packages/npm_spec.rb
new file mode 100644
index 00000000000..e1316a10fb1
--- /dev/null
+++ b/spec/support_specs/helpers/packages/npm_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::API::Helpers::Packages::Npm, feature_category: :package_registry do # rubocop: disable RSpec/FilePath
+ let(:object) { klass.new(params) }
+ let(:klass) do
+ Struct.new(:params) do
+ include ::API::Helpers
+ include ::API::Helpers::Packages::Npm
+ end
+ end
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:namespace) { group }
+ let_it_be(:project) { create(:project, :public, namespace: namespace) }
+ let_it_be(:package) { create(:npm_package, project: project) }
+
+ describe '#endpoint_scope' do
+ subject { object.endpoint_scope }
+
+ context 'when params includes an id' do
+ let(:params) { { id: 42, package_name: 'foo' } }
+
+ it { is_expected.to eq(:project) }
+ end
+
+ context 'when params does not include an id' do
+ let(:params) { { package_name: 'foo' } }
+
+ it { is_expected.to eq(:instance) }
+ end
+ end
+
+ describe '#finder_for_endpoint_scope' do
+ subject { object.finder_for_endpoint_scope(package_name) }
+
+ let(:package_name) { package.name }
+
+ context 'when called with project scope' do
+ let(:params) { { id: project.id } }
+
+ it 'returns a PackageFinder for project scope' do
+ expect(::Packages::Npm::PackageFinder).to receive(:new).with(package_name, project: project)
+
+ subject
+ end
+ end
+
+ context 'when called with instance scope' do
+ let(:params) { { package_name: package_name } }
+
+ it 'returns a PackageFinder for namespace scope' do
+ expect(::Packages::Npm::PackageFinder).to receive(:new).with(package_name, namespace: group)
+
+ subject
+ end
+ end
+ end
+
+ describe '#project_id_or_nil' do
+ subject { object.project_id_or_nil }
+
+ context 'when called with project scope' do
+ let(:params) { { id: project.id } }
+
+ it { is_expected.to eq(project.id) }
+ end
+
+ context 'when called with namespace scope' do
+ context 'when given an unscoped name' do
+ let(:params) { { package_name: 'foo' } }
+
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'when given a scope that does not match a group name' do
+ let(:params) { { package_name: '@nonexistent-group/foo' } }
+
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'when given a scope that matches a group name' do
+ let(:params) { { package_name: package.name } }
+
+ it { is_expected.to eq(project.id) }
+
+ context 'with another package with the same name, in another project in the namespace' do
+ let_it_be(:project2) { create(:project, :public, namespace: namespace) }
+ let_it_be(:package2) { create(:npm_package, name: package.name, project: project2) }
+
+ it 'returns the project id for the newest matching package within the scope' do
+ expect(subject).to eq(project2.id)
+ end
+ end
+ end
+
+ context 'with npm_allow_packages_in_multiple_projects disabled' do
+ before do
+ stub_feature_flags(npm_allow_packages_in_multiple_projects: false)
+ end
+
+ context 'when given an unscoped name' do
+ let(:params) { { package_name: 'foo' } }
+
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'when given a scope that does not match a group name' do
+ let(:params) { { package_name: '@nonexistent-group/foo' } }
+
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'when given a scope that matches a group name' do
+ let(:params) { { package_name: package.name } }
+
+ it { is_expected.to eq(project.id) }
+
+ context 'with another package with the same name, in another project in the namespace' do
+ let_it_be(:project2) { create(:project, :public, namespace: namespace) }
+ let_it_be(:package2) { create(:npm_package, name: package.name, project: project2) }
+
+ it 'returns the project id for the newest matching package within the scope' do
+ expect(subject).to eq(project2.id)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support_specs/matchers/exceed_redis_call_limit_spec.rb b/spec/support_specs/matchers/exceed_redis_call_limit_spec.rb
new file mode 100644
index 00000000000..819f50e26b6
--- /dev/null
+++ b/spec/support_specs/matchers/exceed_redis_call_limit_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'RedisCommand matchers', :use_clean_rails_redis_caching, feature_category: :source_code_management do
+ let(:control) do
+ RedisCommands::Recorder.new do
+ Rails.cache.read('test')
+ Rails.cache.read('test')
+ Rails.cache.write('test', 1)
+ end
+ end
+
+ before do
+ Rails.cache.read('warmup')
+ end
+
+ it 'verifies maximum number of Redis calls' do
+ expect(control).not_to exceed_redis_calls_limit(3)
+
+ expect(control).not_to exceed_redis_command_calls_limit(:get, 2)
+ expect(control).not_to exceed_redis_command_calls_limit(:set, 1)
+ end
+
+ it 'verifies minimum number of Redis calls' do
+ expect(control).to exceed_redis_calls_limit(2)
+
+ expect(control).to exceed_redis_command_calls_limit(:get, 1)
+ expect(control).to exceed_redis_command_calls_limit(:set, 0)
+ end
+
+ context 'with Recorder matching only some Redis calls' do
+ it 'counts only Redis calls captured by Recorder' do
+ Rails.cache.write('ignored', 1)
+
+ control = RedisCommands::Recorder.new do
+ Rails.cache.read('recorded')
+ end
+
+ Rails.cache.write('also_ignored', 1)
+
+ expect(control).not_to exceed_redis_calls_limit(1)
+ expect(control).not_to exceed_redis_command_calls_limit(:set, 0)
+ expect(control).not_to exceed_redis_command_calls_limit(:get, 1)
+ end
+ end
+
+ context 'when expect part is a function' do
+ it 'automatically enables RedisCommand::Recorder for it' do
+ func = -> do
+ Rails.cache.read('test')
+ Rails.cache.read('test')
+ end
+
+ expect { func.call }.not_to exceed_redis_calls_limit(2)
+ expect { func.call }.not_to exceed_redis_command_calls_limit(:get, 2)
+ end
+ end
+end