diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-19 12:10:52 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-19 12:10:52 +0300 |
commit | 17295c75a1a28df78f719e0098dd31fe45ce0446 (patch) | |
tree | 0544bd2f74e72e45b4a62ff68a4736c26a02a832 /spec | |
parent | 6c2b987064064500b42da924d86d43473bfd2b7f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
15 files changed, 559 insertions, 129 deletions
diff --git a/spec/bin/feature_flag_spec.rb b/spec/bin/feature_flag_spec.rb index d1e4be5be28..f9caa5549ca 100644 --- a/spec/bin/feature_flag_spec.rb +++ b/spec/bin/feature_flag_spec.rb @@ -7,12 +7,28 @@ load File.expand_path('../../bin/feature-flag', __dir__) RSpec.describe 'bin/feature-flag', feature_category: :feature_flags do using RSpec::Parameterized::TableSyntax + let(:groups) do + { + geo: { label: 'group::geo' } + } + end + + before do + allow(HTTParty) + .to receive(:get) + .with(FeatureFlagOptionParser::WWW_GITLAB_COM_GROUPS_JSON, format: :plain) + .and_return(groups.to_json) + end + describe FeatureFlagCreator do - let(:argv) { %w[feature-flag-name -t development -g group::geo -i https://url -m http://url] } + let(:argv) { %w[feature-flag-name -t gitlab_com_derisk -g group::geo -a https://url -i https://url -m http://url -u username -M 16.6] } let(:options) { FeatureFlagOptionParser.parse(argv) } let(:creator) { described_class.new(options) } let(:existing_flags) do - { 'existing_feature_flag' => File.join('config', 'feature_flags', 'development', 'existing_feature_flag.yml') } + { + 'existing_feature_flag' => + File.join('config', 'feature_flags', 'gitlab_com_derisk', 'existing_feature_flag.yml') + } end before do @@ -31,7 +47,7 @@ RSpec.describe 'bin/feature-flag', feature_category: :feature_flags do it 'properly creates a feature flag' do expect(File).to receive(:write).with( - File.join('config', 'feature_flags', 'development', 'feature_flag_name.yml'), + File.join('config', 'feature_flags', 'gitlab_com_derisk', 'feature_flag_name.yml'), anything) expect do @@ -108,85 +124,97 @@ RSpec.describe 'bin/feature-flag', feature_category: :feature_flags do end describe '.read_type' do - let(:type) { 'development' } + before do + stub_const('FeatureFlagOptionParser::TYPES', + development: { description: 'short' }, + deprecated: { description: 'deprecated', deprecated: true }, + licensed: { description: 'licensed' } + ) + end - context 'when there is only a single type defined' do - before do - stub_const('FeatureFlagOptionParser::TYPES', - development: { description: 'short' } - ) - end + context 'when valid type is given' do + let(:type) { 'development' } - it 'returns that type' do - expect(described_class.read_type).to eq(:development) + it 'reads type from stdin' do + expect(Readline).to receive(:readline).and_return(type) + expect do + expect(described_class.read_type).to eq(:development) + end.to output(/Specify the feature flag type/).to_stdout end end - context 'when there is deprecated feature flag type' do - before do - stub_const('FeatureFlagOptionParser::TYPES', - development: { description: 'short' }, - deprecated: { description: 'deprecated', deprecated: true } - ) + context 'when valid index is given' do + it 'picks the type successfully' do + expect(Readline).to receive(:readline).and_return('3') + expect do + expect(described_class.read_type).to eq(:licensed) + end.to output(/Specify the feature flag type./).to_stdout end + end - context 'and deprecated type is given' do - let(:type) { 'deprecated' } + context 'when deprecated type is given' do + let(:type) { 'deprecated' } - it 'shows error message and retries' do - expect(Readline).to receive(:readline).and_return(type) - expect(Readline).to receive(:readline).and_raise('EOF') + it 'shows error message and retries' do + expect(Readline).to receive(:readline).and_return(type) + expect(Readline).to receive(:readline).and_raise('EOF') - expect do - expect { described_class.read_type }.to raise_error(/EOF/) - end.to output(/Specify the feature flag type/).to_stdout - .and output(/Invalid type specified/).to_stderr - end + expect do + expect { described_class.read_type }.to raise_error(/EOF/) + end.to output(/Specify the feature flag type/).to_stdout + .and output(/Invalid type specified/).to_stderr end end - context 'when there are many types defined' do - before do - stub_const('FeatureFlagOptionParser::TYPES', - development: { description: 'short' }, - licensed: { description: 'licensed' } - ) - end + context 'when invalid type is given' do + let(:type) { 'invalid' } - it 'reads type from stdin' do + it 'shows error message and retries' do expect(Readline).to receive(:readline).and_return(type) + expect(Readline).to receive(:readline).and_raise('EOF') + expect do - expect(described_class.read_type).to eq(:development) + expect { described_class.read_type }.to raise_error(/EOF/) end.to output(/Specify the feature flag type/).to_stdout + .and output(/Invalid type specified/).to_stderr end + end - context 'when invalid type is given' do - let(:type) { 'invalid' } - - it 'shows error message and retries' do - expect(Readline).to receive(:readline).and_return(type) - expect(Readline).to receive(:readline).and_raise('EOF') + context 'when invalid index is given' do + it 'shows error message and retries' do + expect(Readline).to receive(:readline).and_return('12') + expect(Readline).to receive(:readline).and_raise('EOF') - expect do - expect { described_class.read_type }.to raise_error(/EOF/) - end.to output(/Specify the feature flag type/).to_stdout - .and output(/Invalid type specified/).to_stderr - end + expect do + expect { described_class.read_type }.to raise_error(/EOF/) + end.to output(/Specify the feature flag type/).to_stdout + .and output(/Invalid type specified/).to_stderr end end end describe '.read_group' do - let(:group) { 'group::geo' } + context 'when valid group is given' do + let(:group) { 'group::geo' } - it 'reads type from stdin' do - expect(Readline).to receive(:readline).and_return(group) - expect do - expect(described_class.read_group).to eq('group::geo') - end.to output(/Specify the group introducing the feature flag/).to_stdout + it 'reads group from stdin' do + expect(Readline).to receive(:readline).and_return(group) + expect do + expect(described_class.read_group).to eq('group::geo') + end.to output(/Specify the group label to which the feature flag belongs, from the following list/).to_stdout + end end - context 'invalid group given' do + context 'when valid index is given' do + it 'picks the group successfully' do + expect(Readline).to receive(:readline).and_return('1') + expect do + expect(described_class.read_group).to eq('group::geo') + end.to output(/Specify the group label to which the feature flag belongs, from the following list/).to_stdout + end + end + + context 'with invalid group given' do let(:type) { 'invalid' } it 'shows error message and retries' do @@ -195,78 +223,151 @@ RSpec.describe 'bin/feature-flag', feature_category: :feature_flags do expect do expect { described_class.read_group }.to raise_error(/EOF/) - end.to output(/Specify the group introducing the feature flag/).to_stdout - .and output(/The group needs to include/).to_stderr + end.to output(/Specify the group label to which the feature flag belongs, from the following list/).to_stdout + .and output(/The group label isn't in the above labels list/).to_stderr + end + end + + context 'when invalid index is given' do + it 'shows error message and retries' do + expect(Readline).to receive(:readline).and_return('12') + expect(Readline).to receive(:readline).and_raise('EOF') + + expect do + expect { described_class.read_group }.to raise_error(/EOF/) + end.to output(/Specify the group label to which the feature flag belongs, from the following list/).to_stdout + .and output(/The group label isn't in the above labels list/).to_stderr end end end - describe '.read_introduced_by_url' do - let(:url) { 'https://merge-request' } + shared_examples 'read_url' do |method, prompt| + context 'with valid URL given' do + let(:url) { 'https://merge-request' } - it 'reads type from stdin' do - expect(Readline).to receive(:readline).and_return(url) - expect do - expect(described_class.read_introduced_by_url).to eq('https://merge-request') - end.to output(/URL of the MR introducing the feature flag/).to_stdout + it 'reads URL from stdin' do + expect(Readline).to receive(:readline).and_return(url) + expect(HTTParty).to receive(:head).with(url).and_return(instance_double(HTTParty::Response, success?: true)) + + expect do + expect(described_class.public_send(method)).to eq('https://merge-request') + end.to output(/#{prompt}/).to_stdout + end end - context 'empty URL given' do + context 'with invalid URL given' do + let(:url) { 'https://invalid' } + + it 'shows error message and retries' do + expect(Readline).to receive(:readline).and_return(url) + expect(HTTParty).to receive(:head).with(url).and_return(instance_double(HTTParty::Response, success?: false)) + expect(Readline).to receive(:readline).and_raise('EOF') + + expect do + expect { described_class.public_send(method) }.to raise_error(/EOF/) + end.to output(/#{prompt}/).to_stdout + .and output(/URL '#{url}' isn't valid/).to_stderr + end + end + + context 'with empty URL given' do let(:url) { '' } it 'skips entry' do expect(Readline).to receive(:readline).and_return(url) + expect do - expect(described_class.read_introduced_by_url).to be_nil - end.to output(/URL of the MR introducing the feature flag/).to_stdout + expect(described_class.public_send(method)).to be_nil + end.to output(/#{prompt}/).to_stdout end end - context 'invalid URL given' do - let(:url) { 'invalid' } + context 'with a non-URL given' do + let(:url) { 'malformed' } it 'shows error message and retries' do expect(Readline).to receive(:readline).and_return(url) expect(Readline).to receive(:readline).and_raise('EOF') expect do - expect { described_class.read_introduced_by_url }.to raise_error(/EOF/) - end.to output(/URL of the MR introducing the feature flag/).to_stdout + expect { described_class.public_send(method) }.to raise_error(/EOF/) + end.to output(/#{prompt}/).to_stdout .and output(/URL needs to start with/).to_stderr end end end + describe '.read_feature_issue_url' do + it_behaves_like 'read_url', :read_feature_issue_url, 'URL of the original feature issue' + end + + describe '.read_introduced_by_url' do + it_behaves_like 'read_url', :read_introduced_by_url, 'URL of the MR introducing the feature flag' + end + describe '.read_rollout_issue_url' do - let(:options) { double('options', name: 'foo', type: :development) } - let(:url) { 'https://issue' } + let(:options) do + FeatureFlagOptionParser::Options.new({ + name: 'foo', + username: 'joe', + type: :gitlab_com_derisk, + introduced_by_url: 'https://introduced_by_url', + feature_issue_url: 'https://feature_issue_url', + milestone: '16.6', + group: 'group::geo' + }) + end - it 'reads type from stdin' do - expect(Readline).to receive(:readline).and_return(url) - expect do - expect(described_class.read_rollout_issue_url(options)).to eq('https://issue') - end.to output(/URL of the rollout issue/).to_stdout + context 'with valid URL given' do + let(:url) { 'https://rollout_issue_url' } + + it 'reads type from stdin' do + expect(described_class).to receive(:copy_to_clipboard!).and_return(true) + expect(Readline).to receive(:readline).and_return('') # enter to open the new issue url + expect(described_class).to receive(:open_url!).and_return(true) + expect(Readline).to receive(:readline).and_return(url) + expect(HTTParty).to receive(:head).with(url).and_return(instance_double(HTTParty::Response, success?: true)) + + expect do + expect(described_class.read_rollout_issue_url(options)).to eq(url) + end.to output(/URL of the rollout issue/).to_stdout + end end - context 'invalid URL given' do - let(:type) { 'invalid' } + context 'with invalid URL given' do + let(:url) { 'https://invalid' } it 'shows error message and retries' do - expect(Readline).to receive(:readline).and_return(type) + expect(described_class).to receive(:copy_to_clipboard!).and_return(true) + expect(Readline).to receive(:readline).and_return('') # enter to open the new issue url + expect(described_class).to receive(:open_url!).and_return(true) + expect(Readline).to receive(:readline).and_return(url) + expect(HTTParty).to receive(:head).with(url).and_return(instance_double(HTTParty::Response, success?: false)) expect(Readline).to receive(:readline).and_raise('EOF') expect do expect { described_class.read_rollout_issue_url(options) }.to raise_error(/EOF/) end.to output(/URL of the rollout issue/).to_stdout - .and output(/URL needs to start/).to_stderr + .and output(/URL '#{url}' isn't valid/).to_stderr end end - end - describe '.read_ee_only' do - let(:options) { double('options', name: 'foo', type: :development) } + context 'with a non-URL given' do + let(:url) { 'malformed' } + + it 'shows error message and retries' do + expect(described_class).to receive(:copy_to_clipboard!).and_return(true) + expect(Readline).to receive(:readline).and_return('') # enter to open the new issue url + expect(described_class).to receive(:open_url!).and_return(true) + expect(Readline).to receive(:readline).and_return(url) + expect(Readline).to receive(:readline).and_raise('EOF') - it { expect(described_class.read_ee_only(options)).to eq(false) } + expect do + expect { described_class.read_rollout_issue_url(options) }.to raise_error(/EOF/) + end.to output(/URL of the rollout issue/).to_stdout + .and output(/URL needs to start/).to_stderr + end + end end end end diff --git a/spec/finders/packages/terraform_module/packages_finder_spec.rb b/spec/finders/packages/terraform_module/packages_finder_spec.rb new file mode 100644 index 00000000000..4550b3be055 --- /dev/null +++ b/spec/finders/packages/terraform_module/packages_finder_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Packages::TerraformModule::PackagesFinder, feature_category: :package_registry do + let_it_be(:project) { create(:project) } + let_it_be(:package1) { create(:terraform_module_package, project: project, version: '1.0.0') } + let_it_be(:package2) { create(:terraform_module_package, project: project, version: '2.0.0', name: package1.name) } + + let(:params) { {} } + + subject { described_class.new(project, params).execute } + + describe '#execute' do + context 'without project' do + let(:project) { nil } + + it { is_expected.to be_empty } + end + + context 'without package_name' do + let(:params) { { package_name: nil } } + + it { is_expected.to be_empty } + end + + context 'with package_name' do + let(:params) { { package_name: package1.name } } + + it 'returns packages with the given name ordered by version desc' do + is_expected.to eq([package2, package1]) + end + + context 'with package_version' do + let(:params) { { package_name: package1.name, package_version: package1.version } } + + it { is_expected.to eq([package1]) } + end + + context 'when package is not installable' do + before do + package1.update_column(:status, 3) + end + + it { is_expected.to eq([package2]) } + end + + context 'when package has no version' do + before do + package1.update_column(:version, nil) + end + + it { is_expected.to eq([package2]) } + end + + context 'when package is not a terraform module' do + before do + package1.update_column(:package_type, 1) + end + + it { is_expected.to eq([package2]) } + end + end + end +end diff --git a/spec/graphql/types/namespace_type_spec.rb b/spec/graphql/types/namespace_type_spec.rb index 9e1a2bfd466..d80235023ef 100644 --- a/spec/graphql/types/namespace_type_spec.rb +++ b/spec/graphql/types/namespace_type_spec.rb @@ -15,5 +15,5 @@ RSpec.describe GitlabSchema.types['Namespace'] do expect(described_class).to include_graphql_fields(*expected_fields) end - specify { expect(described_class).to require_graphql_authorizations(:read_namespace_via_membership) } + specify { expect(described_class).to require_graphql_authorizations(:read_namespace) } end diff --git a/spec/lib/feature/definition_spec.rb b/spec/lib/feature/definition_spec.rb index 595725d357c..b75c780a33e 100644 --- a/spec/lib/feature/definition_spec.rb +++ b/spec/lib/feature/definition_spec.rb @@ -30,11 +30,11 @@ RSpec.describe Feature::Definition do :name | 'ALL_CAPS' | /Feature flag 'ALL_CAPS' is invalid/ :name | nil | /Feature flag is missing name/ :path | nil | /Feature flag 'feature_flag' is missing path/ - :type | nil | /Feature flag 'feature_flag' is missing type/ + :type | nil | /Feature flag 'feature_flag' is missing `type`/ :type | 'invalid' | /Feature flag 'feature_flag' type 'invalid' is invalid/ :path | 'development/invalid.yml' | /Feature flag 'feature_flag' has an invalid path/ - :path | 'invalid/feature_flag.yml' | /Feature flag 'feature_flag' has an invalid type/ - :default_enabled | nil | /Feature flag 'feature_flag' is missing default_enabled/ + :path | 'invalid/feature_flag.yml' | /Feature flag 'feature_flag' has an invalid path/ + :default_enabled | nil | /Feature flag 'feature_flag' is missing `default_enabled`/ end with_them do diff --git a/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb index 1940442d2ad..903ae64cf33 100644 --- a/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb @@ -31,6 +31,14 @@ RSpec.describe Gitlab::UsageDataCounters::QuickActionActivityUniqueCounter, :cle end end + context 'when tracking react' do + let(:quickaction_name) { 'react' } + + it_behaves_like 'a tracked quick action unique event' do + let(:action) { 'i_quickactions_award' } + end + end + context 'tracking assigns' do let(:quickaction_name) { 'assign' } diff --git a/spec/models/work_items/hierarchy_restriction_spec.rb b/spec/models/work_items/hierarchy_restriction_spec.rb index 2c4d5d32fb8..890c007b6cd 100644 --- a/spec/models/work_items/hierarchy_restriction_spec.rb +++ b/spec/models/work_items/hierarchy_restriction_spec.rb @@ -15,4 +15,24 @@ RSpec.describe WorkItems::HierarchyRestriction do it { is_expected.to validate_presence_of(:child_type) } it { is_expected.to validate_uniqueness_of(:child_type).scoped_to(:parent_type_id) } end + + describe '#clear_parent_type_cache!' do + subject(:hierarchy_restriction) { build(:hierarchy_restriction) } + + context 'when a hierarchy restriction is saved' do + it 'calls #clear_reactive_cache! on parent type' do + expect(hierarchy_restriction.parent_type).to receive(:clear_reactive_cache!).once + + hierarchy_restriction.save! + end + end + + context 'when a hierarchy restriction is destroyed' do + it 'calls #clear_reactive_cache! on parent type' do + expect(hierarchy_restriction.parent_type).to receive(:clear_reactive_cache!).once + + hierarchy_restriction.destroy! + end + end + end end diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb index a4bc94798be..107fcd8dcdd 100644 --- a/spec/requests/api/graphql/namespace/projects_spec.rb +++ b/spec/requests/api/graphql/namespace/projects_spec.rb @@ -20,6 +20,7 @@ RSpec.describe 'getting projects', feature_category: :groups_and_projects do 'namespace', { 'fullPath' => subject.full_path }, <<~QUERY + id projects(includeSubgroups: #{include_subgroups}) { edges { node { @@ -53,24 +54,30 @@ RSpec.describe 'getting projects', feature_category: :groups_and_projects do expect(graphql_data['namespace']['projects']['edges'].size).to eq(count) end + end - context 'with no user' do - it 'finds only public projects' do - post_graphql(query, current_user: nil) + it_behaves_like 'a graphql namespace' - expect(graphql_data['namespace']).to be_nil - end + context 'when no user is given' do + it 'finds only public projects' do + post_graphql(query, current_user: nil) + + expect(graphql_data_at(:namespace, :projects, :edges).size).to eq(1) end end - it_behaves_like 'a graphql namespace' - context 'when the namespace is a user' do subject { user.namespace } let(:include_subgroups) { false } it_behaves_like 'a graphql namespace' + + it 'does not show namespace entity for anonymous user' do + post_graphql(query, current_user: nil) + + expect(graphql_data['namespace']).to be_nil + end end context 'when not including subgroups' do diff --git a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb index c8819f1e38f..273b6b8c25b 100644 --- a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb +++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb @@ -63,7 +63,7 @@ RSpec.describe 'rendering namespace statistics', feature_category: :metrics do it 'hides statistics for unauthenticated requests' do post_graphql(query, current_user: nil) - expect(graphql_data['namespace']).to be_blank + expect(graphql_data_at(:namespace, :root_storage_statistics)).to be_blank end end end diff --git a/spec/requests/api/graphql/namespace_query_spec.rb b/spec/requests/api/graphql/namespace_query_spec.rb index c0c7c5fee2b..86808915564 100644 --- a/spec/requests/api/graphql/namespace_query_spec.rb +++ b/spec/requests/api/graphql/namespace_query_spec.rb @@ -8,7 +8,8 @@ RSpec.describe 'Query', feature_category: :groups_and_projects do let_it_be(:user) { create(:user) } let_it_be(:other_user) { create(:user) } - let_it_be(:group_namespace) { create(:group) } + let_it_be(:group_namespace) { create(:group, :private) } + let_it_be(:public_group_namespace) { create(:group, :public) } let_it_be(:user_namespace) { create(:user_namespace, owner: user) } let_it_be(:project_namespace) { create(:project_namespace, parent: group_namespace) } @@ -60,6 +61,51 @@ RSpec.describe 'Query', feature_category: :groups_and_projects do end end + context 'when used with a public group' do + let(:target_namespace) { public_group_namespace } + + before do + subject + end + + it_behaves_like 'a working graphql query' + + context 'when user is a member' do + before do + public_group_namespace.add_developer(user) + end + + it 'fetches the expected data' do + expect(query_result).to include( + 'fullPath' => target_namespace.full_path, + 'name' => target_namespace.name + ) + end + end + + context 'when user is anonymous' do + let(:current_user) { nil } + + it 'fetches the expected data' do + expect(query_result).to include( + 'fullPath' => target_namespace.full_path, + 'name' => target_namespace.name + ) + end + end + + context 'when user is not a member' do + let(:current_user) { other_user } + + it 'fetches the expected data' do + expect(query_result).to include( + 'fullPath' => target_namespace.full_path, + 'name' => target_namespace.name + ) + end + end + end + it_behaves_like 'retrieving a namespace' do let(:target_namespace) { group_namespace } diff --git a/spec/requests/api/terraform/modules/v1/project_packages_spec.rb b/spec/requests/api/terraform/modules/v1/project_packages_spec.rb index 1f3b2283d59..3377f8d6647 100644 --- a/spec/requests/api/terraform/modules/v1/project_packages_spec.rb +++ b/spec/requests/api/terraform/modules/v1/project_packages_spec.rb @@ -6,6 +6,18 @@ RSpec.describe API::Terraform::Modules::V1::ProjectPackages, feature_category: : include_context 'for terraform modules api setup' using RSpec::Parameterized::TableSyntax + describe 'GET /api/v4/projects/:project_id/packages/terraform/modules/:module_name/:module_system' do + it_behaves_like 'handling project level terraform module download requests' do + let(:module_version) { nil } + end + end + + describe 'GET /api/v4/projects/:project_id/packages/terraform/modules/:module_name/:module_system/:module_version' do + it_behaves_like 'handling project level terraform module download requests' do + let(:module_version) { package.version } + end + end + describe 'PUT /api/v4/projects/:project_id/packages/terraform/modules/:module_name/:module_system/:module_version/file/authorize' do include_context 'workhorse headers' diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb index 63b5d54a18d..0e46391c0ad 100644 --- a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb +++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb @@ -188,28 +188,4 @@ RSpec.describe PagesDomains::ObtainLetsEncryptCertificateService, feature_catego service.execute end end - - context 'when the domain URL is longer than 64 characters' do - let(:long_domain) { "a.b.c.#{'d' * 63}" } - let(:pages_domain) { create(:pages_domain, :without_certificate, :without_key, domain: long_domain) } - let(:service) { described_class.new(pages_domain) } - - it 'logs an error and does not proceed with certificate acquisition' do - expect(Gitlab::AppLogger).to receive(:error).with( - hash_including( - message: "Domain name too long for Let's Encrypt certificate", - pages_domain: long_domain, - pages_domain_bytesize: long_domain.bytesize, - max_allowed_bytesize: described_class::MAX_DOMAIN_LENGTH, - project_id: pages_domain.project_id - ) - ) - - # Ensure that the certificate acquisition is not attempted - expect(::PagesDomains::CreateAcmeOrderService).not_to receive(:new) - expect(PagesDomainSslRenewalWorker).not_to receive(:perform_in) - - service.execute - end - end end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index dc93fd96aee..c1dbb5b80b3 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -564,7 +564,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning it 'returns the reaction message' do _, _, message = service.execute(content, issuable) - expect(message).to eq('Toggled :100: emoji award.') + expect(message).to eq('Toggled :100: emoji reaction.') end end @@ -1911,8 +1911,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning let(:content) { "#{command} :100:" } let(:issuable) { commit } - # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/434446 - it_behaves_like 'failed command', "Could not apply award command." + it_behaves_like 'failed command', "Could not apply react command." end end end @@ -2877,7 +2876,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning it 'includes the emoji' do _, explanations = service.explain(content, issue) - expect(explanations).to eq(['Toggles :confetti_ball: emoji award.']) + expect(explanations).to eq(['Toggles :confetti_ball: emoji reaction.']) end end diff --git a/spec/support/formatters/json_formatter.rb b/spec/support/formatters/json_formatter.rb index a54004b3024..398ff0187a1 100644 --- a/spec/support/formatters/json_formatter.rb +++ b/spec/support/formatters/json_formatter.rb @@ -89,7 +89,13 @@ module Support [metadata[:file_path], metadata[:line_number]] else # If there are nested shared examples, the outermost location is last in the array - metadata[:shared_group_inclusion_backtrace].last.formatted_inclusion_location.split(':') + ( + metadata[:shared_group_inclusion_backtrace].last.formatted_inclusion_location || + # RSpec ignores some paths by default, e.g. bin/, which result in the above being nil. + # Source: https://github.com/rspec/rspec-core/blob/v3.12.2/lib/rspec/core/backtrace_formatter.rb#L11 + # In that case, we fallback to use the raw `#inclusion_location`. + metadata[:shared_group_inclusion_backtrace].last.inclusion_location + ).split(':') end end diff --git a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb index ae2855083f6..b56de050d1e 100644 --- a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb @@ -150,8 +150,15 @@ RSpec.shared_examples 'grants terraform module package file access' do |user_typ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous end - it_behaves_like 'returning response status', status it_behaves_like 'a package tracking event', described_class.name, 'pull_package' + + it 'returns a valid response' do + subject + + expect(response).to have_gitlab_http_status(status) + expect(response.media_type).to eq('application/octet-stream') + expect(response.body).to eq(package.package_files.last.file.read) + end end end @@ -273,3 +280,169 @@ RSpec.shared_examples 'process terraform module upload' do |user_type, status, a end end end + +RSpec.shared_examples 'handling project level terraform module download requests' do + using RSpec::Parameterized::TableSyntax + let(:project_id) { project.id } + let(:package_name) { package.name } + let(:url) { "/projects/#{project_id}/packages/terraform/modules/#{package_name}/#{module_version}?archive=tgz" } + + subject { get api(url), headers: headers } + + it { is_expected.to have_request_urgency(:low) } + + context 'with valid project' do + where(:visibility, :user_role, :member, :token_type, :shared_examples_name, :expected_status) do + :public | :anonymous | false | nil | 'grants terraform module package file access' | :success + :private | :anonymous | false | nil | 'rejects terraform module packages access' | :unauthorized + + :public | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized + :public | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized + :public | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized + :public | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized + :private | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized + :private | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized + :private | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized + :private | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized + :internal | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized + :internal | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized + :internal | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized + :internal | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized + + :public | :developer | true | :personal_access_token | 'grants terraform module package file access' | :success + :public | :guest | true | :personal_access_token | 'grants terraform module package file access' | :success + :public | :developer | false | :personal_access_token | 'grants terraform module package file access' | :success + :public | :guest | false | :personal_access_token | 'grants terraform module package file access' | :success + :private | :developer | true | :personal_access_token | 'grants terraform module package file access' | :success + :private | :guest | true | :personal_access_token | 'rejects terraform module packages access' | :forbidden + :private | :developer | false | :personal_access_token | 'rejects terraform module packages access' | :not_found + :private | :guest | false | :personal_access_token | 'rejects terraform module packages access' | :not_found + :internal | :developer | true | :personal_access_token | 'grants terraform module package file access' | :success + :internal | :guest | true | :personal_access_token | 'grants terraform module package file access' | :success + :internal | :developer | false | :personal_access_token | 'grants terraform module package file access' | :success + :internal | :guest | false | :personal_access_token | 'grants terraform module package file access' | :success + + :public | :developer | true | :job_token | 'grants terraform module package file access' | :success + :public | :guest | true | :job_token | 'grants terraform module package file access' | :success + :public | :developer | false | :job_token | 'grants terraform module package file access' | :success + :public | :guest | false | :job_token | 'grants terraform module package file access' | :success + :private | :developer | true | :job_token | 'grants terraform module package file access' | :success + :private | :guest | true | :job_token | 'rejects terraform module packages access' | :forbidden + :private | :developer | false | :job_token | 'rejects terraform module packages access' | :not_found + :private | :guest | false | :job_token | 'rejects terraform module packages access' | :not_found + :internal | :developer | true | :job_token | 'grants terraform module package file access' | :success + :internal | :guest | true | :job_token | 'grants terraform module package file access' | :success + :internal | :developer | false | :job_token | 'grants terraform module package file access' | :success + :internal | :guest | false | :job_token | 'grants terraform module package file access' | :success + + :public | :anonymous | false | :deploy_token | 'grants terraform module package file access' | :success + :private | :anonymous | false | :deploy_token | 'grants terraform module package file access' | :success + :internal | :anonymous | false | :deploy_token | 'grants terraform module package file access' | :success + end + + with_them do + let(:headers) do + case token_type + when :personal_access_token, :invalid + basic_auth_headers(user.username, token) + when :deploy_token + basic_auth_headers(deploy_token.username, token) + when :job_token + basic_auth_headers(::Gitlab::Auth::CI_JOB_USER, token) + else + {} + end + end + + let(:snowplow_gitlab_standard_context) do + { + project: project, + namespace: project.namespace, + property: 'i_package_terraform_module_user' + }.tap do |context| + context[:user] = user if token_type && token_type != :deploy_token + context[:user] = deploy_token if token_type == :deploy_token + end + end + + before do + project.update!(visibility: visibility.to_s) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + context 'with/without module version' do + let(:headers) { basic_auth_headers } + let(:finder_params) do + { package_name: package_name }.tap do |p| + p[:package_version] = module_version if module_version + end + end + + before do + project.add_developer(user) + end + + it 'calls the finder with the correct params' do + expect_next_instance_of(::Packages::TerraformModule::PackagesFinder, project, finder_params) do |finder| + expect(finder).to receive(:execute).and_call_original + end + + subject + end + end + + context 'with non-existent module version' do + let(:headers) { basic_auth_headers } + let(:module_version) { '1.99.322' } + + before do + project.add_developer(user) + end + + it_behaves_like 'returning response status', :not_found + end + + context 'with invalid project' do + let(:project_id) { '123456' } + + let(:headers) { basic_auth_headers } + + it_behaves_like 'rejects terraform module packages access', :anonymous, :not_found + end + + context 'with invalid package name' do + let(:headers) { basic_auth_headers } + + [nil, '', '%20', 'unknown', '..%2F..', '../..'].each do |pkg_name| + context "with package name #{pkg_name}" do + let(:package_name) { pkg_name } + + it_behaves_like 'rejects terraform module packages access', :anonymous, :not_found + end + end + end + + context 'when terraform-get param is received' do + let(:headers) { basic_auth_headers } + let(:url) { "#{super().split('?').first}?terraform-get=1" } + + before do + project.add_developer(user) + end + + it 'returns a valid response' do + subject + + expect(response.headers).to include 'X-Terraform-Get' + expect(response.headers['X-Terraform-Get']).to include '?archive=tgz' + expect(response.headers['X-Terraform-Get']).not_to include 'terraform-get=1' + end + end + + def basic_auth_headers(username = user.username, password = personal_access_token.token) + { Authorization: "Basic #{Base64.strict_encode64("#{username}:#{password}")}" } + end +end diff --git a/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb b/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb index 0545be7c741..4f6b27a99c6 100644 --- a/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb +++ b/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb @@ -7,12 +7,23 @@ RSpec.shared_examples 'work item hierarchy restrictions importer' do end end + shared_examples 'clears type reactive cache' do + specify do + expect_next_found_instances_of(WorkItems::Type, 7) do |instance| + expect(instance).to receive(:clear_reactive_cache!) + end + + subject + end + end + context 'when restrictions are missing' do before do WorkItems::HierarchyRestriction.delete_all end it_behaves_like 'adds restrictions' + it_behaves_like 'clears type reactive cache' end context 'when base types are missing' do @@ -41,6 +52,8 @@ RSpec.shared_examples 'work item hierarchy restrictions importer' do change { restriction.maximum_depth }.from(depth + 1).to(depth) ) end + + it_behaves_like 'clears type reactive cache' end context 'when some restrictions are missing' do @@ -55,6 +68,8 @@ RSpec.shared_examples 'work item hierarchy restrictions importer' do ) expect(WorkItems::HierarchyRestriction.count).to eq(7) end + + it_behaves_like 'clears type reactive cache' end context 'when restrictions contain attributes not present in the table' do @@ -70,5 +85,7 @@ RSpec.shared_examples 'work item hierarchy restrictions importer' do subject end + + it_behaves_like 'clears type reactive cache' end end |