summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 12:26:25 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 12:26:25 +0000
commita09983ae35713f5a2bbb100981116d31ce99826e (patch)
tree2ee2af7bd104d57086db360a7e6d8c9d5d43667a /spec/support
parent18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff)
downloadgitlab-ce-a09983ae35713f5a2bbb100981116d31ce99826e.tar.gz
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/capybara.rb22
-rw-r--r--spec/support/controllers/project_import_rate_limiter_shared_examples.rb2
-rw-r--r--spec/support/helpers/expect_offense.rb22
-rw-r--r--spec/support/helpers/fast_rails_root.rb10
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb5
-rw-r--r--spec/support/helpers/jira_service_helper.rb3
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb14
-rw-r--r--spec/support/helpers/notification_helpers.rb12
-rw-r--r--spec/support/helpers/packages_manager_api_spec_helper.rb46
-rw-r--r--spec/support/helpers/partitioning_helpers.rb46
-rw-r--r--spec/support/helpers/rack_attack_spec_helpers.rb12
-rw-r--r--spec/support/helpers/reference_parser_helpers.rb4
-rw-r--r--spec/support/helpers/snippet_helpers.rb14
-rw-r--r--spec/support/helpers/stub_configuration.rb8
-rw-r--r--spec/support/helpers/stub_object_storage.rb26
-rw-r--r--spec/support/helpers/test_env.rb30
-rw-r--r--spec/support/helpers/trigger_helpers.rb5
-rw-r--r--spec/support/helpers/usage_data_helpers.rb12
-rw-r--r--spec/support/matchers/background_migrations_matchers.rb3
-rw-r--r--spec/support/matchers/jsonb_matchers.rb24
-rw-r--r--spec/support/rspec.rb6
-rw-r--r--spec/support/services/clusters/create_service_shared.rb9
-rw-r--r--spec/support/services/issuable_description_quick_actions_shared_examples.rb (renamed from spec/support/services/issuable_create_service_slash_commands_shared_examples.rb)37
-rw-r--r--spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/design_management_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/features/error_tracking_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb10
-rw-r--r--spec/support/shared_contexts/issuable/merge_request_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/issuable/project_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb1
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb3
-rw-r--r--spec/support/shared_contexts/presenters/nuget_shared_context.rb41
-rw-r--r--spec/support/shared_contexts/project_service_jira_context.rb15
-rw-r--r--spec/support/shared_contexts/project_service_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/prometheus/alert_shared_context.rb76
-rw-r--r--spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb70
-rw-r--r--spec/support/shared_contexts/sentry_error_tracking_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/spam_constants.rb2
-rw-r--r--spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb36
-rw-r--r--spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb33
-rw-r--r--spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb98
-rw-r--r--spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb147
-rw-r--r--spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb49
-rw-r--r--spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/controllers/unique_visits_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb111
-rw-r--r--spec/support/shared_examples/create_alert_issue_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb2
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/features/error_tracking_shared_example.rb6
-rw-r--r--spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb7
-rw-r--r--spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/graphql/mutation_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/helm_commands_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb66
-rw-r--r--spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/wikis_api_examples.rb174
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/models/cluster_application_version_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/models/jira_import_state_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/models/note_access_check_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/services_fields_shared_examples.rb31
-rw-r--r--spec/support/shared_examples/models/synthetic_note_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/namespaces/hierarchy_examples.rb21
-rw-r--r--spec/support/shared_examples/policies/namespace_policy_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/policies/project_policy_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb91
-rw-r--r--spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb138
-rw-r--r--spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb408
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb185
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb152
-rw-r--r--spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/requests/api/snippets_shared_examples.rb79
-rw-r--r--spec/support/shared_examples/requests/snippet_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/resource_events.rb26
-rw-r--r--spec/support/shared_examples/routing/resource_routing_shared_examples.rb73
-rw-r--r--spec/support/shared_examples/routing/wiki_routing_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/services/alert_management_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb89
-rw-r--r--spec/support/shared_examples/services/common_system_notes_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb80
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb193
-rw-r--r--spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/snippet_blob_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/uploaders/upload_type_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/views/pipeline_status_changes_email.rb2
-rw-r--r--spec/support/shared_examples/views/plain_text_email.rb9
-rw-r--r--spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb8
109 files changed, 3151 insertions, 466 deletions
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 38f9ccf23f5..66fce4fddf1 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -9,6 +9,9 @@ require 'selenium-webdriver'
# Give CI some extra time
timeout = ENV['CI'] || ENV['CI_SERVER'] ? 60 : 30
+# Support running Capybara on a specific port to allow saving commonly used pages
+Capybara.server_port = ENV['CAPYBARA_PORT'] if ENV['CAPYBARA_PORT']
+
# Define an error class for JS console messages
JSConsoleError = Class.new(StandardError)
@@ -153,11 +156,20 @@ RSpec.configure do |config|
# fixed. If we raised the `JSException` the fixed test would be marked as
# failed again.
if example.exception && !example.exception.is_a?(RSpec::Core::Pending::PendingExampleFixedError)
- console = page.driver.browser.manage.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER }
-
- if console.present?
- message = "Unexpected browser console output:\n" + console.map(&:message).join("\n")
- raise JSConsoleError, message
+ begin
+ console = page.driver.browser.manage.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER }
+
+ if console.present?
+ message = "Unexpected browser console output:\n" + console.map(&:message).join("\n")
+ raise JSConsoleError, message
+ end
+ rescue Selenium::WebDriver::Error::WebDriverError => error
+ if error.message =~ /unknown command: session\/[0-9a-zA-Z]+(?:\/se)?\/log/
+ message = "Unable to access Chrome javascript console logs. You may be using an outdated version of ChromeDriver."
+ raise JSConsoleError, message
+ else
+ raise error
+ end
end
end
diff --git a/spec/support/controllers/project_import_rate_limiter_shared_examples.rb b/spec/support/controllers/project_import_rate_limiter_shared_examples.rb
index 9a543dd00d4..66d753a4010 100644
--- a/spec/support/controllers/project_import_rate_limiter_shared_examples.rb
+++ b/spec/support/controllers/project_import_rate_limiter_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'project import rate limiter' do
+RSpec.shared_examples 'project import rate limiter' do
let(:user) { create(:user) }
before do
diff --git a/spec/support/helpers/expect_offense.rb b/spec/support/helpers/expect_offense.rb
deleted file mode 100644
index 76301fe19ff..00000000000
--- a/spec/support/helpers/expect_offense.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require 'rubocop/rspec/support'
-
-# https://github.com/backus/rubocop-rspec/blob/master/spec/support/expect_offense.rb
-# rubocop-rspec gem extension of RuboCop's ExpectOffense module.
-#
-# This mixin is the same as rubocop's ExpectOffense except the default
-# filename ends with `_spec.rb`
-module ExpectOffense
- include RuboCop::RSpec::ExpectOffense
-
- DEFAULT_FILENAME = 'example_spec.rb'.freeze
-
- def expect_offense(source, filename = DEFAULT_FILENAME)
- super
- end
-
- def expect_no_offenses(source, filename = DEFAULT_FILENAME)
- super
- end
-end
diff --git a/spec/support/helpers/fast_rails_root.rb b/spec/support/helpers/fast_rails_root.rb
new file mode 100644
index 00000000000..1510fe0825c
--- /dev/null
+++ b/spec/support/helpers/fast_rails_root.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# For specs which don't load Rails, provide a path to Rails root
+module FastRailsRoot
+ RAILS_ROOT = File.absolute_path("#{__dir__}/../../..")
+
+ def rails_root_join(*args)
+ File.join(RAILS_ROOT, *args)
+ end
+end
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index 99a5e043825..1847a8f8a06 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -45,9 +45,8 @@ module FilteredSearchHelpers
all_count = open_count + closed_count
expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: open_count)
- end
+
+ expect(page).to have_selector('.issue', count: open_count)
end
# Enables input to be added character by character
diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb
index 198bedfe3bc..9072c41fe66 100644
--- a/spec/support/helpers/jira_service_helper.rb
+++ b/spec/support/helpers/jira_service_helper.rb
@@ -5,14 +5,13 @@ module JiraServiceHelper
JIRA_API = JIRA_URL + "/rest/api/2"
def jira_service_settings
- title = "Jira tracker"
url = JIRA_URL
username = 'jira-user'
password = 'my-secret-password'
jira_issue_transition_id = '1'
jira_tracker.update(
- title: title, url: url, username: username, password: password,
+ url: url, username: username, password: password,
jira_issue_transition_id: jira_issue_transition_id, active: true
)
end
diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb
index b8a641d5911..b2dd8ead7dd 100644
--- a/spec/support/helpers/metrics_dashboard_helpers.rb
+++ b/spec/support/helpers/metrics_dashboard_helpers.rb
@@ -7,6 +7,12 @@ module MetricsDashboardHelpers
create(:project, :custom_repo, files: { dashboard_path => dashboard_yml })
end
+ def project_with_dashboard_namespace(dashboard_path, dashboard_yml = nil)
+ dashboard_yml ||= fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')
+
+ create(:project, :custom_repo, namespace: namespace, path: 'monitor-project', files: { dashboard_path => dashboard_yml })
+ end
+
def delete_project_dashboard(project, user, dashboard_path)
project.repository.delete_file(
user,
@@ -18,6 +24,14 @@ module MetricsDashboardHelpers
project.repository.refresh_method_caches([:metrics_dashboard])
end
+ def load_sample_dashboard
+ load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml'))
+ end
+
+ def load_dashboard_yaml(data)
+ ::Gitlab::Config::Loader::Yaml.new(data).load_raw!
+ end
+
def system_dashboard_path
Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH
end
diff --git a/spec/support/helpers/notification_helpers.rb b/spec/support/helpers/notification_helpers.rb
index b3e0e7d811b..887d68de4e1 100644
--- a/spec/support/helpers/notification_helpers.rb
+++ b/spec/support/helpers/notification_helpers.rb
@@ -38,26 +38,26 @@ module NotificationHelpers
end
def expect_delivery_jobs_count(count)
- expect(ActionMailer::DeliveryJob).to have_been_enqueued.exactly(count).times
+ expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.exactly(count).times
end
def expect_no_delivery_jobs
- expect(ActionMailer::DeliveryJob).not_to have_been_enqueued
+ expect(ActionMailer::MailDeliveryJob).not_to have_been_enqueued
end
def expect_any_delivery_jobs
- expect(ActionMailer::DeliveryJob).to have_been_enqueued.at_least(:once)
+ expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.at_least(:once)
end
def have_enqueued_email(*args, mailer: "Notify", mail: "", delivery: "deliver_now")
- have_enqueued_job(ActionMailer::DeliveryJob).with(mailer, mail, delivery, *args)
+ have_enqueued_job(ActionMailer::MailDeliveryJob).with(mailer, mail, delivery, args: args)
end
def expect_enqueud_email(*args, mailer: "Notify", mail: "", delivery: "deliver_now")
- expect(ActionMailer::DeliveryJob).to have_been_enqueued.with(mailer, mail, delivery, *args)
+ expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.with(mailer, mail, delivery, args: args)
end
def expect_not_enqueud_email(*args, mailer: "Notify", mail: "")
- expect(ActionMailer::DeliveryJob).not_to have_been_enqueued.with(mailer, mail, *args, any_args)
+ expect(ActionMailer::MailDeliveryJob).not_to have_been_enqueued.with(mailer, mail, args: any_args)
end
end
diff --git a/spec/support/helpers/packages_manager_api_spec_helper.rb b/spec/support/helpers/packages_manager_api_spec_helper.rb
new file mode 100644
index 00000000000..e5a690e1680
--- /dev/null
+++ b/spec/support/helpers/packages_manager_api_spec_helper.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module PackagesManagerApiSpecHelpers
+ def build_auth_headers(value)
+ { 'HTTP_AUTHORIZATION' => value }
+ end
+
+ def build_basic_auth_header(username, password)
+ build_auth_headers(ActionController::HttpAuthentication::Basic.encode_credentials(username, password))
+ end
+
+ def build_token_auth_header(token)
+ build_auth_headers("Bearer #{token}")
+ end
+
+ def build_jwt(personal_access_token, secret: jwt_secret, user_id: nil)
+ JSONWebToken::HMACToken.new(secret).tap do |jwt|
+ jwt['access_token'] = personal_access_token.id
+ jwt['user_id'] = user_id || personal_access_token.user_id
+ end
+ end
+
+ def build_jwt_from_job(job, secret: jwt_secret)
+ JSONWebToken::HMACToken.new(secret).tap do |jwt|
+ jwt['access_token'] = job.token
+ jwt['user_id'] = job.user.id
+ end
+ end
+
+ def build_jwt_from_deploy_token(deploy_token, secret: jwt_secret)
+ JSONWebToken::HMACToken.new(secret).tap do |jwt|
+ jwt['access_token'] = deploy_token.token
+ jwt['user_id'] = deploy_token.username
+ end
+ end
+
+ def temp_file(package_tmp)
+ upload_path = ::Packages::PackageFileUploader.workhorse_local_upload_path
+ file_path = "#{upload_path}/#{package_tmp}"
+
+ FileUtils.mkdir_p(upload_path)
+ File.write(file_path, 'test')
+
+ UploadedFile.new(file_path, filename: File.basename(file_path))
+ end
+end
diff --git a/spec/support/helpers/partitioning_helpers.rb b/spec/support/helpers/partitioning_helpers.rb
index 98a13915d76..8981fea04d5 100644
--- a/spec/support/helpers/partitioning_helpers.rb
+++ b/spec/support/helpers/partitioning_helpers.rb
@@ -9,13 +9,36 @@ module PartitioningHelpers
end
def expect_range_partition_of(partition_name, table_name, min_value, max_value)
- definition = find_partition_definition(partition_name)
+ definition = find_partition_definition(partition_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
expect(definition).not_to be_nil
expect(definition['base_table']).to eq(table_name.to_s)
expect(definition['condition']).to eq("FOR VALUES FROM (#{min_value}) TO (#{max_value})")
end
+ def expect_total_partitions(table_name, count, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
+ partitions = find_partitions(table_name, schema: schema)
+
+ expect(partitions.size).to eq(count)
+ end
+
+ def expect_range_partitions_for(table_name, partitions)
+ partitions.each do |suffix, (min_value, max_value)|
+ partition_name = "#{table_name}_#{suffix}"
+ expect_range_partition_of(partition_name, table_name, min_value, max_value)
+ end
+
+ expect_total_partitions(table_name, partitions.size, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
+ end
+
+ def expect_hash_partition_of(partition_name, table_name, modulus, remainder)
+ definition = find_partition_definition(partition_name, schema: Gitlab::Database::STATIC_PARTITIONS_SCHEMA)
+
+ expect(definition).not_to be_nil
+ expect(definition['base_table']).to eq(table_name.to_s)
+ expect(definition['condition']).to eq("FOR VALUES WITH (modulus #{modulus}, remainder #{remainder})")
+ end
+
private
def find_partitioned_columns(table)
@@ -40,7 +63,7 @@ module PartitioningHelpers
SQL
end
- def find_partition_definition(partition)
+ def find_partition_definition(partition, schema: )
connection.select_one(<<~SQL)
select
parent_class.relname as base_table,
@@ -48,7 +71,24 @@ module PartitioningHelpers
from pg_class
inner join pg_inherits i on pg_class.oid = inhrelid
inner join pg_class parent_class on parent_class.oid = inhparent
- where pg_class.relname = '#{partition}' and pg_class.relispartition;
+ inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace
+ where pg_namespace.nspname = '#{schema}'
+ and pg_class.relname = '#{partition}'
+ and pg_class.relispartition
+ SQL
+ end
+
+ def find_partitions(partition, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
+ connection.select_rows(<<~SQL)
+ select
+ pg_class.relname
+ from pg_class
+ inner join pg_inherits i on pg_class.oid = inhrelid
+ inner join pg_class parent_class on parent_class.oid = inhparent
+ inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace
+ where pg_namespace.nspname = '#{schema}'
+ and parent_class.relname = '#{partition}'
+ and pg_class.relispartition
SQL
end
end
diff --git a/spec/support/helpers/rack_attack_spec_helpers.rb b/spec/support/helpers/rack_attack_spec_helpers.rb
index e0cedb5a57b..65082ec690f 100644
--- a/spec/support/helpers/rack_attack_spec_helpers.rb
+++ b/spec/support/helpers/rack_attack_spec_helpers.rb
@@ -30,4 +30,16 @@ module RackAttackSpecHelpers
expect(response).to have_gitlab_http_status(:too_many_requests)
end
+
+ def expect_ok(&block)
+ yield
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ def random_next_ip
+ allow_next_instance_of(Rack::Attack::Request) do |instance|
+ allow(instance).to receive(:ip).and_return(FFaker::Internet.ip_v4_address)
+ end
+ end
end
diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb
index 9084265b587..e65cb8c96db 100644
--- a/spec/support/helpers/reference_parser_helpers.rb
+++ b/spec/support/helpers/reference_parser_helpers.rb
@@ -10,7 +10,7 @@ module ReferenceParserHelpers
expect(result[:not_visible].count).to eq(not_visible_count)
end
- shared_examples 'no project N+1 queries' do
+ RSpec.shared_examples 'no project N+1 queries' do
it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
context = Banzai::RenderContext.new(project, user)
@@ -28,7 +28,7 @@ module ReferenceParserHelpers
end
end
- shared_examples 'no N+1 queries' do
+ RSpec.shared_examples 'no N+1 queries' do
it_behaves_like 'no project N+1 queries'
it 'avoids N+1 queries in #records_for_nodes', :request_store do
diff --git a/spec/support/helpers/snippet_helpers.rb b/spec/support/helpers/snippet_helpers.rb
new file mode 100644
index 00000000000..de64ad7d3e2
--- /dev/null
+++ b/spec/support/helpers/snippet_helpers.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module SnippetHelpers
+ def sign_in_as(user)
+ sign_in(public_send(user)) if user
+ end
+
+ def snippet_blob_file(blob)
+ {
+ "path" => blob.path,
+ "raw_url" => gitlab_raw_snippet_blob_url(blob.container, blob.path)
+ }
+ end
+end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 6a832ca97d1..e19f230d8df 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -113,6 +113,14 @@ module StubConfiguration
allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages))
end
+ def stub_service_desk_email_setting(messages)
+ allow(::Gitlab.config.service_desk_email).to receive_messages(to_settings(messages))
+ end
+
+ def stub_packages_setting(messages)
+ allow(::Gitlab.config.packages).to receive_messages(to_settings(messages))
+ end
+
private
# Modifies stubbed messages to also stub possible predicate versions
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index b473cdaefc1..6056359d026 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -1,6 +1,25 @@
# frozen_string_literal: true
module StubObjectStorage
+ def stub_packages_object_storage(**params)
+ stub_object_storage_uploader(config: ::Gitlab.config.packages.object_store,
+ uploader: ::Packages::PackageFileUploader,
+ remote_directory: 'packages',
+ **params)
+ end
+
+ def stub_dependency_proxy_object_storage(**params)
+ stub_object_storage_uploader(config: ::Gitlab.config.dependency_proxy.object_store,
+ uploader: ::DependencyProxy::FileUploader,
+ remote_directory: 'dependency_proxy',
+ **params)
+ end
+
+ def stub_object_storage_pseudonymizer
+ stub_object_storage(connection_params: Pseudonymizer::Uploader.object_store_credentials,
+ remote_directory: Pseudonymizer::Uploader.remote_directory)
+ end
+
def stub_object_storage_uploader(
config:,
uploader:,
@@ -73,7 +92,7 @@ module StubObjectStorage
def stub_terraform_state_object_storage(uploader = described_class, **params)
stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store,
uploader: uploader,
- remote_directory: 'terraform_state',
+ remote_directory: 'terraform',
**params)
end
@@ -89,8 +108,3 @@ module StubObjectStorage
EOS
end
end
-
-require_relative '../../../ee/spec/support/helpers/ee/stub_object_storage' if
- Dir.exist?("#{__dir__}/../../../ee")
-
-StubObjectStorage.prepend_if_ee('EE::StubObjectStorage')
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 130650b7e2e..f787aedf7aa 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -6,6 +6,8 @@ module TestEnv
ComponentFailedToInstallError = Class.new(StandardError)
+ SHA_REGEX = /\A[0-9a-f]{5,40}\z/i.freeze
+
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'signed-commits' => '6101e87',
@@ -29,6 +31,10 @@ module TestEnv
'gitattributes' => '5a62481',
'expand-collapse-diffs' => '4842455',
'symlink-expand-diff' => '81e6355',
+ 'diff-files-symlink-to-image' => '8cfca84',
+ 'diff-files-image-to-symlink' => '3e94fda',
+ 'diff-files-symlink-to-text' => '689815e',
+ 'diff-files-text-to-symlink' => '5e2c270',
'expand-collapse-files' => '025db92',
'expand-collapse-lines' => '238e82d',
'pages-deploy' => '7897d5b',
@@ -165,8 +171,9 @@ module TestEnv
task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
Gitlab::SetupHelper::Gitaly.create_configuration(gitaly_dir, { 'default' => repos_path }, force: true)
Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true)
- start_gitaly(gitaly_dir)
end
+
+ start_gitaly(gitaly_dir)
end
def gitaly_socket_path
@@ -459,7 +466,6 @@ module TestEnv
end
def component_timed_setup(component, install_dir:, version:, task:)
- puts "\n==> Setting up #{component}..."
start = Time.now
ensure_component_dir_name_is_correct!(component, install_dir)
@@ -468,22 +474,22 @@ module TestEnv
return if File.exist?(install_dir) && ci?
if component_needs_update?(install_dir, version)
+ puts "\n==> Setting up #{component}..."
# Cleanup the component entirely to ensure we start fresh
FileUtils.rm_rf(install_dir)
unless system('rake', task)
raise ComponentFailedToInstallError
end
- end
- yield if block_given?
+ yield if block_given?
+ puts " #{component} set up in #{Time.now - start} seconds...\n"
+ end
rescue ComponentFailedToInstallError
puts "\n#{component} failed to install, cleaning up #{install_dir}!\n"
FileUtils.rm_rf(install_dir)
exit 1
- ensure
- puts " #{component} set up in #{Time.now - start} seconds...\n"
end
def ci?
@@ -504,6 +510,8 @@ module TestEnv
# Allow local overrides of the component for tests during development
return false if Rails.env.test? && File.symlink?(component_folder)
+ return false if component_matches_git_sha?(component_folder, expected_version)
+
version = File.read(File.join(component_folder, 'VERSION')).strip
# Notice that this will always yield true when using branch versions
@@ -513,6 +521,16 @@ module TestEnv
rescue Errno::ENOENT
true
end
+
+ def component_matches_git_sha?(component_folder, expected_version)
+ # Not a git SHA, so return early
+ return false unless expected_version =~ SHA_REGEX
+
+ sha, exit_status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} rev-parse HEAD), component_folder)
+ return false if exit_status != 0
+
+ expected_version == sha.chomp
+ end
end
require_relative('../../../ee/spec/support/helpers/ee/test_env') if Gitlab.ee?
diff --git a/spec/support/helpers/trigger_helpers.rb b/spec/support/helpers/trigger_helpers.rb
index fa4f499b900..67c62cf4869 100644
--- a/spec/support/helpers/trigger_helpers.rb
+++ b/spec/support/helpers/trigger_helpers.rb
@@ -27,7 +27,10 @@ module TriggerHelpers
expected_timing, expected_events = fires_on.first
expect(timing).to eq(expected_timing.to_s)
expect(events).to match_array(Array.wrap(expected_events))
- expect(definition).to eq("execute procedure #{fn_name}()")
+
+ # TODO: Update CREATE TRIGGER syntax to use EXECUTE FUNCTION
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/227089
+ expect(definition).to match(%r{execute (?:procedure|function) #{fn_name}()})
end
private
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index f6c415a75bc..a4f40a4af0a 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -78,7 +78,6 @@ module UsageDataHelpers
labels
lfs_objects
merge_requests
- merge_requests_users
milestone_lists
milestones
notes
@@ -89,8 +88,6 @@ module UsageDataHelpers
projects_jira_active
projects_jira_server_active
projects_jira_cloud_active
- projects_slack_notifications_active
- projects_slack_slash_active
projects_slack_active
projects_slack_slash_commands_active
projects_custom_issue_tracker_active
@@ -231,6 +228,15 @@ module UsageDataHelpers
def allow_prometheus_queries
allow_next_instance_of(Gitlab::PrometheusClient) do |client|
allow(client).to receive(:aggregate).and_return({})
+ allow(client).to receive(:query).and_return({})
+ end
+ end
+
+ def for_defined_days_back(days: [29, 2])
+ days.each do |n|
+ Timecop.travel(n.days.ago) do
+ yield
+ end
end
end
end
diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb
index 8735dac8b2a..0144a044f6c 100644
--- a/spec/support/matchers/background_migrations_matchers.rb
+++ b/spec/support/matchers/background_migrations_matchers.rb
@@ -10,7 +10,8 @@ RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected|
failure_message do |migration|
"Migration `#{migration}` with args `#{expected.inspect}` " \
- 'not scheduled in expected time!'
+ "not scheduled in expected time! Expected any of `#{BackgroundMigrationWorker.jobs.map { |j| j['args'] }}` to be `#{[migration, expected]}` " \
+ "and any of `#{BackgroundMigrationWorker.jobs.map { |j| j['at'].to_i }}` to be `#{delay.to_i + Time.now.to_i}` (`#{delay.to_i}` + `#{Time.now.to_i}`)."
end
end
diff --git a/spec/support/matchers/jsonb_matchers.rb b/spec/support/matchers/jsonb_matchers.rb
new file mode 100644
index 00000000000..823888708f3
--- /dev/null
+++ b/spec/support/matchers/jsonb_matchers.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :validate_jsonb_schema do |jsonb_columns|
+ match do |actual|
+ next true if jsonb_columns.blank?
+
+ expect(actual.validators).to include(a_kind_of(JsonSchemaValidator))
+ end
+
+ failure_message do
+ <<~FAILURE_MESSAGE
+ Expected #{actual.name} to validate the schema of #{jsonb_columns.join(', ')}.
+
+ Use JsonSchemaValidator in your model when using a jsonb column.
+ See doc/development/migration_style_guide.html#storing-json-in-database for more information.
+
+ To fix this, please add `validates :#{jsonb_columns.first}, json_schema: { filename: "filename" }` in your model file, for example:
+
+ class #{actual.name}
+ validates :#{jsonb_columns.first}, json_schema: { filename: "filename" }
+ end
+ FAILURE_MESSAGE
+ end
+end
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index 7d011c5eb95..861b57c9efa 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -4,7 +4,8 @@ require_relative "helpers/stub_configuration"
require_relative "helpers/stub_metrics"
require_relative "helpers/stub_object_storage"
require_relative "helpers/stub_env"
-require_relative "helpers/expect_offense"
+require_relative "helpers/fast_rails_root"
+require 'rubocop/rspec/support'
RSpec.configure do |config|
config.mock_with :rspec
@@ -14,6 +15,7 @@ RSpec.configure do |config|
config.include StubMetrics
config.include StubObjectStorage
config.include StubENV
+ config.include FastRailsRoot
- config.include ExpectOffense, type: :rubocop
+ config.include RuboCop::RSpec::ExpectOffense, type: :rubocop
end
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
index 31aee08baec..f8a58a828ce 100644
--- a/spec/support/services/clusters/create_service_shared.rb
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
RSpec.shared_context 'valid cluster create params' do
+ let(:clusterable) { Clusters::Instance.new }
let(:params) do
{
name: 'test-cluster',
@@ -11,12 +12,14 @@ RSpec.shared_context 'valid cluster create params' do
num_nodes: 1,
machine_type: 'machine_type-a',
legacy_abac: 'true'
- }
+ },
+ clusterable: clusterable
}
end
end
RSpec.shared_context 'invalid cluster create params' do
+ let(:clusterable) { Clusters::Instance.new }
let(:params) do
{
name: 'test-cluster',
@@ -26,7 +29,9 @@ RSpec.shared_context 'invalid cluster create params' do
zone: 'us-central1-a',
num_nodes: 1,
machine_type: 'machine_type-a'
- }
+ },
+ clusterable: clusterable
+
}
end
end
diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_description_quick_actions_shared_examples.rb
index 3d45fe06134..1970301e4c9 100644
--- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
+++ b/spec/support/services/issuable_description_quick_actions_shared_examples.rb
@@ -3,20 +3,25 @@
# Specifications for behavior common to all objects with executable attributes.
# It can take a `default_params`.
-RSpec.shared_examples 'new issuable record that supports quick actions' do
- let!(:project) { create(:project, :repository) }
- let(:user) { create(:user).tap { |u| project.add_maintainer(u) } }
- let(:assignee) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
- let!(:labels) { create_list(:label, 3, project: project) }
+RSpec.shared_examples 'issuable record that supports quick actions' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:assignee) { create(:user) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
+ let_it_be(:labels) { create_list(:label, 3, project: project) }
+
let(:base_params) { { title: 'My issuable title' } }
let(:params) { base_params.merge(defined?(default_params) ? default_params : {}).merge(example_params) }
- let(:issuable) { described_class.new(project, user, params).execute }
- before do
+ before_all do
+ project.add_maintainer(user)
project.add_maintainer(assignee)
end
+ before do
+ issuable.reload
+ end
+
context 'with labels in command only' do
let(:example_params) do
{
@@ -25,7 +30,6 @@ RSpec.shared_examples 'new issuable record that supports quick actions' do
end
it 'attaches labels to issuable' do
- expect(issuable).to be_persisted
expect(issuable.label_ids).to match_array([labels.first.id, labels.second.id])
end
end
@@ -39,7 +43,6 @@ RSpec.shared_examples 'new issuable record that supports quick actions' do
end
it 'attaches all labels to issuable' do
- expect(issuable).to be_persisted
expect(issuable.label_ids).to match_array([labels.first.id, labels.second.id])
end
end
@@ -52,22 +55,8 @@ RSpec.shared_examples 'new issuable record that supports quick actions' do
end
it 'assigns and sets milestone to issuable' do
- expect(issuable).to be_persisted
expect(issuable.assignees).to eq([assignee])
expect(issuable.milestone).to eq(milestone)
end
end
-
- describe '/close' do
- let(:example_params) do
- {
- description: '/close'
- }
- end
-
- it 'returns an open issue' do
- expect(issuable).to be_persisted
- expect(issuable).to be_open
- end
- end
end
diff --git a/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb b/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
index 04f49e94647..e3c1d0afa53 100644
--- a/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
+++ b/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'allowed user IDs are cached' do
+RSpec.shared_examples 'allowed user IDs are cached' do
it 'caches the allowed user IDs in cache', :use_clean_rails_memory_store_caching do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
diff --git a/spec/support/shared_contexts/design_management_shared_contexts.rb b/spec/support/shared_contexts/design_management_shared_contexts.rb
index 2866effb3a8..3ff6a521338 100644
--- a/spec/support/shared_contexts/design_management_shared_contexts.rb
+++ b/spec/support/shared_contexts/design_management_shared_contexts.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'four designs in three versions' do
+RSpec.shared_context 'four designs in three versions' do
include DesignManagementTestHelpers
let_it_be(:issue) { create(:issue) }
diff --git a/spec/support/shared_contexts/features/error_tracking_shared_context.rb b/spec/support/shared_contexts/features/error_tracking_shared_context.rb
index 102cf7c9b11..1f4eb3a6df9 100644
--- a/spec/support/shared_contexts/features/error_tracking_shared_context.rb
+++ b/spec/support/shared_contexts/features/error_tracking_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'sentry error tracking context feature' do
+RSpec.shared_context 'sentry error tracking context feature' do
include ReactiveCachingHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
index 2b8daa80ab4..07b6b98222f 100644
--- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -23,12 +23,12 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
# We cannot use `let_it_be` here otherwise we get:
# Failure/Error: allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
# The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported.
- let(:project2) do
+ let!(:project2) do
allow_gitaly_n_plus_1 do
fork_project(project1, user)
end
end
- let(:project3) do
+ let!(:project3) do
allow_gitaly_n_plus_1 do
fork_project(project1, user).tap do |project|
project.update!(archived: true)
@@ -45,6 +45,9 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
end
+ let!(:label) { create(:label, project: project1) }
+ let!(:label2) { create(:label, project: project1) }
+
let!(:merge_request1) do
create(:merge_request, assignees: [user], author: user,
source_project: project2, target_project: project1,
@@ -72,6 +75,9 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
title: '[WIP]')
end
+ let!(:label_link) { create(:label_link, label: label, target: merge_request2) }
+ let!(:label_link2) { create(:label_link, label: label2, target: merge_request3) }
+
before do
project1.add_maintainer(user)
project2.add_developer(user)
diff --git a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
index 05ffb934c34..79fc42e73c7 100644
--- a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
+++ b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'merge request show action' do
+RSpec.shared_context 'merge request show action' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
diff --git a/spec/support/shared_contexts/issuable/project_shared_context.rb b/spec/support/shared_contexts/issuable/project_shared_context.rb
index 6dbf3154977..5e5f6f2b7a6 100644
--- a/spec/support/shared_contexts/issuable/project_shared_context.rb
+++ b/spec/support/shared_contexts/issuable/project_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'project show action' do
+RSpec.shared_context 'project show action' do
let(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project, author: user) }
let(:user) { create(:user) }
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 79b5ff44d4f..d9a72f2b54a 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -42,6 +42,7 @@ RSpec.shared_context 'project navbar structure' do
_('List'),
_('Boards'),
_('Labels'),
+ _('Service Desk'),
_('Milestones')
]
},
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index a0d54666dff..4b0c7afab6d 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -18,7 +18,7 @@ RSpec.shared_context 'GroupPolicy context' do
]
end
let(:read_group_permissions) { %i[read_label read_list read_milestone read_board] }
- let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation] }
+ let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation read_prometheus] }
let(:developer_permissions) { %i[admin_milestone create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation] }
let(:maintainer_permissions) do
%i[
@@ -38,6 +38,7 @@ RSpec.shared_context 'GroupPolicy context' do
:update_default_branch_protection
].compact
end
+ let(:admin_permissions) { %i[read_confidential_issues] }
before_all do
group.add_guest(guest)
diff --git a/spec/support/shared_contexts/presenters/nuget_shared_context.rb b/spec/support/shared_contexts/presenters/nuget_shared_context.rb
new file mode 100644
index 00000000000..dd381db5a8b
--- /dev/null
+++ b/spec/support/shared_contexts/presenters/nuget_shared_context.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'with expected presenters dependency groups' do
+ def expected_dependency_groups(project_id, package_name, package_version)
+ [
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/.netstandard2.0",
+ target_framework: '.NETStandard2.0',
+ type: 'PackageDependencyGroup',
+ dependencies: [
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/.netstandard2.0/newtonsoft.json",
+ range: '12.0.3',
+ name: 'Newtonsoft.Json',
+ type: 'PackageDependency'
+ }
+ ]
+ },
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup",
+ type: 'PackageDependencyGroup',
+ dependencies: [
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/castle.core",
+ range: '4.4.1',
+ name: 'Castle.Core',
+ type: 'PackageDependency'
+ }
+ ]
+ }
+ ]
+ end
+
+ def create_dependencies_for(package)
+ dependency1 = Packages::Dependency.find_by(name: 'Newtonsoft.Json', version_pattern: '12.0.3') || create(:packages_dependency, name: 'Newtonsoft.Json', version_pattern: '12.0.3')
+ dependency2 = Packages::Dependency.find_by(name: 'Castle.Core', version_pattern: '4.4.1') || create(:packages_dependency, name: 'Castle.Core', version_pattern: '4.4.1')
+
+ create(:packages_dependency_link, :with_nuget_metadatum, package: package, dependency: dependency1)
+ create(:packages_dependency_link, package: package, dependency: dependency2)
+ end
+end
diff --git a/spec/support/shared_contexts/project_service_jira_context.rb b/spec/support/shared_contexts/project_service_jira_context.rb
new file mode 100644
index 00000000000..4ca5c99323a
--- /dev/null
+++ b/spec/support/shared_contexts/project_service_jira_context.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'project service Jira context' do
+ let(:url) { 'http://jira.example.com' }
+ let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' }
+
+ def fill_form(disable: false)
+ click_active_toggle if disable
+
+ fill_in 'service_url', with: url
+ fill_in 'service_username', with: 'username'
+ fill_in 'service_password', with: 'password'
+ fill_in 'service_jira_issue_transition_id', with: '25'
+ end
+end
diff --git a/spec/support/shared_contexts/project_service_shared_context.rb b/spec/support/shared_contexts/project_service_shared_context.rb
index 5b0dd26bd7b..a72d4901b72 100644
--- a/spec/support/shared_contexts/project_service_shared_context.rb
+++ b/spec/support/shared_contexts/project_service_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'project service activation' do
+RSpec.shared_context 'project service activation' do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/support/shared_contexts/prometheus/alert_shared_context.rb b/spec/support/shared_contexts/prometheus/alert_shared_context.rb
new file mode 100644
index 00000000000..330d2c4515f
--- /dev/null
+++ b/spec/support/shared_contexts/prometheus/alert_shared_context.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+# These contexts expect a `project` to be defined.
+# It is expected that these contexts are used to create an
+# alert.
+RSpec.shared_context 'self-managed prometheus alert attributes' do
+ let_it_be(:environment) { create(:environment, project: project, name: 'production') }
+
+ let(:starts_at) { '2018-03-12T09:06:00Z' }
+ let(:title) { 'title' }
+ let(:y_label) { 'y_label' }
+ let(:query) { 'avg(metric) > 1.0' }
+
+ let(:embed_content) do
+ {
+ panel_groups: [{
+ panels: [{
+ type: 'line-graph',
+ title: title,
+ y_label: y_label,
+ metrics: [{ query_range: query }]
+ }]
+ }]
+ }.to_json
+ end
+
+ let(:payload) do
+ {
+ 'startsAt' => starts_at,
+ 'generatorURL' => "http://host?g0.expr=#{CGI.escape(query)}",
+ 'labels' => {
+ 'gitlab_environment_name' => 'production'
+ },
+ 'annotations' => {
+ 'title' => title,
+ 'gitlab_y_label' => y_label
+ }
+ }
+ end
+
+ let(:dashboard_url_for_alert) do
+ Gitlab::Routing.url_helpers.metrics_dashboard_project_environment_url(
+ project,
+ environment,
+ embed_json: embed_content,
+ embedded: true,
+ end: '2018-03-12T09:36:00Z',
+ start: '2018-03-12T08:36:00Z'
+ )
+ end
+end
+
+RSpec.shared_context 'gitlab-managed prometheus alert attributes' do
+ let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) }
+ let(:prometheus_metric_id) { prometheus_alert.prometheus_metric_id }
+
+ let(:payload) do
+ {
+ 'startsAt' => '2018-03-12T09:06:00Z',
+ 'labels' => {
+ 'gitlab_alert_id' => prometheus_metric_id
+ }
+ }
+ end
+
+ let(:dashboard_url_for_alert) do
+ Gitlab::Routing.url_helpers.metrics_dashboard_project_prometheus_alert_url(
+ project,
+ prometheus_metric_id,
+ environment_id: prometheus_alert.environment_id,
+ embedded: true,
+ end: '2018-03-12T09:36:00Z',
+ start: '2018-03-12T08:36:00Z'
+ )
+ end
+end
diff --git a/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb b/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
index f0722beb3ed..7f150bed43d 100644
--- a/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
+++ b/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'jira projects request context' do
+RSpec.shared_context 'jira projects request context' do
let(:url) { 'https://jira.example.com' }
let(:username) { 'jira-username' }
let(:password) { 'jira-password' }
@@ -74,6 +74,48 @@ shared_context 'jira projects request context' do
}'
end
+ let_it_be(:all_jira_projects_json) do
+ '[{
+ "expand": "description,lead,issueTypes,url,projectKeys,permissions,insight",
+ "self": "https://gitlab-jira.atlassian.net/rest/api/2/project/10000",
+ "id": "10000",
+ "key": "EX",
+ "name": "Example",
+ "avatarUrls": {
+ "48x48": "https://gitlab-jira.atlassian.net/secure/projectavatar?pid=10000&avatarId=10425",
+ "24x24": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=small&s=small&pid=10000&avatarId=10425",
+ "16x16": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10000&avatarId=10425",
+ "32x32": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10000&avatarId=10425"
+ },
+ "projectTypeKey": "software",
+ "simplified": false,
+ "style": "classic",
+ "isPrivate": false,
+ "properties": {
+ }
+ },
+ {
+ "expand": "description,lead,issueTypes,url,projectKeys,permissions,insight",
+ "self": "https://gitlab-jira.atlassian.net/rest/api/2/project/10001",
+ "id": "10001",
+ "key": "ABC",
+ "name": "Alphabetical",
+ "avatarUrls": {
+ "48x48": "https://gitlab-jira.atlassian.net/secure/projectavatar?pid=10001&avatarId=10405",
+ "24x24": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=small&s=small&pid=10001&avatarId=10405",
+ "16x16": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10001&avatarId=10405",
+ "32x32": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10001&avatarId=10405"
+ },
+ "projectTypeKey": "software",
+ "simplified": true,
+ "style": "next-gen",
+ "isPrivate": false,
+ "properties": {
+ },
+ "entityId": "14935009-f8aa-481e-94bc-f7251f320b0e",
+ "uuid": "14935009-f8aa-481e-94bc-f7251f320b0e"
+ }]'
+ end
let_it_be(:empty_jira_projects_json) do
'{
"self": "https://your-domain.atlassian.net/rest/api/2/project/search?startAt=0&maxResults=2",
@@ -86,10 +128,32 @@ shared_context 'jira projects request context' do
}'
end
+ let(:server_info_json) do
+ '{
+ "baseUrl": "https://gitlab-jira.atlassian.net",
+ "version": "1001.0.0-SNAPSHOT",
+ "versionNumbers": [
+ 1001,
+ 0,
+ 0
+ ],
+ "deploymentType": "Cloud",
+ "buildNumber": 100128,
+ "buildDate": "2020-06-03T01:58:44.000-0700",
+ "serverTime": "2020-06-04T06:15:13.686-0700",
+ "scmInfo": "e736ab140ddb281c7cf5dcf9062c9ce2c08b3c1c",
+ "serverTitle": "Jira",
+ "defaultLocale": {
+ "locale": "en_US"
+ }
+ }'
+ end
+
let(:test_url) { "#{url}/rest/api/2/project/search?maxResults=50&query=&startAt=0" }
let(:start_at_20_url) { "#{url}/rest/api/2/project/search?maxResults=50&query=&startAt=20" }
let(:start_at_1_url) { "#{url}/rest/api/2/project/search?maxResults=50&query=&startAt=1" }
let(:max_results_1_url) { "#{url}/rest/api/2/project/search?maxResults=1&query=&startAt=0" }
+ let(:all_projects_url) { "#{url}/rest/api/2/project" }
before do
WebMock.stub_request(:get, test_url).with(basic_auth: [username, password])
@@ -100,5 +164,9 @@ shared_context 'jira projects request context' do
.to_return(body: jira_projects_json, headers: { "Content-Type": "application/json" })
WebMock.stub_request(:get, max_results_1_url).with(basic_auth: [username, password])
.to_return(body: jira_projects_json, headers: { "Content-Type": "application/json" })
+ WebMock.stub_request(:get, all_projects_url).with(basic_auth: [username, password])
+ .to_return(body: all_jira_projects_json, headers: { "Content-Type": "application/json" })
+ WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/serverInfo')
+ .to_return(status: 200, body: server_info_json, headers: {})
end
end
diff --git a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
index f06de53f0c1..3453f954c9d 100644
--- a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
+++ b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'sentry error tracking context' do
+RSpec.shared_context 'sentry error tracking context' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index bf4eac8f324..899b43ade01 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -12,6 +12,8 @@ Service.available_services_names.each do |service|
service_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/
hash.merge!(k => 'secrettoken')
+ elsif service == 'confluence' && k == :confluence_url
+ hash.merge!(k => 'https://example.atlassian.net/wiki')
elsif k =~ /^(.*_url|url|webhook)/
hash.merge!(k => "http://example.com")
elsif service_klass.method_defined?("#{k}?")
diff --git a/spec/support/shared_contexts/spam_constants.rb b/spec/support/shared_contexts/spam_constants.rb
index 32371f4b92f..813f9d00123 100644
--- a/spec/support/shared_contexts/spam_constants.rb
+++ b/spec/support/shared_contexts/spam_constants.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'includes Spam constants' do
+RSpec.shared_context 'includes Spam constants' do
before do
stub_const('CONDITIONAL_ALLOW', Spam::SpamConstants::CONDITIONAL_ALLOW)
stub_const('DISALLOW', Spam::SpamConstants::DISALLOW)
diff --git a/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb b/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb
deleted file mode 100644
index 88ad1f6cde2..00000000000
--- a/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'import controller with new_import_ui feature flag' do
- include ImportSpecHelper
-
- context 'with new_import_ui feature flag enabled' do
- let(:group) { create(:group) }
-
- before do
- stub_feature_flags(new_import_ui: true)
- group.add_owner(user)
- end
-
- it "returns variables for json request" do
- project = create(:project, import_type: provider_name, creator_id: user.id)
- stub_client(client_repos_field => [repo])
-
- get :status, format: :json
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
- expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id)
- expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
- end
-
- it "does not show already added project" do
- project = create(:project, import_type: provider_name, namespace: user.namespace, import_status: :finished, import_source: import_source)
- stub_client(client_repos_field => [repo])
-
- get :status, format: :json
-
- expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
- expect(json_response.dig("provider_repos")).to eq([])
- end
- end
-end
diff --git a/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
new file mode 100644
index 00000000000..ecb9abc5c46
--- /dev/null
+++ b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'import controller status' do
+ include ImportSpecHelper
+
+ let(:group) { create(:group) }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it "returns variables for json request" do
+ project = create(:project, import_type: provider_name, creator_id: user.id)
+ stub_client(client_repos_field => [repo])
+
+ get :status, format: :json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
+ expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id)
+ expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
+ end
+
+ it "does not show already added project" do
+ project = create(:project, import_type: provider_name, namespace: user.namespace, import_status: :finished, import_source: import_source)
+ stub_client(client_repos_field => [repo])
+
+ get :status, format: :json
+
+ expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
+ expect(json_response.dig("provider_repos")).to eq([])
+ end
+end
diff --git a/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb b/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb
index 60abb76acec..7f26155f9d6 100644
--- a/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb
@@ -9,13 +9,38 @@ RSpec.shared_examples 'known sign in' do
user.update!(current_sign_in_ip: ip)
end
- context 'with a valid post' do
- context 'when remote IP does not match user last sign in IP' do
- before do
- stub_user_ip('127.0.0.1')
- stub_remote_ip('169.0.0.1')
- end
+ def stub_cookie(value = user.id)
+ cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE] = {
+ value: value, expires: KnownSignIn::KNOWN_SIGN_IN_COOKIE_EXPIRY
+ }
+ end
+
+ context 'when the remote IP and the last sign in IP match' do
+ before do
+ stub_user_ip('169.0.0.1')
+ stub_remote_ip('169.0.0.1')
+ end
+
+ it 'does not notify the user' do
+ expect(NotificationService).not_to receive(:new)
+ post_action
+ end
+
+ it 'sets/updates the encrypted cookie' do
+ post_action
+
+ expect(cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE]).to eq(user.id)
+ end
+ end
+
+ context 'when the remote IP and the last sign in IP do not match' do
+ before do
+ stub_user_ip('127.0.0.1')
+ stub_remote_ip('169.0.0.1')
+ end
+
+ context 'when the cookie is not previously set' do
it 'notifies the user' do
expect_next_instance_of(NotificationService) do |instance|
expect(instance).to receive(:unknown_sign_in)
@@ -23,37 +48,68 @@ RSpec.shared_examples 'known sign in' do
post_action
end
- end
-
- context 'when remote IP matches an active session' do
- before do
- existing_sessions = ActiveSession.session_ids_for_user(user.id)
- existing_sessions.each { |sessions| ActiveSession.destroy(user, sessions) }
- stub_user_ip('169.0.0.1')
- stub_remote_ip('127.0.0.1')
+ it 'sets the encrypted cookie' do
+ post_action
- ActiveSession.set(user, request)
+ expect(cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE]).to eq(user.id)
end
+ end
- it 'does not notify the user' do
- expect_any_instance_of(NotificationService).not_to receive(:unknown_sign_in)
+ it 'notifies the user when the cookie is expired' do
+ stub_cookie
+
+ Timecop.freeze((KnownSignIn::KNOWN_SIGN_IN_COOKIE_EXPIRY + 1.day).from_now) do
+ expect_next_instance_of(NotificationService) do |instance|
+ expect(instance).to receive(:unknown_sign_in)
+ end
post_action
end
end
- context 'when remote IP address matches last sign in IP' do
+ context 'when notify_on_unknown_sign_in global setting is false' do
before do
- stub_user_ip('127.0.0.1')
- stub_remote_ip('127.0.0.1')
+ stub_application_setting(notify_on_unknown_sign_in: false)
end
it 'does not notify the user' do
- expect_any_instance_of(NotificationService).not_to receive(:unknown_sign_in)
+ expect(NotificationService).not_to receive(:new)
+
+ post_action
+ end
+ it 'does not set a cookie' do
post_action
+
+ expect(cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE]).to be_nil
+ end
+ end
+
+ it 'notifies the user when the cookie is for another user' do
+ stub_cookie(create(:user).id)
+
+ expect_next_instance_of(NotificationService) do |instance|
+ expect(instance).to receive(:unknown_sign_in)
end
+
+ post_action
+ end
+
+ it 'does not notify the user when remote IP matches an active session' do
+ ActiveSession.set(user, request)
+
+ expect(NotificationService).not_to receive(:new)
+
+ post_action
+ end
+
+ it 'does not notify the user when the cookie is present and not expired' do
+ stub_cookie
+
+ expect(NotificationService).not_to receive(:new)
+
+ post_action
end
end
end
diff --git a/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb b/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb
new file mode 100644
index 00000000000..94cd6971f7c
--- /dev/null
+++ b/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'metrics dashboard prometheus api proxy' do
+ let(:service_params) { [proxyable, 'GET', 'query', expected_params] }
+ let(:service_result) { { status: :success, body: prometheus_body } }
+ let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) }
+ let(:proxyable_params) do
+ {
+ id: proxyable.id.to_s
+ }
+ end
+ let(:expected_params) do
+ ActionController::Parameters.new(
+ prometheus_proxy_params(
+ proxy_path: 'query',
+ controller: described_class.controller_path,
+ action: 'prometheus_proxy'
+ )
+ ).permit!
+ end
+
+ before do
+ allow_next_instance_of(Prometheus::ProxyService, *service_params) do |proxy_service|
+ allow(proxy_service).to receive(:execute).and_return(service_result)
+ end
+ end
+
+ context 'with valid requests' do
+ context 'with success result' do
+ let(:prometheus_body) { '{"status":"success"}' }
+ let(:prometheus_json_body) { Gitlab::Json.parse(prometheus_body) }
+
+ it 'returns prometheus response' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(Prometheus::ProxyService).to have_received(:new).with(*service_params)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq(prometheus_json_body)
+ end
+
+ context 'with nil query' do
+ let(:params_without_query) do
+ prometheus_proxy_params.except(:query)
+ end
+
+ before do
+ expected_params.delete(:query)
+ end
+
+ it 'does not raise error' do
+ get :prometheus_proxy, params: params_without_query
+
+ expect(Prometheus::ProxyService).to have_received(:new).with(*service_params)
+ end
+ end
+ end
+
+ context 'with nil result' do
+ let(:service_result) { nil }
+
+ it 'returns 204 no_content' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(json_response['status']).to eq(_('processing'))
+ expect(json_response['message']).to eq(_('Not ready yet. Try again later.'))
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
+ context 'with 404 result' do
+ let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } }
+
+ it 'returns body' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['body']).to eq('value')
+ end
+ end
+
+ context 'with error result' do
+ context 'with http_status' do
+ let(:service_result) do
+ { http_status: :service_unavailable, status: :error, message: 'error message' }
+ end
+
+ it 'sets the http response status code' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:service_unavailable)
+ expect(json_response['status']).to eq('error')
+ expect(json_response['message']).to eq('error message')
+ end
+ end
+
+ context 'without http_status' do
+ let(:service_result) { { status: :error, message: 'error message' } }
+
+ it 'returns bad_request' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['status']).to eq('error')
+ expect(json_response['message']).to eq('error message')
+ end
+ end
+ end
+ end
+
+ context 'with inappropriate requests' do
+ let(:prometheus_body) { nil }
+
+ context 'without correct permissions' do
+ let(:user2) { create(:user) }
+
+ before do
+ sign_out(user)
+ sign_in(user2)
+ end
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'with invalid proxyable id' do
+ let(:prometheus_body) { nil }
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params(id: proxyable.id + 1)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ private
+
+ def prometheus_proxy_params(params = {})
+ {
+ proxy_path: 'query',
+ query: '1'
+ }.merge(proxyable_params).merge(params)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb b/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb
new file mode 100644
index 00000000000..cb8f6721d66
--- /dev/null
+++ b/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'GET #metrics_dashboard correctly formatted response' do
+ it 'returns a json object with the correct keys' do
+ get :metrics_dashboard, params: metrics_dashboard_req_params, format: :json
+
+ # Exclude `all_dashboards` to handle separately, at spec/controllers/projects/environments_controller_spec.rb:565
+ # because `all_dashboards` key is not part of expected shared behavior
+ found_keys = json_response.keys - ['all_dashboards']
+
+ expect(response).to have_gitlab_http_status(status_code)
+ expect(found_keys).to contain_exactly(*expected_keys)
+ end
+end
+
+RSpec.shared_examples_for 'GET #metrics_dashboard for dashboard' do |dashboard_name|
+ let(:expected_keys) { %w(dashboard status metrics_data) }
+ let(:status_code) { :ok }
+
+ it_behaves_like 'GET #metrics_dashboard correctly formatted response'
+
+ it 'returns correct dashboard' do
+ get :metrics_dashboard, params: metrics_dashboard_req_params, format: :json
+
+ expect(json_response['dashboard']['dashboard']).to eq(dashboard_name)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb b/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb
deleted file mode 100644
index 7885eb6c1f8..00000000000
--- a/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'namespace storage limit alert' do
- let(:alert_level) { :info }
-
- before do
- allow_next_instance_of(Namespaces::CheckStorageSizeService, namespace, user) do |check_storage_size_service|
- expect(check_storage_size_service).to receive(:execute).and_return(
- ServiceResponse.success(
- payload: {
- alert_level: alert_level,
- usage_message: "Usage",
- explanation_message: "Explanation",
- root_namespace: namespace
- }
- )
- )
- end
-
- allow(controller).to receive(:current_user).and_return(user)
- end
-
- render_views
-
- it 'does render' do
- subject
-
- expect(response.body).to match(/Explanation/)
- expect(response.body).to have_css('.js-namespace-storage-alert-dismiss')
- end
-
- context 'when alert_level is error' do
- let(:alert_level) { :error }
-
- it 'does not render a dismiss button' do
- subject
-
- expect(response.body).not_to have_css('.js-namespace-storage-alert-dismiss')
- end
- end
-
- context 'when cookie is set' do
- before do
- cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
- end
-
- it 'does not render alert' do
- subject
-
- expect(response.body).not_to match(/Explanation/)
- end
- end
-end
diff --git a/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb b/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
new file mode 100644
index 00000000000..c3e8f807afb
--- /dev/null
+++ b/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'raw snippet blob' do
+ context 'with valid params' do
+ before do
+ subject
+ end
+
+ it 'delivers file with correct Workhorse headers' do
+ expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with invalid file path' do
+ let(:filepath) { 'doesnotexist' }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'with invalid ref' do
+ let(:ref) { 'doesnotexist' }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ it_behaves_like 'content disposition headers'
+end
+
+RSpec.shared_examples 'raw snippet without repository' do |unauthorized_status|
+ context 'when authorized' do
+ it 'returns a 422' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'when unauthorized' do
+ let(:visibility) { :private }
+
+ it_behaves_like 'returning response status', unauthorized_status
+ end
+end
diff --git a/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb
new file mode 100644
index 00000000000..aa4d78b23f4
--- /dev/null
+++ b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'snippets sort order' do
+ let(:params) { {} }
+ let(:sort_argument) { {} }
+ let(:sort_params) { params.merge(sort_argument)}
+
+ before do
+ sign_in(user)
+
+ stub_snippet_counter
+ end
+
+ subject { get :index, params: sort_params }
+
+ context 'when no sort param is provided' do
+ it 'calls SnippetsFinder with updated_at sort option' do
+ expect(SnippetsFinder).to receive(:new).with(user,
+ hash_including(sort: 'updated_desc')).and_call_original
+
+ subject
+ end
+ end
+
+ context 'when sort param is provided' do
+ let(:order) { 'created_desc' }
+ let(:sort_argument) { { sort: order } }
+
+ it 'calls SnippetsFinder with the given sort param' do
+ expect(SnippetsFinder).to receive(:new).with(user,
+ hash_including(sort: order)).and_call_original
+
+ subject
+ end
+ end
+
+ def stub_snippet_counter
+ allow(Snippets::CountService)
+ .to receive(:new).and_return(double(:count_service, execute: {}))
+ end
+end
diff --git a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
new file mode 100644
index 00000000000..90588756eb0
--- /dev/null
+++ b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'tracking unique visits' do |method|
+ it 'tracks unique visit if the format is HTML' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit).with(instance_of(String), target_id)
+
+ get method, params: request_params, format: :html
+ end
+
+ it 'tracks unique visit if DNT is not enabled' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit).with(instance_of(String), target_id)
+ request.headers['DNT'] = '0'
+
+ get method, params: request_params, format: :html
+ end
+
+ it 'does not track unique visit if DNT is enabled' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+ request.headers['DNT'] = '1'
+
+ get method, params: request_params, format: :html
+ end
+
+ it 'does not track unique visit if the format is JSON' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+
+ get method, params: request_params, format: :json
+ end
+end
diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
index b5f2c0d07bf..4df3139d56e 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -104,6 +104,35 @@ RSpec.shared_examples 'wiki controller actions' do
end
end
+ describe 'GET #diff' do
+ context 'when commit exists' do
+ it 'renders the diff' do
+ get :diff, params: routing_params.merge(id: wiki_title, version_id: wiki.repository.commit.id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/diff')
+ expect(assigns(:diffs)).to be_a(Gitlab::Diff::FileCollection::Base)
+ expect(assigns(:diff_notes_disabled)).to be(true)
+ end
+ end
+
+ context 'when commit does not exist' do
+ it 'returns a 404 error' do
+ get :diff, params: routing_params.merge(id: wiki_title, version_id: 'invalid')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when page does not exist' do
+ it 'returns a 404 error' do
+ get :diff, params: routing_params.merge(id: 'invalid')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
describe 'GET #show' do
render_views
@@ -118,6 +147,7 @@ RSpec.shared_examples 'wiki controller actions' do
subject
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/show')
expect(assigns(:page).title).to eq(wiki_title)
expect(assigns(:sidebar_wiki_entries)).to contain_exactly(an_instance_of(WikiPage))
expect(assigns(:sidebar_limited)).to be(false)
@@ -130,6 +160,7 @@ RSpec.shared_examples 'wiki controller actions' do
subject
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/show')
expect(flash[:notice]).to eq(_('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.'))
end
end
@@ -138,19 +169,37 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when the page does not exist' do
let(:id) { 'does not exist' }
- before do
- subject
- end
+ context 'when the user can create pages' do
+ before do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/edit')
+ end
+
+ it 'builds a new wiki page with the id as the title' do
+ expect(assigns(:page).title).to eq(id)
+ end
+
+ context 'when a random_title param is present' do
+ let(:random_title) { true }
- it 'builds a new wiki page with the id as the title' do
- expect(assigns(:page).title).to eq(id)
+ it 'builds a new wiki page with no title' do
+ expect(assigns(:page).title).to be_empty
+ end
+ end
end
- context 'when a random_title param is present' do
- let(:random_title) { true }
+ context 'when the user cannot create pages' do
+ before do
+ sign_out(:user)
+ end
- it 'builds a new wiki page with no title' do
- expect(assigns(:page).title).to be_empty
+ it 'shows the empty state' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/empty')
end
end
end
@@ -166,6 +215,7 @@ RSpec.shared_examples 'wiki controller actions' do
it 'delivers the file with the correct headers' do
subject
+ expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true')
expect(response.cache_control[:public]).to be(false)
@@ -179,12 +229,31 @@ RSpec.shared_examples 'wiki controller actions' do
it 'renders json in a correct format' do
post :preview_markdown, params: routing_params.merge(id: 'page/path', text: '*Markdown* text')
+ expect(response).to have_gitlab_http_status(:ok)
expect(json_response.keys).to match_array(%w(body references))
end
end
- describe 'GET #edit' do
- subject { get(:edit, params: routing_params.merge(id: wiki_title)) }
+ shared_examples 'edit action' do
+ context 'when the page does not exist' do
+ let(:id_param) { 'invalid' }
+
+ it 'redirects to show' do
+ subject
+
+ expect(response).to redirect_to_wiki(wiki, 'invalid')
+ end
+ end
+
+ context 'when id param is blank' do
+ let(:id_param) { ' ' }
+
+ it 'redirects to the home page' do
+ subject
+
+ expect(response).to redirect_to_wiki(wiki, 'home')
+ end
+ end
context 'when page content encoding is invalid' do
it 'redirects to show' do
@@ -208,6 +277,14 @@ RSpec.shared_examples 'wiki controller actions' do
expect(response).to redirect_to_wiki(wiki, page)
end
end
+ end
+
+ describe 'GET #edit' do
+ let(:id_param) { wiki_title }
+
+ subject { get(:edit, params: routing_params.merge(id: id_param)) }
+
+ it_behaves_like 'edit action'
context 'when page content encoding is valid' do
render_views
@@ -224,23 +301,17 @@ RSpec.shared_examples 'wiki controller actions' do
describe 'PATCH #update' do
let(:new_title) { 'New title' }
let(:new_content) { 'New content' }
+ let(:id_param) { wiki_title }
subject do
patch(:update,
params: routing_params.merge(
- id: wiki_title,
+ id: id_param,
wiki: { title: new_title, content: new_content }
))
end
- context 'when page content encoding is invalid' do
- it 'redirects to show' do
- allow(controller).to receive(:valid_encoding?).and_return(false)
-
- subject
- expect(response).to redirect_to_wiki(wiki, wiki.list_pages.first)
- end
- end
+ it_behaves_like 'edit action'
context 'when page content encoding is valid' do
render_views
diff --git a/spec/support/shared_examples/create_alert_issue_shared_examples.rb b/spec/support/shared_examples/create_alert_issue_shared_examples.rb
new file mode 100644
index 00000000000..9f4e1c4335a
--- /dev/null
+++ b/spec/support/shared_examples/create_alert_issue_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'create alert issue sets issue labels' do
+ let(:title) { IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES[:title] }
+ let!(:label) { create(:label, project: project, title: title) }
+ let(:label_service) { instance_double(IncidentManagement::CreateIncidentLabelService, execute: label_service_response) }
+
+ before do
+ allow(IncidentManagement::CreateIncidentLabelService).to receive(:new).with(project, user).and_return(label_service)
+ end
+
+ context 'when create incident label responds with success' do
+ let(:label_service_response) { ServiceResponse.success(payload: { label: label }) }
+
+ it 'adds label to issue' do
+ expect(issue.labels).to eq([label])
+ end
+ end
+
+ context 'when create incident label responds with error' do
+ let(:label_service_response) { ServiceResponse.error(payload: { label: label }, message: 'label error') }
+
+ it 'creates an issue without labels' do
+ expect(issue.labels).to be_empty
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index 6007798c290..9fc5d8933e5 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -266,7 +266,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
end
end
- it 'has "Comment" selected when opening the menu', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196825' do
+ it 'has "Comment" selected when opening the menu', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196825' do
find(toggle_selector).click
find("#{menu_selector} li", match: :first)
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index 964c80007b0..487c38da7da 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -53,13 +53,17 @@ RSpec.shared_examples 'an editable merge request' do
find('#merge_request_description').native.send_keys('')
fill_in 'merge_request_description', with: user.to_reference[0..4]
- wait_for_requests
-
page.within('.atwho-view') do
expect(page).to have_content(user2.name)
end
end
+ it 'description has quick action autocomplete', :js do
+ find('#merge_request_description').native.send_keys('/')
+
+ expect(page).to have_selector('.atwho-container')
+ end
+
it 'has class js-quick-submit in form' do
expect(page).to have_selector('.js-quick-submit')
end
diff --git a/spec/support/shared_examples/features/error_tracking_shared_example.rb b/spec/support/shared_examples/features/error_tracking_shared_example.rb
index 1cd05b22ae9..ae7d62f31a2 100644
--- a/spec/support/shared_examples/features/error_tracking_shared_example.rb
+++ b/spec/support/shared_examples/features/error_tracking_shared_example.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'error tracking index page' do
+RSpec.shared_examples 'error tracking index page' do
it 'renders the error index page', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
within('div.js-title-container') do
expect(page).to have_content(project.namespace.name)
@@ -33,7 +33,7 @@ shared_examples 'error tracking index page' do
end
end
-shared_examples 'expanded stack trace context' do |selected_line: nil, expected_line: 1|
+RSpec.shared_examples 'expanded stack trace context' do |selected_line: nil, expected_line: 1|
it 'expands the stack trace context', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
within('div.stacktrace') do
find("div.file-holder:nth-child(#{selected_line}) svg.ic-chevron-right").click if selected_line
@@ -48,7 +48,7 @@ shared_examples 'expanded stack trace context' do |selected_line: nil, expected_
end
end
-shared_examples 'error tracking show page' do
+RSpec.shared_examples 'error tracking show page' do
it 'renders the error details', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
content = page.find(".content")
nav = page.find("nav.breadcrumbs")
diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
index 98010150e65..00ce690d2e3 100644
--- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
+++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
@@ -35,7 +35,12 @@ RSpec.shared_examples 'Maintainer manages access requests' do
expect_visible_access_request(entity, user)
- accept_confirm { click_on 'Deny access' }
+ # Open modal
+ click_on 'Deny access request'
+
+ expect(page).not_to have_field "Also unassign this user from related issues and merge requests"
+
+ click_on 'Deny access request'
expect_no_visible_access_request(entity, user)
expect(page).not_to have_content user.name
diff --git a/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
index 2b96010477c..b2047f1d32c 100644
--- a/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-shared_examples 'no Jira import data present' do
+RSpec.shared_examples 'no Jira import data present' do
it 'returns none' do
expect(resolve_imports).to eq JiraImportState.none
end
end
-shared_examples 'no Jira import access' do
+RSpec.shared_examples 'no Jira import access' do
it 'raises error' do
expect do
resolve_imports
diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
index 022d41c0bdd..86d2bb6c747 100644
--- a/spec/support/shared_examples/graphql/mutation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
@@ -7,13 +7,23 @@
#
# There must be a method or let called `mutation` defined that executes
# the mutation.
-RSpec.shared_examples 'a mutation that returns top-level errors' do |errors:|
+RSpec.shared_examples 'a mutation that returns top-level errors' do |errors: []|
+ let(:match_errors) { eq(errors) }
+
it do
post_graphql_mutation(mutation, current_user: current_user)
error_messages = graphql_errors.map { |e| e['message'] }
- expect(error_messages).to eq(errors)
+ expect(error_messages).to match_errors
+ end
+end
+
+RSpec.shared_examples 'an invalid argument to the mutation' do |argument_name:|
+ it_behaves_like 'a mutation that returns top-level errors' do
+ let(:match_errors) do
+ contain_exactly(include("invalid value for #{GraphqlHelpers.fieldnamerize(argument_name)}"))
+ end
end
end
diff --git a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
index 4bed322564a..94b7ed1618d 100644
--- a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-shared_examples 'no project services' do
+RSpec.shared_examples 'no project services' do
it 'returns empty collection' do
expect(resolve_services).to eq []
end
end
-shared_examples 'cannot access project services' do
+RSpec.shared_examples 'cannot access project services' do
it 'raises error' do
expect do
resolve_services
diff --git a/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb b/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb
index 58cd3d21f66..67d1c2a8254 100644
--- a/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb
@@ -1,7 +1,12 @@
# frozen_string_literal: true
RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
- subject { mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: issuable.iid) }
+ include GraphqlHelpers
+
+ let(:parent_path) { parent.full_path }
+ let(:iid) { issuable.iid }
+
+ subject(:result) { mutation.resolve_issuable(type: type, parent_path: parent_path, iid: iid) }
context 'when user has access' do
before do
@@ -9,37 +14,23 @@ RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
end
it 'resolves issuable by iid' do
- result = type == :merge_request ? subject.sync : subject
expect(result).to eq(issuable)
end
- it 'uses the correct Resolver to resolve issuable' do
- resolver_class = "Resolvers::#{type.to_s.classify.pluralize}Resolver".constantize
- resolve_method = type == :epic ? :resolve_group : :resolve_project
- resolved_parent = mutation.send(resolve_method, full_path: parent.full_path)
-
- allow(mutation).to receive(resolve_method)
- .with(full_path: parent.full_path)
- .and_return(resolved_parent)
-
- expect(resolver_class.single).to receive(:new)
- .with(object: resolved_parent, context: context, field: nil)
- .and_call_original
-
- subject
- end
-
- it 'returns nil if issuable is not found' do
- result = mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: "100")
- result = result.respond_to?(:sync) ? result.sync : result
+ context 'the IID does not refer to a valid issuable' do
+ let(:iid) { '100' }
- expect(result).to be_nil
+ it 'returns nil' do
+ expect(result).to be_nil
+ end
end
- it 'returns nil if parent path is not present' do
- result = mutation.resolve_issuable(type: type, parent_path: "", iid: issuable.iid)
+ context 'the parent path is not present' do
+ let(:parent_path) { '' }
- expect(result).to be_nil
+ it 'returns nil' do
+ expect(result).to be_nil
+ end
end
end
end
diff --git a/spec/support/shared_examples/helm_commands_shared_examples.rb b/spec/support/shared_examples/helm_commands_shared_examples.rb
index f0624fbf29f..0a94c6648cc 100644
--- a/spec/support/shared_examples/helm_commands_shared_examples.rb
+++ b/spec/support/shared_examples/helm_commands_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'helm command generator' do
+RSpec.shared_examples 'helm command generator' do
describe '#generate_script' do
let(:helm_setup) do
<<~EOS
@@ -14,7 +14,7 @@ shared_examples 'helm command generator' do
end
end
-shared_examples 'helm command' do
+RSpec.shared_examples 'helm command' do
describe '#rbac?' do
subject { command.rbac? }
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
index a40c38106e2..af65b61021c 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'resource mentions migration' do |migration_class, resource_class|
+RSpec.shared_examples 'resource mentions migration' do |migration_class, resource_class|
it 'migrates resource mentions' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
@@ -21,7 +21,7 @@ shared_examples 'resource mentions migration' do |migration_class, resource_clas
end
end
-shared_examples 'resource notes mentions migration' do |migration_class, resource_class|
+RSpec.shared_examples 'resource notes mentions migration' do |migration_class, resource_class|
it 'migrates mentions from note' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
@@ -56,7 +56,7 @@ shared_examples 'resource notes mentions migration' do |migration_class, resourc
end
end
-shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes|
+RSpec.shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes|
before do
stub_const("#{described_class.name}::BATCH_SIZE", 1)
end
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
index 14292f70228..d76089d56dd 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
ISSUES_MEDIAN = 30.minutes.to_i
-shared_examples 'base stage' do
+RSpec.shared_examples 'base stage' do
let(:stage) { described_class.new(options: { project: double }) }
before do
@@ -35,7 +35,7 @@ shared_examples 'base stage' do
end
end
-shared_examples 'calculate #median with date range' do
+RSpec.shared_examples 'calculate #median with date range' do
context 'when valid date range is given' do
before do
stage_options[:from] = 5.days.ago
@@ -55,7 +55,7 @@ shared_examples 'calculate #median with date range' do
end
end
-shared_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
+RSpec.shared_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
let(:stage_params) { Gitlab::Analytics::CycleAnalytics::DefaultStages.send("params_for_#{stage_name}_stage").merge(project: project) }
let(:stage) { Analytics::CycleAnalytics::ProjectStage.new(stage_params) }
let(:data_collector) { Gitlab::Analytics::CycleAnalytics::DataCollector.new(stage: stage, params: { from: stage_options[:from], current_user: project.creator }) }
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb
index c053af010b3..4f648b27ea2 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'default query config' do
+RSpec.shared_examples 'default query config' do
let(:project) { create(:project) }
let(:event) { described_class.new(stage: stage_name, options: { from: 1.day.ago, project: project }) }
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
index a00359ce979..d0e41605e00 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
@@ -8,6 +8,7 @@ RSpec.shared_examples_for 'cycle analytics event' do
it { expect(described_class.identifier).to be_a_kind_of(Symbol) }
it { expect(instance.object_type.ancestors).to include(ApplicationRecord) }
it { expect(instance).to respond_to(:timestamp_projection) }
+ it { expect(instance.column_list).to be_a_kind_of(Array) }
describe '#apply_query_customization' do
it 'expects an ActiveRecord::Relation object as argument and returns a modified version of it' do
diff --git a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
index a1cdd054f32..e43ce936b90 100644
--- a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
@@ -10,7 +10,7 @@ RSpec.shared_examples 'diff statistics' do |test_include_stats_flag: true|
end
end
- context 'when should request diff stats' do
+ context 'when include_stats is true' do
it 'Repository#diff_stats is called' do
expect(diffable.project.repository)
.to receive(:diff_stats)
@@ -59,43 +59,87 @@ RSpec.shared_examples 'unfoldable diff' do
end
RSpec.shared_examples 'cacheable diff collection' do
- let(:cache) { instance_double(Gitlab::Diff::HighlightCache) }
+ let(:highlight_cache) { instance_double(Gitlab::Diff::HighlightCache, write_if_empty: true, clear: nil, decorate: nil) }
+ let(:stats_cache) { instance_double(Gitlab::Diff::StatsCache, read: nil, write_if_empty: true, clear: nil) }
before do
- expect(Gitlab::Diff::HighlightCache).to receive(:new).with(subject) { cache }
+ expect(Gitlab::Diff::HighlightCache).to receive(:new).with(subject) { highlight_cache }
end
describe '#write_cache' do
+ before do
+ expect(Gitlab::Diff::StatsCache).to receive(:new).with(cachable_key: diffable.cache_key) { stats_cache }
+ end
+
it 'calls Gitlab::Diff::HighlightCache#write_if_empty' do
- expect(cache).to receive(:write_if_empty).once
+ expect(highlight_cache).to receive(:write_if_empty).once
+
+ subject.write_cache
+ end
+
+ it 'calls Gitlab::Diff::StatsCache#write_if_empty with diff stats' do
+ diff_stats = Gitlab::Git::DiffStatsCollection.new([])
+
+ expect(diffable.project.repository)
+ .to receive(:diff_stats).and_return(diff_stats)
+
+ expect(stats_cache).to receive(:write_if_empty).once.with(diff_stats)
subject.write_cache
end
end
describe '#clear_cache' do
+ before do
+ expect(Gitlab::Diff::StatsCache).to receive(:new).with(cachable_key: diffable.cache_key) { stats_cache }
+ end
+
it 'calls Gitlab::Diff::HighlightCache#clear' do
- expect(cache).to receive(:clear).once
+ expect(highlight_cache).to receive(:clear).once
subject.clear_cache
end
- end
- describe '#cache_key' do
- it 'calls Gitlab::Diff::HighlightCache#key' do
- expect(cache).to receive(:key).once
+ it 'calls Gitlab::Diff::StatsCache#clear' do
+ expect(stats_cache).to receive(:clear).once
- subject.cache_key
+ subject.clear_cache
end
end
describe '#diff_files' do
+ before do
+ expect(Gitlab::Diff::StatsCache).to receive(:new).with(cachable_key: diffable.cache_key) { stats_cache }
+ end
+
it 'calls Gitlab::Diff::HighlightCache#decorate' do
- expect(cache).to receive(:decorate)
+ expect(highlight_cache).to receive(:decorate)
.with(instance_of(Gitlab::Diff::File))
.exactly(cacheable_files_count).times
subject.diff_files
end
+
+ context 'when there are stats cached' do
+ before do
+ allow(stats_cache).to receive(:read).and_return(Gitlab::Git::DiffStatsCollection.new([]))
+ end
+
+ it 'does not make a diff stats rpc call' do
+ expect(diffable.project.repository).not_to receive(:diff_stats)
+
+ subject.diff_files
+ end
+ end
+
+ context 'when there are no stats cached' do
+ it 'makes a diff stats rpc call' do
+ expect(diffable.project.repository)
+ .to receive(:diff_stats)
+ .with(diffable.diff_refs.base_sha, diffable.diff_refs.head_sha)
+
+ subject.diff_files
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb
index 97f4341340d..28137530038 100644
--- a/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
RSpec.shared_examples 'parsing gl_repository identifier' do
- subject { described_class.new(identifier) }
+ subject { described_class.parse(identifier) }
it 'returns correct information' do
- aggregate_failures do
- expect(subject.repo_type).to eq(expected_type)
- expect(subject.fetch_container!).to eq(expected_container)
- end
+ expect(subject).to have_attributes(
+ repo_type: expected_type,
+ container: expected_container
+ )
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb
index 06ea540706a..222390cf9cd 100644
--- a/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'stuck import job detection' do
+RSpec.shared_examples 'stuck import job detection' do
context 'when the job has completed' do
context 'when the import status was already updated' do
before do
diff --git a/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb
index 85dcc053447..b1788bb5912 100644
--- a/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-shared_examples 'raise exception if not implemented' do
+RSpec.shared_examples 'raise exception if not implemented' do
it { expect { described_class.new(project).imported_items_cache_key }.not_to raise_error }
end
diff --git a/spec/support/shared_examples/lib/wikis_api_examples.rb b/spec/support/shared_examples/lib/wikis_api_examples.rb
new file mode 100644
index 00000000000..2e4c667d37e
--- /dev/null
+++ b/spec/support/shared_examples/lib/wikis_api_examples.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'wikis API returns list of wiki pages' do
+ context 'when wiki has pages' do
+ let!(:pages) do
+ [create(:wiki_page, wiki: wiki, title: 'page1', content: 'content of page1'),
+ create(:wiki_page, wiki: wiki, title: 'page2.with.dot', content: 'content of page2')]
+ end
+
+ it 'returns the list of wiki pages without content' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(2)
+
+ json_response.each_with_index do |page, index|
+ expect(page.keys).to match_array(expected_keys_without_content)
+ expect(page['slug']).to eq(pages[index].slug)
+ expect(page['title']).to eq(pages[index].title)
+ end
+ end
+
+ it 'returns the list of wiki pages with content' do
+ get api(url, user), params: { with_content: 1 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(2)
+
+ json_response.each_with_index do |page, index|
+ expect(page.keys).to match_array(expected_keys_with_content)
+ expect(page['content']).to eq(pages[index].content)
+ expect(page['slug']).to eq(pages[index].slug)
+ expect(page['title']).to eq(pages[index].title)
+ end
+ end
+ end
+
+ it 'return the empty list of wiki pages' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(0)
+ end
+end
+
+RSpec.shared_examples_for 'wikis API returns wiki page' do
+ it 'returns the wiki page' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(4)
+ expect(json_response.keys).to match_array(expected_keys_with_content)
+ expect(json_response['content']).to eq(page.content)
+ expect(json_response['slug']).to eq(page.slug)
+ expect(json_response['title']).to eq(page.title)
+ end
+end
+
+RSpec.shared_examples_for 'wikis API creates wiki page' do
+ it 'creates the wiki page' do
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response.size).to eq(4)
+ expect(json_response.keys).to match_array(expected_keys_with_content)
+ expect(json_response['content']).to eq(payload[:content])
+ expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
+ expect(json_response['title']).to eq(payload[:title])
+ expect(json_response['rdoc']).to eq(payload[:rdoc])
+ end
+
+ [:title, :content].each do |part|
+ it "responds with validation error on empty #{part}" do
+ payload.delete(part)
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq("#{part} is missing")
+ end
+ end
+end
+
+RSpec.shared_examples_for 'wikis API updates wiki page' do
+ it 'updates the wiki page' do
+ put(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(4)
+ expect(json_response.keys).to match_array(expected_keys_with_content)
+ expect(json_response['content']).to eq(payload[:content])
+ expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
+ expect(json_response['title']).to eq(payload[:title])
+ end
+
+ [:title, :content, :format].each do |part|
+ it "updates with wiki with missing #{part}" do
+ payload.delete(part)
+
+ put(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 403 Forbidden' do
+ it 'returns 403 Forbidden' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response.size).to eq(1)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 404 Wiki Page Not Found' do
+ it 'returns 404 Wiki Page Not Found' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response.size).to eq(1)
+ expect(json_response['message']).to eq('404 Wiki Page Not Found')
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 404 Not Found' do |what|
+ it "returns 404 #{what} Not Found" do
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response.size).to eq(1)
+ expect(json_response['message']).to eq("404 #{what} Not Found")
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 204 No Content' do
+ it 'returns 204 No Content' do
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+end
+
+RSpec.shared_examples_for 'wiki API uploads wiki attachment' do
+ it 'pushes attachment to the wiki repository' do
+ allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+ workhorse_post_with_file(api(url, user), file_key: :file, params: payload)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to eq result_hash.deep_stringify_keys
+ end
+
+ it 'responds with validation error on empty file' do
+ payload.delete(:file)
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq('file is missing')
+ end
+
+ it 'responds with validation error on invalid temp file' do
+ payload[:file] = { tempfile: '/etc/hosts' }
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq('file is invalid')
+ end
+
+ it 'is backward compatible with regular multipart uploads' do
+ allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to eq result_hash.deep_stringify_keys
+ end
+end
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index 0efa5e56199..f80ca235220 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -88,16 +88,6 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
end
end
- it 'sets the correct version of the application' do
- subject.update!(version: '0.0.0')
-
- subject.make_installed!
-
- subject.reload
-
- expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
- end
-
context 'application is updating' do
subject { create(application_name, :updating) }
@@ -146,16 +136,6 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
end
end
end
-
- it 'updates the version of the application' do
- subject.update!(version: '0.0.0')
-
- subject.make_installed!
-
- subject.reload
-
- expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
- end
end
end
diff --git a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
index cf7010c48c2..ed2e4fee2de 100644
--- a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
@@ -19,4 +19,32 @@ RSpec.shared_examples 'cluster application version specs' do |application_name|
it { is_expected.to be_falsey }
end
end
+
+ describe '#make_installed' do
+ subject { create(application_name, :installing) }
+
+ it 'sets the correct version of the application' do
+ subject.update!(version: '0.0.0')
+
+ subject.make_installed!
+
+ subject.reload
+
+ expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
+ end
+
+ context 'application is updating' do
+ subject { create(application_name, :updating) }
+
+ it 'updates the version of the application' do
+ subject.update!(version: '0.0.0')
+
+ subject.make_installed!
+
+ subject.reload
+
+ expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb b/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
index 7bcd6191f1d..3db5d7a8d7d 100644
--- a/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
@@ -7,17 +7,17 @@ RSpec.shared_examples 'a BulkInsertSafe model' do |klass|
let(:target_class) { klass.dup }
# We consider all callbacks unsafe for bulk insertions unless we have explicitly
- # whitelisted them (esp. anything related to :save, :create, :commit etc.)
- let(:callback_method_blacklist) do
+ # allowed them (especially anything related to :save, :create, :commit, etc.)
+ let(:unsafe_callbacks) do
ActiveRecord::Callbacks::CALLBACKS.reject do |callback|
cb_name = callback.to_s.gsub(/(before_|after_|around_)/, '').to_sym
- BulkInsertSafe::CALLBACK_NAME_WHITELIST.include?(cb_name)
+ BulkInsertSafe::ALLOWED_CALLBACKS.include?(cb_name)
end.to_set
end
context 'when calling class methods directly' do
it 'raises an error when method is not bulk-insert safe' do
- callback_method_blacklist.each do |m|
+ unsafe_callbacks.each do |m|
expect { target_class.send(m, nil) }.to(
raise_error(BulkInsertSafe::MethodNotAllowedError),
"Expected call to #{m} to raise an error, but it didn't"
@@ -26,7 +26,7 @@ RSpec.shared_examples 'a BulkInsertSafe model' do |klass|
end
it 'does not raise an error when method is bulk-insert safe' do
- BulkInsertSafe::CALLBACK_NAME_WHITELIST.each do |name|
+ BulkInsertSafe::ALLOWED_CALLBACKS.each do |name|
expect { target_class.set_callback(name) {} }.not_to raise_error
end
end
diff --git a/spec/support/shared_examples/models/jira_import_state_shared_examples.rb b/spec/support/shared_examples/models/jira_import_state_shared_examples.rb
index f4643375c8e..1999f0f3c49 100644
--- a/spec/support/shared_examples/models/jira_import_state_shared_examples.rb
+++ b/spec/support/shared_examples/models/jira_import_state_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'multiple running imports not allowed' do
+RSpec.shared_examples 'multiple running imports not allowed' do
it 'returns not valid' do
new_import = build(:jira_import_state, project: project)
@@ -9,21 +9,21 @@ shared_examples 'multiple running imports not allowed' do
end
end
-shared_examples 'in progress' do |status|
+RSpec.shared_examples 'in progress' do |status|
it 'returns true' do
jira_import_state = build(:jira_import_state, status: status)
expect(jira_import_state).to be_in_progress
end
end
-shared_examples 'not in progress' do |status|
+RSpec.shared_examples 'not in progress' do |status|
it 'returns false' do
jira_import_state = build(:jira_import_state, status: status)
expect(jira_import_state).not_to be_in_progress
end
end
-shared_examples 'can transition' do |states|
+RSpec.shared_examples 'can transition' do |states|
states.each do |state|
it 'returns true' do
expect(jira_import.send(state)).to be true
@@ -31,7 +31,7 @@ shared_examples 'can transition' do |states|
end
end
-shared_examples 'cannot transition' do |states|
+RSpec.shared_examples 'cannot transition' do |states|
states.each do |state|
it 'returns false' do
expect(jira_import.send(state)).to be false
diff --git a/spec/support/shared_examples/models/note_access_check_shared_examples.rb b/spec/support/shared_examples/models/note_access_check_shared_examples.rb
index 3bafad202f6..44edafe9091 100644
--- a/spec/support/shared_examples/models/note_access_check_shared_examples.rb
+++ b/spec/support/shared_examples/models/note_access_check_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'users with note access' do
+RSpec.shared_examples 'users with note access' do
it 'returns true' do
users.each do |user|
expect(note.system_note_with_references_visible_for?(user)).to be_truthy
@@ -9,7 +9,7 @@ shared_examples 'users with note access' do
end
end
-shared_examples 'users without note access' do
+RSpec.shared_examples 'users without note access' do
it 'returns false' do
users.each do |user|
expect(note.system_note_with_references_visible_for?(user)).to be_falsy
diff --git a/spec/support/shared_examples/models/services_fields_shared_examples.rb b/spec/support/shared_examples/models/services_fields_shared_examples.rb
deleted file mode 100644
index cb36f74460d..00000000000
--- a/spec/support/shared_examples/models/services_fields_shared_examples.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'issue tracker fields' do
- let(:title) { 'custom title' }
- let(:description) { 'custom description' }
- let(:url) { 'http://issue_tracker.example.com' }
-
- context 'when data are stored in the properties' do
- describe '#update' do
- before do
- service.update(title: 'new_title', description: 'new description')
- end
-
- it 'removes title and description from properties' do
- expect(service.reload.properties).not_to include('title', 'description')
- end
-
- it 'stores title & description in services table' do
- expect(service.read_attribute(:title)).to eq('new_title')
- expect(service.read_attribute(:description)).to eq('new description')
- end
- end
-
- describe 'reading fields' do
- it 'returns correct values' do
- expect(service.title).to eq(title)
- expect(service.description).to eq(description)
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/models/synthetic_note_shared_examples.rb b/spec/support/shared_examples/models/synthetic_note_shared_examples.rb
new file mode 100644
index 00000000000..a41ade2950a
--- /dev/null
+++ b/spec/support/shared_examples/models/synthetic_note_shared_examples.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a synthetic note' do |action|
+ it_behaves_like 'a system note', exclude_project: true do
+ let(:action) { action }
+ end
+
+ describe '#discussion_id' do
+ before do
+ allow(event).to receive(:discussion_id).and_return('foobar42')
+ end
+
+ it 'returns the expected discussion id' do
+ expect(subject.discussion_id(nil)).to eq('foobar42')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/namespaces/hierarchy_examples.rb b/spec/support/shared_examples/namespaces/hierarchy_examples.rb
new file mode 100644
index 00000000000..d5754f47be2
--- /dev/null
+++ b/spec/support/shared_examples/namespaces/hierarchy_examples.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'hierarchy with traversal_ids' do
+ # A convenient null node to represent the parent of root.
+ let(:null_node) { double(traversal_ids: []) }
+
+ # Walk the tree to assert that the current_node's traversal_id is always
+ # present and equal to it's parent's traversal_ids plus it's own ID.
+ def validate_traversal_ids(current_node, parent = null_node)
+ expect(current_node.traversal_ids).to be_present
+ expect(current_node.traversal_ids).to eq parent.traversal_ids + [current_node.id]
+
+ current_node.children.each do |child|
+ validate_traversal_ids(child, current_node)
+ end
+ end
+
+ it 'will be valid' do
+ validate_traversal_ids(root)
+ end
+end
diff --git a/spec/support/shared_examples/policies/namespace_policy_shared_examples.rb b/spec/support/shared_examples/policies/namespace_policy_shared_examples.rb
new file mode 100644
index 00000000000..ddec1ba5e3d
--- /dev/null
+++ b/spec/support/shared_examples/policies/namespace_policy_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'update namespace limit policy' do
+ describe 'update_subscription_limit' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:policy) { :update_subscription_limit }
+
+ where(:role, :is_com, :allowed) do
+ :user | true | false
+ :owner | true | false
+ :admin | true | true
+ :user | false | false
+ :owner | false | false
+ :admin | false | false
+ end
+
+ with_them do
+ let(:current_user) { build_stubbed(role) }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(is_com)
+ end
+
+ context 'when admin mode enabled', :enable_admin_mode do
+ it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
+ end
+
+ context 'when admin mode disabled' do
+ it { is_expected.to be_disallowed(policy) }
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
index 4dd0152e3d1..f8526ec68dc 100644
--- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb
+++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
@@ -41,6 +41,28 @@ RSpec.shared_examples 'archived project policies' do
end
end
+RSpec.shared_examples 'project private features with read_all_resources ability' do
+ subject { described_class.new(user, project) }
+
+ before do
+ project.project_feature.update!(
+ repository_access_level: ProjectFeature::PRIVATE,
+ merge_requests_access_level: ProjectFeature::PRIVATE,
+ builds_access_level: ProjectFeature::PRIVATE
+ )
+ end
+
+ [:public, :internal, :private].each do |visibility|
+ context "for #{visibility} projects" do
+ let(:project) { create(:project, visibility, namespace: owner.namespace) }
+
+ it 'allows the download_code ability' do
+ expect_allowed(:download_code)
+ end
+ end
+ end
+end
+
RSpec.shared_examples 'project policies as anonymous' do
context 'abilities for public projects' do
context 'when a project has pending invites' do
@@ -231,6 +253,12 @@ RSpec.shared_examples 'project policies as admin with admin mode' do
let(:regular_abilities) { owner_permissions }
end
end
+
+ context 'abilities for all project visibility', :enable_admin_mode do
+ it_behaves_like 'project private features with read_all_resources ability' do
+ let(:user) { admin }
+ end
+ end
end
RSpec.shared_examples 'project policies as admin without admin mode' do
diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
index 159660e7d1d..910805dbdea 100644
--- a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
@@ -47,7 +47,7 @@ RSpec.shared_examples 'create_merge_request quick action' do
expect(created_mr.source_branch).to eq(issue.to_branch_name)
visit project_merge_request_path(project, created_mr)
- expect(page).to have_content %{WIP: Resolve "#{issue.title}"}
+ expect(page).to have_content %{Draft: Resolve "#{issue.title}"}
end
it 'creates a merge request using the given branch name' do
@@ -60,7 +60,7 @@ RSpec.shared_examples 'create_merge_request quick action' do
expect(created_mr.source_branch).to eq(branch_name)
visit project_merge_request_path(project, created_mr)
- expect(page).to have_content %{WIP: Resolve "#{issue.title}"}
+ expect(page).to have_content %{Draft: Resolve "#{issue.title}"}
end
end
end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
index e0edbc5637a..258d9ab85e4 100644
--- a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
@@ -1,68 +1,85 @@
# frozen_string_literal: true
RSpec.shared_examples 'merge quick action' do
- context 'when the current user can merge the MR' do
+ context 'when updating the description' do
before do
sign_in(user)
- visit project_merge_request_path(project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
end
- it 'merges the MR', :sidekiq_might_not_need_inline do
- add_note("/merge")
-
- expect(page).to have_content 'Merged this merge request.'
+ it 'merges the MR', :sidekiq_inline do
+ fill_in('Description', with: '/merge')
+ click_button('Save changes')
+ expect(page).to have_content('Merged')
expect(merge_request.reload).to be_merged
end
+ end
- context 'when auto merge is avialable' do
+ context 'when creating a new note' do
+ context 'when the current user can merge the MR' do
before do
- create(:ci_pipeline, :detached_merge_request_pipeline,
- project: project, merge_request: merge_request)
- merge_request.update_head_pipeline
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
- it 'schedules to merge the MR' do
+ it 'merges the MR', :sidekiq_inline do
add_note("/merge")
- expect(page).to have_content "Scheduled to merge this merge request (Merge when pipeline succeeds)."
+ expect(page).to have_content 'Merged this merge request.'
- expect(merge_request.reload).to be_auto_merge_enabled
- expect(merge_request.reload).not_to be_merged
+ expect(merge_request.reload).to be_merged
end
- end
- end
- context 'when the head diff changes in the meanwhile' do
- before do
- merge_request.source_branch = 'another_branch'
- merge_request.save
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
+ context 'when auto merge is available' do
+ before do
+ create(:ci_pipeline, :detached_merge_request_pipeline,
+ project: project, merge_request: merge_request)
+ merge_request.update_head_pipeline
+ end
- it 'does not merge the MR' do
- add_note("/merge")
+ it 'schedules to merge the MR' do
+ add_note("/merge")
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).to have_content "Scheduled to merge this merge request (Merge when pipeline succeeds)."
- expect(merge_request.reload).not_to be_merged
+ expect(merge_request.reload).to be_auto_merge_enabled
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
end
- end
- context 'when the current user cannot merge the MR' do
- before do
- project.add_guest(guest)
- sign_in(guest)
- visit project_merge_request_path(project, merge_request)
+ context 'when the head diff changes in the meanwhile' do
+ before do
+ merge_request.source_branch = 'another_branch'
+ merge_request.save
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ add_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
end
- it 'does not merge the MR' do
- add_note("/merge")
+ context 'when the current user cannot merge the MR' do
+ before do
+ project.add_guest(guest)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ add_note("/merge")
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).not_to have_content 'Your commands have been executed!'
- expect(merge_request.reload).not_to be_merged
+ expect(merge_request.reload).not_to be_merged
+ end
end
end
end
diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
new file mode 100644
index 00000000000..5257980d7df
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'Composer user type' do |user_type, add_member|
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+end
+
+RSpec.shared_examples 'Composer package index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/index')
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer empty provider index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/provider')
+ expect(json_response['providers']).to eq({})
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer provider index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/provider')
+ expect(json_response['providers']).to include(package.name)
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer package api request' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/package')
+ expect(json_response['packages']).to include(package.name)
+ expect(json_response['packages'][package.name]).to include(package.version)
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer package creation' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'creates package files' do
+ expect { subject }
+ .to change { project.packages.composer.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'register_package'
+ end
+end
+
+RSpec.shared_examples 'process Composer api request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_context 'Composer auth headers' do |user_role, user_token|
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+end
+
+RSpec.shared_context 'Composer api project access' do |project_visibility_level, user_role, user_token|
+ include_context 'Composer auth headers', user_role, user_token do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+ end
+end
+
+RSpec.shared_context 'Composer api group access' do |project_visibility_level, user_role, user_token|
+ include_context 'Composer auth headers', user_role, user_token do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects Composer access with unknown group id' do
+ context 'with an unknown group' do
+ let(:group) { double(id: non_existing_record_id) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects Composer access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { double(id: non_existing_record_id) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
index 246f1850c3c..da1caef63ba 100644
--- a/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'unauthorized users cannot read services' do
+RSpec.shared_examples 'unauthorized users cannot read services' do
before do
post_graphql(query, current_user: current_user)
end
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index 60ed61269df..a34c48a5ba4 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -132,6 +132,16 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
expect(response).to have_gitlab_http_status(:created)
expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_falsey
+ expect(json_response['author']['username']).to eq(user.username)
+ end
+
+ it "creates a confidential note if confidential is set to true" do
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!', confidential: true }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_truthy
expect(json_response['author']['username']).to eq(user.username)
end
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
new file mode 100644
index 00000000000..8d8483cae72
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -0,0 +1,408 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'rejects nuget packages access' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ if status == :unauthorized
+ it 'has the correct response header' do
+ subject
+
+ expect(response.headers['Www-Authenticate: Basic realm']).to eq 'GitLab Packages Registry'
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget service index request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'nuget_service_index'
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/service_index')
+ expect(json_response).to be_a(Hash)
+ end
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+ end
+end
+
+RSpec.shared_examples 'returning nuget metadata json response with json schema' do |json_schema|
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema(json_schema)
+ expect(json_response).to be_a(Hash)
+ end
+end
+
+RSpec.shared_examples 'process nuget metadata request at package name level' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget metadata request at package name and package version level' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'has the proper content type' do
+ subject
+
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+
+ context 'with a request that bypassed gitlab-workhorse' do
+ let(:headers) do
+ build_basic_auth_header(user.username, personal_access_token.token)
+ .merge(workhorse_header)
+ .tap { |h| h.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) }
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'creates nuget package files' do
+ it 'creates package files' do
+ expect(::Packages::Nuget::ExtractionWorker).to receive(:perform_async).once
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ expect(response).to have_gitlab_http_status(status)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq('package.nupkg')
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ context 'with object storage disabled' do
+ before do
+ stub_package_file_object_storage(enabled: false)
+ end
+
+ context 'without a file from workhorse' do
+ let(:send_rewritten_field) { false }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ context 'with correct params' do
+ it_behaves_like 'package workhorse uploads'
+ it_behaves_like 'creates nuget package files'
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { { package: fog_file, 'package.remote_id' => file_name } }
+
+ context 'and direct upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'creates nuget package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) do
+ {
+ package: fog_file,
+ 'package.remote_id' => remote_id
+ }
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+
+ context 'with crafted package.path param' do
+ let(:crafted_file) { Tempfile.new('nuget.crafted.package.path') }
+ let(:url) { "/projects/#{project.id}/packages/nuget?package.path=#{crafted_file.path}" }
+ let(:params) { { file: temp_file(file_name) } }
+ let(:file_key) { :file }
+
+ it 'does not create a package file' do
+ expect { subject }.to change { ::Packages::PackageFile.count }.by(0)
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+ end
+
+ context 'and direct upload disabled' do
+ context 'and background upload disabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: false)
+ end
+
+ it_behaves_like 'creates nuget package files'
+ end
+
+ context 'and background upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: true)
+ end
+
+ it_behaves_like 'creates nuget package files'
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+end
+
+RSpec.shared_examples 'process nuget download versions request' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'returns a valid nuget download versions json response' do
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/download_versions')
+ expect(json_response).to be_a(Hash)
+ expect(json_response['versions']).to match_array(packages.map(&:version).sort)
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returns a valid nuget download versions json response'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returns a valid nuget download versions json response'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget download content request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+
+ it 'returns a valid package archive' do
+ subject
+
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid package archive' do
+ subject
+
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget search request' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'returns a valid json search response' do |status, total_hits, versions|
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to be_a(Hash)
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/search')
+ expect(json_response['totalHits']).to eq total_hits
+ expect(json_response['data'].map { |e| e['versions'].size }).to match_array(versions)
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returns a valid json search response', status, 4, [1, 5, 5, 1]
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'search_package'
+
+ context 'with skip set to 2' do
+ let(:skip) { 2 }
+
+ it_behaves_like 'returns a valid json search response', status, 4, [5, 1]
+ end
+
+ context 'with take set to 2' do
+ let(:take) { 2 }
+
+ it_behaves_like 'returns a valid json search response', status, 4, [1, 5]
+ end
+
+ context 'without prereleases' do
+ let(:include_prereleases) { false }
+
+ it_behaves_like 'returns a valid json search response', status, 3, [1, 5, 5]
+ end
+
+ context 'with empty search term' do
+ let(:search_term) { '' }
+
+ it_behaves_like 'returns a valid json search response', status, 5, [1, 5, 5, 1, 1]
+ end
+
+ context 'with nil search term' do
+ let(:search_term) { nil }
+
+ it_behaves_like 'returns a valid json search response', status, 5, [1, 5, 5, 1, 1]
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects nuget access with invalid project id' do
+ context 'with a project id with invalid integers' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:project) { OpenStruct.new(id: id) }
+
+ where(:id, :status) do
+ '/../' | :unauthorized
+ '' | :not_found
+ '%20' | :unauthorized
+ '%2e%2e%2f' | :unauthorized
+ 'NaN' | :unauthorized
+ 00002345 | :unauthorized
+ 'anything25' | :unauthorized
+ end
+
+ with_them do
+ it_behaves_like 'rejects nuget packages access', :anonymous, params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects nuget access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { OpenStruct.new(id: 1234567890) }
+
+ context 'as anonymous' do
+ it_behaves_like 'rejects nuget packages access', :anonymous, :unauthorized
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
new file mode 100644
index 00000000000..ec15d7a4d2e
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'deploy token for package GET requests' do
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, 'bar') }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+ end
+end
+
+RSpec.shared_examples 'deploy token for package uploads' do
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, 'bar').merge(workhorse_header) }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
new file mode 100644
index 00000000000..a371d380f47
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
@@ -0,0 +1,185 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'rejects package tags access' do |user_type, status|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'returns package tags' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ stub_application_setting(npm_package_requests_forwarding: false)
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', :success
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to be_a(Hash)
+ end
+
+ it 'returns two package tags' do
+ subject
+
+ expect(json_response).to match_schema('public_api/v4/packages/npm_package_tags')
+ expect(json_response.length).to eq(3) # two tags + latest (auto added)
+ expect(json_response[package_tag1.name]).to eq(package.version)
+ expect(json_response[package_tag2.name]).to eq(package.version)
+ expect(json_response['latest']).to eq(package.version)
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ '%20' | :bad_request
+ nil | :forbidden
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'create package tag' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', :no_content
+
+ it 'creates the package tag' do
+ expect { subject }.to change { Packages::Tag.count }.by(1)
+
+ last_tag = Packages::Tag.last
+ expect(last_tag.name).to eq(tag_name)
+ expect(last_tag.package).to eq(package)
+ end
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+
+ context 'with already existing tag' do
+ let_it_be(:package2) { create(:npm_package, project: project, name: package.name, version: '5.5.55') }
+ let_it_be(:tag) { create(:packages_tag, package: package2, name: tag_name) }
+
+ it_behaves_like 'returning response status', :no_content
+
+ it 'reuses existing tag' do
+ expect(package.tags).to be_empty
+ expect(package2.tags).to eq([tag])
+ expect { subject }.to not_change { Packages::Tag.count }
+ expect(package.reload.tags).to eq([tag])
+ expect(package2.reload.tags).to be_empty
+ end
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ 'unknown' | :forbidden
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid tag name' do
+ where(:tag_name, :status) do
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid version' do
+ where(:version, :status) do
+ ' ' | :bad_request
+ '' | :bad_request
+ nil | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'delete package tag' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ context "for #{user_type} user" do
+ it_behaves_like 'returning response status', :no_content
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+
+ it 'destroy the package tag' do
+ expect(package.tags).to eq([package_tag])
+ expect { subject }.to change { Packages::Tag.count }.by(-1)
+ expect(package.reload.tags).to be_empty
+ end
+
+ context 'with tag from other package' do
+ let(:package2) { create(:npm_package, project: project) }
+ let(:package_tag) { create(:packages_tag, package: package2) }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ 'unknown' | :forbidden
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid tag name' do
+ where(:tag_name, :status) do
+ 'unknown' | :not_found
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+end
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
new file mode 100644
index 00000000000..fcc166ac87d
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'PyPi package creation' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'creating pypi package files' do
+ it 'creates package files' do
+ expect { subject }
+ .to change { project.packages.pypi.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and change { Packages::Pypi::Metadatum.count }.by(1)
+ expect(response).to have_gitlab_http_status(status)
+
+ package = project.reload.packages.pypi.last
+
+ expect(package.name).to eq params[:name]
+ expect(package.version).to eq params[:version]
+ expect(package.pypi_metadatum.required_python).to eq params[:requires_python]
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'creating pypi package files'
+
+ context 'with object storage disabled' do
+ before do
+ stub_package_file_object_storage(enabled: false)
+ end
+
+ context 'without a file from workhorse' do
+ let(:send_rewritten_field) { false }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ context 'with correct params' do
+ it_behaves_like 'package workhorse uploads'
+ it_behaves_like 'creating pypi package files'
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { base_params.merge(content: fog_file, 'content.remote_id' => file_name) }
+
+ context 'and direct upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'creating pypi package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) { base_params.merge(content: fog_file, 'content.remote_id' => remote_id) }
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+ end
+
+ context 'and direct upload disabled' do
+ context 'and background upload disabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: false)
+ end
+
+ it_behaves_like 'creating pypi package files'
+ end
+
+ context 'and background upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: true)
+ end
+
+ it_behaves_like 'creating pypi package files'
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+end
+
+RSpec.shared_examples 'PyPi package versions' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'returns the package listing' do
+ subject
+
+ expect(response.body).to match(package.package_files.first.file_name)
+ end
+
+ it_behaves_like 'returning response status', status
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'list_package'
+ end
+end
+
+RSpec.shared_examples 'PyPi package download' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'returns the package listing' do
+ subject
+
+ expect(response.body).to eq(File.open(package.package_files.first.file.path, "rb").read)
+ end
+
+ it_behaves_like 'returning response status', status
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+end
+
+RSpec.shared_examples 'process PyPi api request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'rejects PyPI access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { OpenStruct.new(id: 1234567890) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process PyPi api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process PyPi api request', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb b/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
index bca51dab353..d21a9f419fd 100644
--- a/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
@@ -16,6 +16,29 @@ RSpec.shared_examples 'resource_milestone_events API' do |parent_type, eventable
expect(json_response.first['action']).to eq(event.action)
end
+ context 'when there is an event with a milestone which is not visible for requesting user' do
+ let!(:private_project) { create(:project, :private) }
+ let!(:private_milestone) { create(:milestone, project: private_project) }
+
+ let!(:other_user) { create(:user) }
+
+ it 'returns the expected events' do
+ create_event(private_milestone)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events", other_user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq('1')
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+
+ expect(json_response.first['id']).to eq(event.id)
+ expect(json_response.first['milestone']['id']).to eq(event.milestone.id)
+ expect(json_response.first['action']).to eq(event.action)
+ end
+ end
+
it "returns a 404 error when eventable id not found" do
get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{non_existing_record_id}/resource_milestone_events", user)
@@ -60,6 +83,20 @@ RSpec.shared_examples 'resource_milestone_events API' do |parent_type, eventable
end
end
+ describe 'pagination' do
+ let!(:event1) { create_event(milestone) }
+ let!(:event2) { create_event(milestone) }
+
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/220192
+ it 'returns the second page' do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events?page=2&per_page=1", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['id']).to eq(event2.id)
+ end
+ end
+
def create_event(milestone, action: :add)
create(:resource_milestone_event, eventable.class.name.underscore => eventable, milestone: milestone, action: action)
end
diff --git a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
new file mode 100644
index 00000000000..cfbb84dd099
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'raw snippet files' do
+ let_it_be(:unauthorized_user) { create(:user) }
+ let(:snippet_id) { snippet.id }
+ let(:user) { snippet.author }
+ let(:file_path) { '%2Egitattributes' }
+ let(:ref) { 'master' }
+
+ context 'with no user' do
+ it 'requires authentication' do
+ get api(api_path)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ shared_examples 'not found' do
+ it 'returns 404' do
+ get api(api_path, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Snippet Not Found')
+ end
+ end
+
+ context 'when not authorized' do
+ let(:user) { unauthorized_user }
+
+ it_behaves_like 'not found'
+ end
+
+ context 'with an invalid snippet ID' do
+ let(:snippet_id) { 'invalid' }
+
+ it_behaves_like 'not found'
+ end
+
+ context 'with valid params' do
+ it 'returns the raw file info' do
+ expect(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original
+
+ get api(api_path, user)
+
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq 'text/plain'
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ expect(response.header['Content-Disposition']).to match 'filename=".gitattributes"'
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:file_path, :ref, :status, :key, :message) do
+ '%2Egitattributes' | 'invalid-ref' | :not_found | 'message' | '404 Reference Not Found'
+ '%2Egitattributes' | nil | :not_found | 'error' | '404 Not Found'
+ '%2Egitattributes' | '' | :not_found | 'error' | '404 Not Found'
+
+ 'doesnotexist.rb' | 'master' | :not_found | 'message' | '404 File Not Found'
+ '/does/not/exist.rb' | 'master' | :not_found | 'error' | '404 Not Found'
+ '%2E%2E%2Fetc%2Fpasswd' | 'master' | :bad_request | 'error' | 'file_path should be a valid file path'
+ '%2Fetc%2Fpasswd' | 'master' | :bad_request | 'error' | 'file_path should be a valid file path'
+ '../../etc/passwd' | 'master' | :not_found | 'error' | '404 Not Found'
+ end
+
+ with_them do
+ before do
+ get api(api_path, user)
+ end
+
+ it { expect(response).to have_gitlab_http_status(status) }
+ it { expect(json_response[key]).to eq(message) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/snippet_shared_examples.rb b/spec/support/shared_examples/requests/snippet_shared_examples.rb
index f830f957174..644abb191a6 100644
--- a/spec/support/shared_examples/requests/snippet_shared_examples.rb
+++ b/spec/support/shared_examples/requests/snippet_shared_examples.rb
@@ -74,18 +74,14 @@ RSpec.shared_examples 'update with repository actions' do
end
end
-RSpec.shared_examples 'snippet response without repository URLs' do
- it 'skip inclusion of repository URLs' do
- expect(json_response).not_to have_key('ssh_url_to_repo')
- expect(json_response).not_to have_key('http_url_to_repo')
- end
-end
-
RSpec.shared_examples 'snippet blob content' do
it 'returns content from repository' do
+ expect(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original
+
subject
- expect(response.body).to eq(snippet.blobs.first.data)
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
context 'when snippet repository is empty' do
@@ -98,3 +94,15 @@ RSpec.shared_examples 'snippet blob content' do
end
end
end
+
+RSpec.shared_examples 'snippet_multiple_files feature disabled' do
+ before do
+ stub_feature_flags(snippet_multiple_files: false)
+
+ subject
+ end
+
+ it 'does not return files attributes' do
+ expect(json_response).not_to have_key('files')
+ end
+end
diff --git a/spec/support/shared_examples/resource_events.rb b/spec/support/shared_examples/resource_events.rb
index 66f5e760c37..c0158f9b24b 100644
--- a/spec/support/shared_examples/resource_events.rb
+++ b/spec/support/shared_examples/resource_events.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'a resource event' do
+RSpec.shared_examples 'a resource event' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -54,7 +54,7 @@ shared_examples 'a resource event' do
end
end
-shared_examples 'a resource event for issues' do
+RSpec.shared_examples 'a resource event for issues' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -101,9 +101,19 @@ shared_examples 'a resource event for issues' do
expect(events).to be_empty
end
end
+
+ if described_class.method_defined?(:issuable)
+ describe '#issuable' do
+ let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue2) }
+
+ it 'returns the expected issuable' do
+ expect(event1.issuable).to eq(issue2)
+ end
+ end
+ end
end
-shared_examples 'a resource event for merge requests' do
+RSpec.shared_examples 'a resource event for merge requests' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -132,4 +142,14 @@ shared_examples 'a resource event for merge requests' do
expect(events).to be_empty
end
end
+
+ if described_class.method_defined?(:issuable)
+ describe '#issuable' do
+ let_it_be(:event1) { create(described_class.name.underscore.to_sym, merge_request: merge_request2) }
+
+ it 'returns the expected issuable' do
+ expect(event1.issuable).to eq(merge_request2)
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/routing/resource_routing_shared_examples.rb b/spec/support/shared_examples/routing/resource_routing_shared_examples.rb
new file mode 100644
index 00000000000..b98901a57ea
--- /dev/null
+++ b/spec/support/shared_examples/routing/resource_routing_shared_examples.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+# Shared examples for resource routes.
+#
+# By default it tests all the default REST actions: index, create, new, edit,
+# show, update, and destroy. You can remove actions by customizing the
+# `actions` variable.
+#
+# The subject is expected to be an instance of the controller under test.
+#
+# It also expects a `base_path` variable to be available which defines the
+# base path of the controller, and a `base_params` variable which
+# defines the route params the base path maps to.
+#
+# Examples
+#
+# # Default behavior
+# describe Projects::CommitsController, 'routing' do
+# it_behaves_like 'resource routing' do
+# let(:base_path) { '/gitlab/gitlabhq/-/commits' }
+# let(:base_params) { { namespace_id: 'gitlab', project_id: 'gitlabhq' } }
+# end
+# end
+#
+# # Customizing actions
+# it_behaves_like 'resource routing' do
+# let(:base_path) { '/gitlab/gitlabhq/-/commits' }
+#
+# # Specify default actions
+# let(:actions) { [:index] }
+#
+# # Add custom actions by passing a hash with action names
+# # as keys, and the HTTP method and path as values.
+# let(:additional_actions) do
+# {
+# preview_markdown: [:post, '/:id/preview_markdown'],
+# }
+# end
+# end
+RSpec.shared_examples 'resource routing' do
+ let(:controller) { described_class.controller_path }
+ let(:id) { '123' }
+
+ let(:default_actions) do
+ {
+ index: [:get, ''],
+ show: [:get, '/:id'],
+ new: [:get, '/new'],
+ create: [:post, ''],
+ edit: [:get, '/:id/edit'],
+ update: [:put, '/:id'],
+ destroy: [:delete, '/:id']
+ }
+ end
+
+ let(:actions) { default_actions.keys }
+ let(:additional_actions) { {} }
+
+ it 'routes resource actions', :aggregate_failures do
+ selected_actions = default_actions.slice(*actions).merge(additional_actions)
+
+ selected_actions.each do |action, (method, action_path)|
+ expected_params = base_params.merge(controller: controller.to_s, action: action.to_s)
+
+ if action_path.include?(':id')
+ action_path = action_path.sub(':id', id)
+ expected_params[:id] = id
+ end
+
+ expect(public_send(method, "#{base_path}#{action_path}")).to route_to(expected_params)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb b/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb
new file mode 100644
index 00000000000..9289934677e
--- /dev/null
+++ b/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'wiki routing' do
+ it_behaves_like 'resource routing' do
+ let(:id) { 'directory/page' }
+ let(:actions) { %i[show new create edit update destroy] }
+ let(:additional_actions) do
+ {
+ pages: [:get, '/pages'],
+ history: [:get, '/:id/history'],
+ git_access: [:get, '/git_access'],
+ preview_markdown: [:post, '/:id/preview_markdown']
+ }
+ end
+ end
+
+ it 'redirects the base path to the home page', type: :request do
+ expect(get(base_path)).to redirect_to("#{base_path}/home")
+ end
+end
diff --git a/spec/support/shared_examples/services/alert_management_shared_examples.rb b/spec/support/shared_examples/services/alert_management_shared_examples.rb
new file mode 100644
index 00000000000..a1354a8099b
--- /dev/null
+++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'creates an alert management alert' do
+ it { is_expected.to be_success }
+
+ it 'creates AlertManagement::Alert' do
+ expect { subject }.to change(AlertManagement::Alert, :count).by(1)
+ end
+
+ it 'executes the alert service hooks' do
+ slack_service = create(:service, type: 'SlackService', project: project, alert_events: true, active: true)
+
+ subject
+
+ expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash))
+ end
+end
+
+RSpec.shared_examples 'does not an create alert management alert' do
+ it 'does not create alert' do
+ expect { subject }.not_to change(AlertManagement::Alert, :count)
+ end
+end
+
+RSpec.shared_examples 'adds an alert management alert event' do
+ it { is_expected.to be_success }
+
+ it 'does not create an alert' do
+ expect { subject }.not_to change(AlertManagement::Alert, :count)
+ end
+
+ it 'increases alert events count' do
+ expect { subject }.to change { alert.reload.events }.by(1)
+ end
+
+ it 'does not executes the alert service hooks' do
+ expect(alert).not_to receive(:execute_services)
+
+ subject
+ end
+end
diff --git a/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb b/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb
new file mode 100644
index 00000000000..cbe20928f98
--- /dev/null
+++ b/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
+ let(:application_class) { Clusters::Cluster::APPLICATIONS[release_name] }
+ let(:cluster_application) { cluster.public_send("application_#{release_name}") }
+ let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
+ let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
+
+ context 'release is missing' do
+ let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_missing.json.gz" }
+
+ context 'application does not exist' do
+ it 'does not create or destroy an application' do
+ expect do
+ described_class.new(job, user).execute(artifact)
+ end.not_to change(application_class, :count)
+ end
+ end
+
+ context 'application exists' do
+ before do
+ create("clusters_applications_#{release_name}".to_sym, :installed, cluster: cluster)
+ end
+
+ it 'marks the application as uninstalled' do
+ described_class.new(job, user).execute(artifact)
+
+ cluster_application.reload
+ expect(cluster_application).to be_uninstalled
+ end
+ end
+ end
+
+ context 'release is deployed' do
+ let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_deployed.json.gz" }
+
+ context 'application does not exist' do
+ it 'creates an application and marks it as installed' do
+ expect do
+ described_class.new(job, user).execute(artifact)
+ end.to change(application_class, :count)
+
+ expect(cluster_application).to be_persisted
+ expect(cluster_application).to be_installed
+ end
+ end
+
+ context 'application exists' do
+ before do
+ create("clusters_applications_#{release_name}".to_sym, :errored, cluster: cluster)
+ end
+
+ it 'marks the application as installed' do
+ described_class.new(job, user).execute(artifact)
+
+ expect(cluster_application).to be_installed
+ end
+ end
+ end
+
+ context 'release is failed' do
+ let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_failed.json.gz" }
+
+ context 'application does not exist' do
+ it 'creates an application and marks it as errored' do
+ expect do
+ described_class.new(job, user).execute(artifact)
+ end.to change(application_class, :count)
+
+ expect(cluster_application).to be_persisted
+ expect(cluster_application).to be_errored
+ expect(cluster_application.status_reason).to eq('Helm release failed to install')
+ end
+ end
+
+ context 'application exists' do
+ before do
+ create("clusters_applications_#{release_name}".to_sym, :installed, cluster: cluster)
+ end
+
+ it 'marks the application as errored' do
+ described_class.new(job, user).execute(artifact)
+
+ expect(cluster_application).to be_errored
+ expect(cluster_application.status_reason).to eq('Helm release failed to install')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
index 4ce3e32d774..20856b05de6 100644
--- a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
+++ b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
@@ -17,10 +17,10 @@ RSpec.shared_examples 'system note creation' do |update_params, note_text|
end
end
-RSpec.shared_examples 'WIP notes creation' do |wip_action|
+RSpec.shared_examples 'draft notes creation' do |wip_action|
subject { described_class.new(project, user).execute(issuable, old_labels: []) }
- it 'creates WIP toggle and title change notes' do
+ it 'creates Draft toggle and title change notes' do
expect { subject }.to change { Note.count }.from(0).to(2)
expect(Note.first.note).to match("#{wip_action} as a **Work In Progress**")
diff --git a/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb b/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb
index c5e56ed3539..8fd76f7cb1f 100644
--- a/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'responds with error' do |message|
+RSpec.shared_examples 'responds with error' do |message|
it 'returns error' do
expect(subject).to be_a(ServiceResponse)
expect(subject).to be_error
diff --git a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
index 5dd1badbefc..c8fabfe30b9 100644
--- a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
+++ b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
@@ -29,14 +29,43 @@ RSpec.shared_examples 'valid dashboard service response' do
end
RSpec.shared_examples 'caches the unprocessed dashboard for subsequent calls' do
- it do
- expect(YAML).to receive(:safe_load).once.and_call_original
+ specify do
+ expect_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ expect(loader).to receive(:load_raw!).once.and_call_original
+ end
described_class.new(*service_params).get_dashboard
described_class.new(*service_params).get_dashboard
end
end
+# This spec is applicable for predefined/out-of-the-box dashboard services.
+RSpec.shared_examples 'refreshes cache when dashboard_version is changed' do
+ specify do
+ allow_next_instance_of(described_class) do |service|
+ allow(service).to receive(:dashboard_version).and_return('1', '2')
+ end
+
+ expect(File).to receive(:read).twice.and_call_original
+
+ service = described_class.new(*service_params)
+
+ service.get_dashboard
+ service.get_dashboard
+ end
+end
+
+# This spec is applicable for predefined/out-of-the-box dashboard services.
+# This shared_example requires the following variables to be defined:
+# dashboard_path: Relative path to the dashboard, ex: 'config/prometheus/common_metrics.yml'
+# dashboard_version: The version string used in the cache_key.
+RSpec.shared_examples 'dashboard_version contains SHA256 hash of dashboard file content' do
+ specify do
+ dashboard = File.read(Rails.root.join(dashboard_path))
+ expect(Digest::SHA256.hexdigest(dashboard)).to eq(dashboard_version)
+ end
+end
+
RSpec.shared_examples 'valid embedded dashboard service response' do
let(:dashboard_schema) { Gitlab::Json.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/embedded_dashboard.json')) }
@@ -128,3 +157,50 @@ RSpec.shared_examples 'updates gitlab_metrics_dashboard_processing_time_ms metri
expect(metric.get(labels)).to be > 0
end
end
+
+RSpec.shared_examples '#raw_dashboard raises error if dashboard loading fails' do
+ context 'when yaml is too large' do
+ before do
+ allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ allow(loader).to receive(:load_raw!)
+ .and_raise(Gitlab::Config::Loader::Yaml::DataTooLargeError, 'The parsed YAML is too big')
+ end
+ end
+
+ it 'raises error' do
+ expect { subject.raw_dashboard }.to raise_error(
+ Gitlab::Metrics::Dashboard::Errors::LayoutError,
+ 'The parsed YAML is too big'
+ )
+ end
+ end
+
+ context 'when yaml loader returns error' do
+ before do
+ allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ allow(loader).to receive(:load_raw!)
+ .and_raise(Gitlab::Config::Loader::FormatError, 'Invalid configuration format')
+ end
+ end
+
+ it 'raises error' do
+ expect { subject.raw_dashboard }.to raise_error(
+ Gitlab::Metrics::Dashboard::Errors::LayoutError,
+ 'Invalid yaml'
+ )
+ end
+ end
+
+ context 'when yaml is not a hash' do
+ before do
+ allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ allow(loader).to receive(:load_raw!)
+ .and_raise(Gitlab::Config::Loader::Yaml::NotHashError, 'Invalid configuration format')
+ end
+ end
+
+ it 'returns nil' do
+ expect(subject.raw_dashboard).to eq({})
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
new file mode 100644
index 00000000000..45a4c2bb151
--- /dev/null
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -0,0 +1,193 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'assigns build to package' do
+ context 'with build info' do
+ let(:job) { create(:ci_build, user: user) }
+ let(:params) { super().merge(build: job) }
+
+ it 'assigns the pipeline to the package' do
+ package = subject
+
+ expect(package.build_info).to be_present
+ expect(package.build_info.pipeline).to eq job.pipeline
+ end
+ end
+end
+
+RSpec.shared_examples 'returns packages' do |container_type, user_type|
+ context "for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it 'returns success response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+
+ it 'returns a valid response schema' do
+ subject
+
+ expect(response).to match_response_schema(package_schema)
+ end
+
+ it 'returns two packages' do
+ subject
+
+ expect(json_response.length).to eq(2)
+ expect(json_response.map { |package| package['id'] }).to contain_exactly(package1.id, package2.id)
+ end
+ end
+end
+
+RSpec.shared_examples 'returns packages with subgroups' do |container_type, user_type|
+ context "with subgroups for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it 'returns success response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+
+ it 'returns a valid response schema' do
+ subject
+
+ expect(response).to match_response_schema(package_schema)
+ end
+
+ it 'returns three packages' do
+ subject
+
+ expect(json_response.length).to eq(3)
+ expect(json_response.map { |package| package['id'] }).to contain_exactly(package1.id, package2.id, package3.id)
+ end
+ end
+end
+
+RSpec.shared_examples 'package sorting' do |order_by|
+ subject { get api(url), params: { sort: sort, order_by: order_by } }
+
+ context "sorting by #{order_by}" do
+ context 'ascending order' do
+ let(:sort) { 'asc' }
+
+ it 'returns the sorted packages' do
+ subject
+
+ expect(json_response.map { |package| package['id'] }).to eq(packages.map(&:id))
+ end
+ end
+
+ context 'descending order' do
+ let(:sort) { 'desc' }
+
+ it 'returns the sorted packages' do
+ subject
+
+ expect(json_response.map { |package| package['id'] }).to eq(packages.reverse.map(&:id))
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects packages access' do |container_type, user_type, status|
+ context "for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'returns paginated packages' do
+ let(:per_page) { 2 }
+
+ context 'when viewing the first page' do
+ let(:page) { 1 }
+
+ it 'returns first 2 packages' do
+ get api(url, user), params: { page: page, per_page: per_page }
+
+ expect_paginated_array_response([package1.id, package2.id])
+ end
+ end
+
+ context 'when viewing the second page' do
+ let(:page) { 2 }
+
+ it 'returns first 2 packages' do
+ get api(url, user), params: { page: page, per_page: per_page }
+
+ expect_paginated_array_response([package3.id, package4.id])
+ end
+ end
+end
+
+RSpec.shared_examples 'background upload schedules a file migration' do
+ context 'background upload enabled' do
+ before do
+ stub_package_file_object_storage(background_upload: true)
+ end
+
+ it 'schedules migration of file to object storage' do
+ expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('Packages::PackageFileUploader', 'Packages::PackageFile', :file, kind_of(Numeric))
+
+ subject
+ end
+ end
+end
+
+RSpec.shared_context 'package filter context' do
+ def package_filter_url(filter, param)
+ "/projects/#{project.id}/packages?package_#{filter}=#{param}"
+ end
+
+ def group_filter_url(filter, param)
+ "/groups/#{group.id}/packages?package_#{filter}=#{param}"
+ end
+end
+
+RSpec.shared_examples 'filters on each package_type' do |is_project: false|
+ include_context 'package filter context'
+
+ let_it_be(:package1) { create(:conan_package, project: project) }
+ let_it_be(:package2) { create(:maven_package, project: project) }
+ let_it_be(:package3) { create(:npm_package, project: project) }
+ let_it_be(:package4) { create(:nuget_package, project: project) }
+ let_it_be(:package5) { create(:pypi_package, project: project) }
+ let_it_be(:package6) { create(:composer_package, project: project) }
+
+ Packages::Package.package_types.keys.each do |package_type|
+ context "for package type #{package_type}" do
+ let(:url) { is_project ? package_filter_url(:type, package_type) : group_filter_url(:type, package_type) }
+
+ subject { get api(url, user) }
+
+ it "returns #{package_type} packages" do
+ subject
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.map { |package| package['package_type'] }).to contain_exactly(package_type)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'package workhorse uploads' do
+ context 'without a workhorse header' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'invalid header' }, Gitlab::Workhorse.secret, 'HS256') }
+
+ it_behaves_like 'returning response status', :forbidden
+
+ it 'logs an error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).once
+
+ subject
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
index 0e6ecf49cd0..2ddbdebdb97 100644
--- a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
@@ -25,19 +25,18 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- allow(project_repository_double).to receive(:create_repository)
- .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:create_repository)
- .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)
.and_return(repository_checksum)
+
+ expect(GitlabShellWorker).to receive(:perform_async).with(:mv_repository, 'default', anything, anything)
+ .twice.and_call_original
end
it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read only" do
@@ -48,6 +47,7 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
old_repository_path = repository.full_path
result = subject.execute
+ project.reload
expect(result).to be_success
expect(project).not_to be_repository_read_only
@@ -101,15 +101,11 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
it 'unmarks the repository as read-only without updating the repository storage' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- allow(project_repository_double).to receive(:create_repository)
- .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:create_repository)
- .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
.and_raise(Gitlab::Git::CommandError)
@@ -128,15 +124,11 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
it 'unmarks the repository as read-only without updating the repository storage' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- allow(project_repository_double).to receive(:create_repository)
- .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:create_repository)
- .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)
diff --git a/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb b/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb
index c5f84e205cf..ef41c2fcc13 100644
--- a/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'a milestone events creator' do
+RSpec.shared_examples 'a milestone events creator' do
let_it_be(:user) { create(:user) }
let(:created_at_time) { Time.utc(2019, 12, 30) }
diff --git a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb
index efcb83a34af..ebe78c299a5 100644
--- a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb
@@ -63,16 +63,6 @@ RSpec.shared_examples 'WikiPages::CreateService#execute' do |container_type|
include_examples 'correct event created'
end
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not record the activity' do
- expect { service.execute }.not_to change(Event, :count)
- end
- end
-
context 'when the options are bad' do
let(:page_title) { '' }
diff --git a/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb
index 1231c012c31..db1b50fdf3c 100644
--- a/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb
@@ -37,14 +37,4 @@ RSpec.shared_examples 'WikiPages::DestroyService#execute' do |container_type|
expect { service.execute(nil) }.not_to change { counter.read(:delete) }
end
-
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not record the activity' do
- expect { service.execute(page) }.not_to change(Event, :count)
- end
- end
end
diff --git a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb
index 77354fec069..0191a6dfbc9 100644
--- a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb
@@ -67,16 +67,6 @@ RSpec.shared_examples 'WikiPages::UpdateService#execute' do |container_type|
include_examples 'adds activity event'
end
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not record the activity' do
- expect { service.execute(page) }.not_to change(Event, :count)
- end
- end
-
context 'when the options are bad' do
let(:page_title) { '' }
diff --git a/spec/support/shared_examples/snippet_blob_shared_examples.rb b/spec/support/shared_examples/snippet_blob_shared_examples.rb
new file mode 100644
index 00000000000..ba97688d017
--- /dev/null
+++ b/spec/support/shared_examples/snippet_blob_shared_examples.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples 'snippet blob raw path' do
+ let(:blob) { snippet.blobs.first }
+ let(:ref) { blob.repository.root_ref }
+
+ context 'for PersonalSnippets' do
+ let(:snippet) { personal_snippet }
+
+ it 'returns the raw personal snippet blob path' do
+ expect(subject).to eq("/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}")
+ end
+ end
+
+ context 'for ProjectSnippets' do
+ let(:snippet) { project_snippet }
+
+ it 'returns the raw project snippet blob path' do
+ expect(subject).to eq("/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}")
+ end
+ end
+end
diff --git a/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb b/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb
index e58723324d3..9e0104e1410 100644
--- a/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb
@@ -2,7 +2,7 @@
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
-shared_examples 'denied carrierwave upload' do
+RSpec.shared_examples 'denied carrierwave upload' do
it 'will deny upload' do
fixture_file = fixture_file_upload(path)
expect { uploader.cache!(fixture_file) }.to raise_exception(CarrierWave::IntegrityError)
@@ -11,7 +11,7 @@ end
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
-shared_examples 'accepted carrierwave upload' do
+RSpec.shared_examples 'accepted carrierwave upload' do
let(:fixture_file) { fixture_file_upload(path) }
before do
@@ -30,7 +30,7 @@ end
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
# @param content_type [String] the upload file content type after cache
-shared_examples 'upload with content type' do |content_type|
+RSpec.shared_examples 'upload with content type' do |content_type|
let(:fixture_file) { fixture_file_upload(path, content_type) }
it 'will not change upload file content type' do
diff --git a/spec/support/shared_examples/views/pipeline_status_changes_email.rb b/spec/support/shared_examples/views/pipeline_status_changes_email.rb
index 15b4ce9c44e..698f11c2216 100644
--- a/spec/support/shared_examples/views/pipeline_status_changes_email.rb
+++ b/spec/support/shared_examples/views/pipeline_status_changes_email.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'pipeline status changes email' do
+RSpec.shared_examples 'pipeline status changes email' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user, developer_projects: [project]) }
diff --git a/spec/support/shared_examples/views/plain_text_email.rb b/spec/support/shared_examples/views/plain_text_email.rb
new file mode 100644
index 00000000000..23f9262b446
--- /dev/null
+++ b/spec/support/shared_examples/views/plain_text_email.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'renders plain text email correctly' do
+ it 'renders the email without HTML links' do
+ render
+
+ expect(rendered).to have_no_selector('a')
+ end
+end
diff --git a/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb b/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb
index ae8c82cb67c..2cca76c8fe3 100644
--- a/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb
+++ b/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'include import workers modules' do
+RSpec.shared_examples 'include import workers modules' do
it { expect(described_class).to include_module(ApplicationWorker) }
it { expect(described_class).to include_module(Gitlab::JiraImport::QueueOptions) }
@@ -12,7 +12,7 @@ shared_examples 'include import workers modules' do
end
end
-shared_examples 'does not advance to next stage' do
+RSpec.shared_examples 'does not advance to next stage' do
it 'does not advance to next stage' do
expect(Gitlab::JiraImport::AdvanceStageWorker).not_to receive(:perform_async)
@@ -20,7 +20,7 @@ shared_examples 'does not advance to next stage' do
end
end
-shared_examples 'cannot do Jira import' do
+RSpec.shared_examples 'cannot do Jira import' do
it 'does not advance to next stage' do
worker = described_class.new
expect(worker).not_to receive(:import)
@@ -29,7 +29,7 @@ shared_examples 'cannot do Jira import' do
end
end
-shared_examples 'advance to next stage' do |next_stage|
+RSpec.shared_examples 'advance to next stage' do |next_stage|
let(:job_waiter) { Gitlab::JobWaiter.new(2, 'some-job-key') }
it "advances to #{next_stage} stage" do