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
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-19 12:10:52 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-19 12:10:52 +0300
commit17295c75a1a28df78f719e0098dd31fe45ce0446 (patch)
tree0544bd2f74e72e45b4a62ff68a4736c26a02a832 /spec
parent6c2b987064064500b42da924d86d43473bfd2b7f (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/bin/feature_flag_spec.rb273
-rw-r--r--spec/finders/packages/terraform_module/packages_finder_spec.rb65
-rw-r--r--spec/graphql/types/namespace_type_spec.rb2
-rw-r--r--spec/lib/feature/definition_spec.rb6
-rw-r--r--spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb8
-rw-r--r--spec/models/work_items/hierarchy_restriction_spec.rb20
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb21
-rw-r--r--spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace_query_spec.rb48
-rw-r--r--spec/requests/api/terraform/modules/v1/project_packages_spec.rb12
-rw-r--r--spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb24
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb7
-rw-r--r--spec/support/formatters/json_formatter.rb8
-rw-r--r--spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb175
-rw-r--r--spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb17
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