summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/capybara.rb6
-rw-r--r--spec/support/gitlab_experiment.rb10
-rw-r--r--spec/support/helpers/ci/template_helpers.rb11
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb10
-rw-r--r--spec/support/helpers/database/table_schema_helpers.rb20
-rw-r--r--spec/support/helpers/feature_flag_helpers.rb6
-rw-r--r--spec/support/helpers/features/admin_users_helpers.rb25
-rw-r--r--spec/support/helpers/features/invite_members_modal_helper.rb2
-rw-r--r--spec/support/helpers/features/snippet_helpers.rb11
-rw-r--r--spec/support/helpers/features/source_editor_spec_helpers.rb (renamed from spec/support/helpers/features/editor_lite_spec_helpers.rb)4
-rw-r--r--spec/support/helpers/features/top_nav_spec_helpers.rb2
-rw-r--r--spec/support/helpers/grafana_api_helpers.rb2
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb14
-rw-r--r--spec/support/helpers/jira_service_helper.rb4
-rw-r--r--spec/support/helpers/live_debugger.rb12
-rw-r--r--spec/support/helpers/merge_request_diff_helpers.rb4
-rw-r--r--spec/support/helpers/require_migration.rb4
-rw-r--r--spec/support/helpers/services_helper.rb11
-rw-r--r--spec/support/helpers/stub_experiments.rb2
-rw-r--r--spec/support/helpers/stub_spam_services.rb23
-rw-r--r--spec/support/helpers/stubbed_feature.rb22
-rw-r--r--spec/support/helpers/test_env.rb86
-rw-r--r--spec/support/matchers/be_executed.rb11
-rw-r--r--spec/support/matchers/have_issuable_counts.rb2
-rw-r--r--spec/support/matchers/usage_metric_matchers.rb21
-rw-r--r--spec/support/omniauth_strategy.rb12
-rw-r--r--spec/support/redis/redis_helpers.rb8
-rw-r--r--spec/support/services/issuable_import_csv_service_shared_examples.rb20
-rw-r--r--spec/support/shared_contexts/features/integrations/integrations_shared_context.rb62
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb165
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb4
-rw-r--r--spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb4
-rw-r--r--spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb43
-rw-r--r--spec/support/shared_contexts/unique_ip_check_shared_context.rb6
-rw-r--r--spec/support/shared_examples/ci/edit_job_token_scope_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/features/cascading_settings_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/features/packages_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/features/sidebar_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/features/wiki/user_deletes_wiki_page_shared_examples.rb26
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/graphql/design_fields_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb12
-rw-r--r--spec/support/shared_examples/graphql/spam_protection_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/cache_helpers_shared_examples.rb101
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/lib/gitlab/import_export/relation_factory_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/lib/gitlab/search_results_sorted_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb97
-rw-r--r--spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb256
-rw-r--r--spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb54
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb95
-rw-r--r--spec/support/shared_examples/namespaces/traversal_examples.rb96
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb229
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb136
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb25
-rw-r--r--spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/services/service_ping/service_ping_payload_with_all_expected_metrics_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/services/service_ping/service_ping_payload_without_restricted_metrics_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/services/snippets_shared_examples.rb74
-rw-r--r--spec/support/shared_examples/services/wikis/create_attachment_service_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/workers/in_product_marketing_email_shared_example.rb15
-rw-r--r--spec/support/sidekiq.rb21
84 files changed, 1780 insertions, 526 deletions
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index e48a7b322ac..6f96d552da6 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -60,8 +60,8 @@ Capybara.register_driver :chrome do |app|
# Chrome won't work properly in a Docker container in sandbox mode
options.add_argument("no-sandbox")
- # Run headless by default unless CHROME_HEADLESS specified
- options.add_argument("headless") unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
+ # Run headless by default unless WEBDRIVER_HEADLESS specified
+ options.add_argument("headless") unless ENV['WEBDRIVER_HEADLESS'] =~ /^(false|no|0)$/i || ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
# Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab/issues/4252
options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER']
@@ -197,7 +197,7 @@ RSpec.configure do |config|
raise JSConsoleError, message
end
rescue Selenium::WebDriver::Error::WebDriverError => error
- if error.message =~ /unknown command: session\/[0-9a-zA-Z]+(?:\/se)?\/log/
+ if error.message =~ %r{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
diff --git a/spec/support/gitlab_experiment.rb b/spec/support/gitlab_experiment.rb
index b84adf82d29..3d099dc689c 100644
--- a/spec/support/gitlab_experiment.rb
+++ b/spec/support/gitlab_experiment.rb
@@ -4,16 +4,6 @@
require 'gitlab/experiment/rspec'
require_relative 'stub_snowplow'
-# This is a temporary fix until we have a larger discussion around the
-# challenges raised in https://gitlab.com/gitlab-org/gitlab/-/issues/300104
-require Rails.root.join('app', 'experiments', 'application_experiment')
-class ApplicationExperiment # rubocop:disable Gitlab/NamespacedClass
- def initialize(...)
- super(...)
- Feature.persist_used!(feature_flag_name)
- end
-end
-
RSpec.configure do |config|
config.include StubSnowplow, :experiment
diff --git a/spec/support/helpers/ci/template_helpers.rb b/spec/support/helpers/ci/template_helpers.rb
new file mode 100644
index 00000000000..7bab58a574e
--- /dev/null
+++ b/spec/support/helpers/ci/template_helpers.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Ci
+ module TemplateHelpers
+ def secure_analyzers_prefix
+ 'registry.gitlab.com/gitlab-org/security-products/analyzers'
+ end
+ end
+end
+
+Ci::TemplateHelpers.prepend_mod
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index 4515b96c79e..e48c8125d84 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -12,9 +12,7 @@ module CycleAnalyticsHelpers
page.all('.gl-path-button').collect(&:text).map {|name_with_median| name_with_median.split("\n")[0] }
end
- def add_custom_stage_to_form
- page.find_button(s_('CreateValueStreamForm|Add another stage')).click
-
+ def fill_in_custom_stage_fields
index = page.all('[data-testid="value-stream-stage-fields"]').length
last_stage = page.all('[data-testid="value-stream-stage-fields"]').last
@@ -25,6 +23,12 @@ module CycleAnalyticsHelpers
end
end
+ def add_custom_stage_to_form
+ page.find_button(s_('CreateValueStreamForm|Add another stage')).click
+
+ fill_in_custom_stage_fields
+ end
+
def save_value_stream(custom_value_stream_name)
fill_in 'create-value-stream-name', with: custom_value_stream_name
diff --git a/spec/support/helpers/database/table_schema_helpers.rb b/spec/support/helpers/database/table_schema_helpers.rb
index 48d33442110..472eaa45b4b 100644
--- a/spec/support/helpers/database/table_schema_helpers.rb
+++ b/spec/support/helpers/database/table_schema_helpers.rb
@@ -43,6 +43,14 @@ module Database
expect(index_exists_by_name(name, schema: schema)).to be_nil
end
+ def expect_foreign_key_to_exist(table_name, name, schema: nil)
+ expect(foreign_key_exists_by_name(table_name, name, schema: schema)).to eq(true)
+ end
+
+ def expect_foreign_key_not_to_exist(table_name, name, schema: nil)
+ expect(foreign_key_exists_by_name(table_name, name, schema: schema)).to be_nil
+ end
+
def expect_check_constraint(table_name, name, definition, schema: nil)
expect(check_constraint_definition(table_name, name, schema: schema)).to eq("CHECK ((#{definition}))")
end
@@ -133,6 +141,18 @@ module Database
SQL
end
+ def foreign_key_exists_by_name(table_name, foreign_key_name, schema: nil)
+ table_name = schema ? "#{schema}.#{table_name}" : table_name
+
+ connection.select_value(<<~SQL)
+ SELECT true
+ FROM pg_catalog.pg_constraint
+ WHERE pg_constraint.conrelid = '#{table_name}'::regclass
+ AND pg_constraint.contype = 'f'
+ AND pg_constraint.conname = '#{foreign_key_name}'
+ SQL
+ end
+
def check_constraint_definition(table_name, constraint_name, schema: nil)
table_name = schema ? "#{schema}.#{table_name}" : table_name
diff --git a/spec/support/helpers/feature_flag_helpers.rb b/spec/support/helpers/feature_flag_helpers.rb
index af7a674f3bc..51ba9039b70 100644
--- a/spec/support/helpers/feature_flag_helpers.rb
+++ b/spec/support/helpers/feature_flag_helpers.rb
@@ -14,6 +14,12 @@ module FeatureFlagHelpers
strategies: strategies)
end
+ def create_strategy(feature_flag, name = 'default', parameters = {})
+ create(:operations_strategy,
+ feature_flag: feature_flag,
+ name: name)
+ end
+
def within_feature_flag_row(index)
within ".gl-responsive-table-row:nth-child(#{index + 1})" do
yield
diff --git a/spec/support/helpers/features/admin_users_helpers.rb b/spec/support/helpers/features/admin_users_helpers.rb
new file mode 100644
index 00000000000..99b19eedcff
--- /dev/null
+++ b/spec/support/helpers/features/admin_users_helpers.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Spec
+ module Support
+ module Helpers
+ module Features
+ module AdminUsersHelpers
+ def click_user_dropdown_toggle(user_id)
+ page.within("[data-testid='user-actions-#{user_id}']") do
+ find("[data-testid='dropdown-toggle']").click
+ end
+ end
+
+ def click_action_in_user_dropdown(user_id, action)
+ click_user_dropdown_toggle(user_id)
+
+ within find("[data-testid='user-actions-#{user_id}']") do
+ find('li button', exact_text: action).click
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/features/invite_members_modal_helper.rb b/spec/support/helpers/features/invite_members_modal_helper.rb
index 1127c817656..7b8cd6963c0 100644
--- a/spec/support/helpers/features/invite_members_modal_helper.rb
+++ b/spec/support/helpers/features/invite_members_modal_helper.rb
@@ -9,7 +9,7 @@ module Spec
click_on 'Invite members'
page.within '#invite-members-modal' do
- fill_in 'Select members or type email addresses', with: name
+ find('[data-testid="members-token-select-input"]').set(name)
wait_for_requests
click_button name
diff --git a/spec/support/helpers/features/snippet_helpers.rb b/spec/support/helpers/features/snippet_helpers.rb
index c26849a9680..dc718b1b212 100644
--- a/spec/support/helpers/features/snippet_helpers.rb
+++ b/spec/support/helpers/features/snippet_helpers.rb
@@ -1,14 +1,17 @@
# frozen_string_literal: true
-# These helpers help you interact within the Editor Lite (single-file editor, snippets, etc.).
+# These helpers help you interact within the Source Editor (single-file editor, snippets, etc.).
#
+
+require Rails.root.join("spec/support/helpers/features/source_editor_spec_helpers.rb")
+
module Spec
module Support
module Helpers
module Features
module SnippetSpecHelpers
include ActionView::Helpers::JavaScriptHelper
- include Spec::Support::Helpers::Features::EditorLiteSpecHelpers
+ include Spec::Support::Helpers::Features::SourceEditorSpecHelpers
def snippet_description_locator
'snippet-description'
@@ -31,7 +34,7 @@ module Spec
end
def snippet_get_first_blob_value
- page.find('.gl-editor-lite', match: :first)
+ page.find('.gl-source-editor', match: :first)
end
def snippet_description_value
@@ -53,7 +56,7 @@ module Spec
end
def snippet_fill_in_content(value)
- page.within('.gl-editor-lite') do
+ page.within('.gl-source-editor') do
el = find('.inputarea')
el.send_keys value
end
diff --git a/spec/support/helpers/features/editor_lite_spec_helpers.rb b/spec/support/helpers/features/source_editor_spec_helpers.rb
index 0a67e753379..57057b47fbb 100644
--- a/spec/support/helpers/features/editor_lite_spec_helpers.rb
+++ b/spec/support/helpers/features/source_editor_spec_helpers.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-# These helpers help you interact within the Editor Lite (single-file editor, snippets, etc.).
+# These helpers help you interact within the Source Editor (single-file editor, snippets, etc.).
#
module Spec
module Support
module Helpers
module Features
- module EditorLiteSpecHelpers
+ module SourceEditorSpecHelpers
include ActionView::Helpers::JavaScriptHelper
def editor_set_value(value)
diff --git a/spec/support/helpers/features/top_nav_spec_helpers.rb b/spec/support/helpers/features/top_nav_spec_helpers.rb
index ab664ce4283..87ed897ec74 100644
--- a/spec/support/helpers/features/top_nav_spec_helpers.rb
+++ b/spec/support/helpers/features/top_nav_spec_helpers.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# These helpers help you interact within the Editor Lite (single-file editor, snippets, etc.).
+# These helpers help you interact within the Source Editor (single-file editor, snippets, etc.).
#
module Spec
module Support
diff --git a/spec/support/helpers/grafana_api_helpers.rb b/spec/support/helpers/grafana_api_helpers.rb
index e47b1a808f2..7a7b6fec5b4 100644
--- a/spec/support/helpers/grafana_api_helpers.rb
+++ b/spec/support/helpers/grafana_api_helpers.rb
@@ -31,7 +31,7 @@ module GrafanaApiHelpers
end
def stub_all_grafana_proxy_requests(base_url)
- stub_request(:any, /#{base_url}\/api\/datasources\/proxy/)
+ stub_request(:any, %r{#{base_url}/api/datasources/proxy})
.to_return(
status: 200,
body: fixture_file('grafana/proxy_response.json'),
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index 8fd8a548011..4c90b907d2d 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -43,12 +43,14 @@ module JavaScriptFixturesHelpers
# Public: Reads a GraphQL query from the filesystem as a string
#
# query_path - file path to the GraphQL query, relative to `app/assets/javascripts`
- # fragment_paths - an optional array of file paths to any fragments the query uses,
- # also relative to `app/assets/javascripts`
- def get_graphql_query_as_string(query_path, fragment_paths = [])
- [query_path, *fragment_paths].map do |path|
- File.read(File.join(Rails.root, '/app/assets/javascripts', path))
- end.join("\n")
+ def get_graphql_query_as_string(query_path)
+ path = Rails.root / 'app/assets/javascripts' / query_path
+ queries = Gitlab::Graphql::Queries.find(path)
+ if queries.length == 1
+ queries.first.text(mode: Gitlab.ee? ? :ee : :ce )
+ else
+ raise "Could not find query file at #{path}, please check your query_path" % path
+ end
end
private
diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb
index ce908d53f88..3cfd0de06e8 100644
--- a/spec/support/helpers/jira_service_helper.rb
+++ b/spec/support/helpers/jira_service_helper.rb
@@ -4,7 +4,7 @@ module JiraServiceHelper
JIRA_URL = "http://jira.example.net"
JIRA_API = JIRA_URL + "/rest/api/2"
- def jira_service_settings
+ def jira_integration_settings
url = JIRA_URL
username = 'jira-user'
password = 'my-secret-password'
@@ -77,7 +77,7 @@ module JiraServiceHelper
JIRA_API + "/issue/#{issue_id}"
end
- def stub_jira_service_test
+ def stub_jira_integration_test
WebMock.stub_request(:get, /serverInfo/).to_return(body: { url: 'http://url' }.to_json)
end
diff --git a/spec/support/helpers/live_debugger.rb b/spec/support/helpers/live_debugger.rb
index cdb068760f4..f4199d518a3 100644
--- a/spec/support/helpers/live_debugger.rb
+++ b/spec/support/helpers/live_debugger.rb
@@ -7,8 +7,8 @@ module LiveDebugger
puts
puts "Current example is paused for live debugging."
- if ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
- puts "Switch to the Chrome window that was automatically opened to run the test in order to view current page"
+ if is_headless_disabled?
+ puts "Switch to the browser window that was automatically opened to run the test in order to view current page"
else
puts "Opening #{current_url} in your default browser..."
end
@@ -16,10 +16,16 @@ module LiveDebugger
puts "The current user credentials are: #{@current_user.username} / #{@current_user.password}" if @current_user
puts "Press any key to resume the execution of the example!!"
- `open #{current_url}` if ENV['CHROME_HEADLESS'] !~ /^(false|no|0)$/i
+ `open #{current_url}` if is_headless_disabled?
loop until $stdin.getch
puts "Back to the example!"
end
+
+ def is_headless_disabled?
+ ActiveSupport::Deprecation.warn("CHROME_HEADLESS is deprecated. Use WEBDRIVER_HEADLESS instead.") if ENV.key?('CHROME_HEADLESS')
+
+ ENV['WEBDRIVER_HEADLESS'] =~ /^(false|no|0)$/i || ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
+ end
end
diff --git a/spec/support/helpers/merge_request_diff_helpers.rb b/spec/support/helpers/merge_request_diff_helpers.rb
index 49beecc6d4b..30afde7efed 100644
--- a/spec/support/helpers/merge_request_diff_helpers.rb
+++ b/spec/support/helpers/merge_request_diff_helpers.rb
@@ -3,8 +3,8 @@
module MergeRequestDiffHelpers
def click_diff_line(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side)
- line[:content].hover
- line[:num].find('.js-add-diff-note-button', visible: false).send_keys(:return)
+ line_holder.hover
+ line[:num].find('.js-add-diff-note-button').click
end
def get_line_components(line_holder, diff_side = nil)
diff --git a/spec/support/helpers/require_migration.rb b/spec/support/helpers/require_migration.rb
index 8de71d3073f..de3a8a81ab5 100644
--- a/spec/support/helpers/require_migration.rb
+++ b/spec/support/helpers/require_migration.rb
@@ -15,7 +15,7 @@ class RequireMigration
end
MIGRATION_FOLDERS = %w[db/migrate db/post_migrate].freeze
- SPEC_FILE_PATTERN = /.+\/(?<file_name>.+)_spec\.rb/.freeze
+ SPEC_FILE_PATTERN = %r{.+/(?<file_name>.+)_spec\.rb}.freeze
class << self
def require_migration!(file_name)
@@ -29,7 +29,7 @@ class RequireMigration
migration_folders.flat_map do |path|
migration_path = Rails.root.join(path).to_s
- Find.find(migration_path).grep(/\d+_#{file_name}\.rb/)
+ Find.find(migration_path).select { |m| File.basename(m).match? /\A\d+_#{file_name}\.rb\z/ }
end
end
diff --git a/spec/support/helpers/services_helper.rb b/spec/support/helpers/services_helper.rb
deleted file mode 100644
index bf007815551..00000000000
--- a/spec/support/helpers/services_helper.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-require_relative './after_next_helpers'
-
-module ServicesHelper
- include AfterNextHelpers
-
- def expect_execution_of(service_class, *args)
- expect_next(service_class, *args).to receive(:execute)
- end
-end
diff --git a/spec/support/helpers/stub_experiments.rb b/spec/support/helpers/stub_experiments.rb
index 408d16a7c08..8995b8f5f7b 100644
--- a/spec/support/helpers/stub_experiments.rb
+++ b/spec/support/helpers/stub_experiments.rb
@@ -11,7 +11,6 @@ module StubExperiments
allow(Gitlab::Experimentation).to receive(:active?).and_call_original
experiments.each do |experiment_key, enabled|
- Feature.persist_used!("#{experiment_key}#{feature_flag_suffix}")
allow(Gitlab::Experimentation).to receive(:active?).with(experiment_key) { enabled }
end
end
@@ -26,7 +25,6 @@ module StubExperiments
allow(Gitlab::Experimentation).to receive(:in_experiment_group?).and_call_original
experiments.each do |experiment_key, enabled|
- Feature.persist_used!("#{experiment_key}#{feature_flag_suffix}")
allow(Gitlab::Experimentation).to receive(:in_experiment_group?).with(experiment_key, anything) { enabled }
end
end
diff --git a/spec/support/helpers/stub_spam_services.rb b/spec/support/helpers/stub_spam_services.rb
new file mode 100644
index 00000000000..841e8366845
--- /dev/null
+++ b/spec/support/helpers/stub_spam_services.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module StubSpamServices
+ def stub_spam_services
+ allow(::Spam::SpamParams).to receive(:new_from_request) do
+ ::Spam::SpamParams.new(
+ captcha_response: double(:captcha_response),
+ spam_log_id: double(:spam_log_id),
+ ip_address: double(:ip_address),
+ user_agent: double(:user_agent),
+ referer: double(:referer)
+ )
+ end
+
+ allow_next_instance_of(::Spam::SpamActionService) do |service|
+ allow(service).to receive(:execute)
+ end
+
+ allow_next_instance_of(::UserAgentDetailService) do |service|
+ allow(service).to receive(:create)
+ end
+ end
+end
diff --git a/spec/support/helpers/stubbed_feature.rb b/spec/support/helpers/stubbed_feature.rb
index 67ceb7d9b35..4113a28182b 100644
--- a/spec/support/helpers/stubbed_feature.rb
+++ b/spec/support/helpers/stubbed_feature.rb
@@ -4,14 +4,6 @@
module StubbedFeature
extend ActiveSupport::Concern
- prepended do
- cattr_reader(:persist_used) do
- # persist feature flags in CI
- # nil: indicates that we do not want to persist used feature flags
- Gitlab::Utils.to_boolean(ENV['CI']) ? {} : nil
- end
- end
-
class_methods do
# Turn stubbed feature flags on or off.
def stub=(stub)
@@ -41,8 +33,6 @@ module StubbedFeature
feature_flag = super
return feature_flag unless stub?
- persist_used!(args.first)
-
# If feature flag is not persisted we mark the feature flag as enabled
# We do `m.call` as we want to validate the execution of method arguments
# and a feature flag state if it is not persisted
@@ -52,17 +42,5 @@ module StubbedFeature
feature_flag
end
-
- # This method creates a temporary file in `tmp/feature_flags`
- # if feature flag was touched during execution
- def persist_used!(name)
- return unless persist_used
- return if persist_used[name]
-
- persist_used[name] = true
- FileUtils.touch(
- Rails.root.join('tmp', 'feature_flags', name.to_s + ".used")
- )
- end
end
end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 40a3dbfbf25..8814d260fb3 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
+require 'parallel'
+
module TestEnv
- extend ActiveSupport::Concern
extend self
ComponentFailedToInstallError = Class.new(StandardError)
@@ -94,50 +95,40 @@ module TestEnv
TMP_TEST_PATH = Rails.root.join('tmp', 'tests').freeze
REPOS_STORAGE = 'default'
SECOND_STORAGE_PATH = Rails.root.join('tmp', 'tests', 'second_storage')
+ SETUP_METHODS = %i[setup_gitaly setup_gitlab_shell setup_workhorse setup_factory_repo setup_forked_repo].freeze
+
+ # Can be overriden
+ def setup_methods
+ SETUP_METHODS
+ end
# Test environment
#
# See gitlab.yml.example test section for paths
#
- def init(opts = {})
+ def init
unless Rails.env.test?
puts "\nTestEnv.init can only be run if `RAILS_ENV` is set to 'test' not '#{Rails.env}'!\n"
exit 1
end
+ start = Time.now
# Disable mailer for spinach tests
- disable_mailer if opts[:mailer] == false
-
clean_test_path
- setup_gitlab_shell
-
- setup_gitaly
-
- # Feature specs are run through Workhorse
- setup_workhorse
-
- # Create repository for FactoryBot.create(:project)
- setup_factory_repo
-
- # Create repository for FactoryBot.create(:forked_project_with_submodules)
- setup_forked_repo
- end
-
- included do |config|
- config.append_before do
- set_current_example_group
+ # Install components in parallel as most of the setup is I/O.
+ Parallel.each(setup_methods) do |method|
+ public_send(method)
end
- end
- def disable_mailer
- allow_any_instance_of(NotificationService).to receive(:mailer)
- .and_return(double.as_null_object)
+ post_init
+
+ puts "\nTest environment set up in #{Time.now - start} seconds"
end
- def enable_mailer
- allow_any_instance_of(NotificationService).to receive(:mailer)
- .and_call_original
+ # Can be overriden
+ def post_init
+ start_gitaly(gitaly_dir)
end
# Clean /tmp/tests
@@ -164,12 +155,11 @@ module TestEnv
end
def setup_gitaly
- install_gitaly_args = [gitaly_dir, repos_path, gitaly_url].compact.join(',')
-
component_timed_setup('Gitaly',
install_dir: gitaly_dir,
version: Gitlab::GitalyClient.expected_server_version,
- task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
+ task: "gitlab:gitaly:install",
+ task_args: [gitaly_dir, repos_path, gitaly_url].compact) do
Gitlab::SetupHelper::Gitaly.create_configuration(
gitaly_dir,
{ 'default' => repos_path },
@@ -190,8 +180,6 @@ module TestEnv
)
Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true)
end
-
- start_gitaly(gitaly_dir)
end
def gitaly_socket_path
@@ -273,19 +261,18 @@ module TestEnv
raise "could not connect to #{service} at #{socket.inspect} after #{sleep_time} seconds"
end
+ # Feature specs are run through Workhorse
def setup_workhorse
start = Time.now
return if skip_compile_workhorse?
- puts "\n==> Setting up GitLab Workhorse..."
-
FileUtils.rm_rf(workhorse_dir)
Gitlab::SetupHelper::Workhorse.compile_into(workhorse_dir)
Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil)
File.write(workhorse_tree_file, workhorse_tree) if workhorse_source_clean?
- puts " GitLab Workhorse set up in #{Time.now - start} seconds...\n"
+ puts "==> GitLab Workhorse set up in #{Time.now - start} seconds...\n"
end
def skip_compile_workhorse?
@@ -349,10 +336,12 @@ module TestEnv
ENV.fetch('GITLAB_WORKHORSE_URL', nil)
end
+ # Create repository for FactoryBot.create(:project)
def setup_factory_repo
setup_repo(factory_repo_path, factory_repo_path_bare, factory_repo_name, BRANCH_SHA)
end
+ # Create repository for FactoryBot.create(:forked_project_with_submodules)
# This repo has a submodule commit that is not present in the main test
# repository.
def setup_forked_repo
@@ -363,20 +352,18 @@ module TestEnv
clone_url = "https://gitlab.com/gitlab-org/#{repo_name}.git"
unless File.directory?(repo_path)
- puts "\n==> Setting up #{repo_name} repository in #{repo_path}..."
start = Time.now
system(*%W(#{Gitlab.config.git.bin_path} clone --quiet -- #{clone_url} #{repo_path}))
- puts " #{repo_path} set up in #{Time.now - start} seconds...\n"
+ puts "==> #{repo_path} set up in #{Time.now - start} seconds...\n"
end
set_repo_refs(repo_path, refs)
unless File.directory?(repo_path_bare)
- puts "\n==> Setting up #{repo_name} bare repository in #{repo_path_bare}..."
start = Time.now
# We must copy bare repositories because we will push to them.
system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --quiet --bare -- #{repo_path} #{repo_path_bare}))
- puts " #{repo_path_bare} set up in #{Time.now - start} seconds...\n"
+ puts "==> #{repo_path_bare} set up in #{Time.now - start} seconds...\n"
end
end
@@ -468,10 +455,6 @@ module TestEnv
private
- def set_current_example_group
- Thread.current[:current_example_group] = ::RSpec.current_example.metadata[:example_group]
- end
-
# These are directories that should be preserved at cleanup time
def test_dirs
@test_dirs ||= %w[
@@ -526,7 +509,7 @@ module TestEnv
end
end
- def component_timed_setup(component, install_dir:, version:, task:)
+ def component_timed_setup(component, install_dir:, version:, task:, task_args: [])
start = Time.now
ensure_component_dir_name_is_correct!(component, install_dir)
@@ -535,17 +518,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
+ if ENV['SKIP_RAILS_ENV_IN_RAKE']
+ # When we run `scripts/setup-test-env`, we take care of loading the necessary dependencies
+ # so we can run the rake task programmatically.
+ Rake::Task[task].invoke(*task_args)
+ else
+ # In other cases, we run the task via `rake` so that the environment
+ # and dependencies are automatically loaded.
+ raise ComponentFailedToInstallError unless system('rake', "#{task}[#{task_args.join(',')}]")
end
yield if block_given?
- puts " #{component} set up in #{Time.now - start} seconds...\n"
+ puts "==> #{component} set up in #{Time.now - start} seconds...\n"
end
rescue ComponentFailedToInstallError
puts "\n#{component} failed to install, cleaning up #{install_dir}!\n"
diff --git a/spec/support/matchers/be_executed.rb b/spec/support/matchers/be_executed.rb
new file mode 100644
index 00000000000..fea86386755
--- /dev/null
+++ b/spec/support/matchers/be_executed.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+# named as `get_executed` to avoid clashing
+# with `be_executed === have_attributes(executed: true)`
+RSpec::Matchers.define :get_executed do |args = []|
+ include AfterNextHelpers
+
+ match do |service_class|
+ expect_next(service_class, *args).to receive(:execute)
+ end
+end
diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb
index 049cfc022fb..586ba0651dc 100644
--- a/spec/support/matchers/have_issuable_counts.rb
+++ b/spec/support/matchers/have_issuable_counts.rb
@@ -6,7 +6,7 @@ RSpec::Matchers.define :have_issuable_counts do |opts|
end
match do |actual|
- actual.within '.issues-state-filters' do
+ actual.within '.top-area' do
expected_counts.each do |expected_count|
expect(actual).to have_content(expected_count)
end
diff --git a/spec/support/matchers/usage_metric_matchers.rb b/spec/support/matchers/usage_metric_matchers.rb
new file mode 100644
index 00000000000..83433334e8b
--- /dev/null
+++ b/spec/support/matchers/usage_metric_matchers.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :have_usage_metric do |key_path|
+ match do |payload|
+ payload = payload.deep_stringify_keys
+
+ key_path.split('.').each do |part|
+ break false unless payload&.has_key?(part)
+
+ payload = payload[part]
+ end
+ end
+
+ failure_message do
+ "Payload does not contain metric with key path: '#{key_path}'"
+ end
+
+ failure_message_when_negated do
+ "Payload contains restricted metric with key path: '#{key_path}'"
+ end
+end
diff --git a/spec/support/omniauth_strategy.rb b/spec/support/omniauth_strategy.rb
index 23907b8e450..5d5ee7dc1db 100644
--- a/spec/support/omniauth_strategy.rb
+++ b/spec/support/omniauth_strategy.rb
@@ -6,12 +6,6 @@ module StrategyHelpers
include Shoulda::Matchers::ActionController
include OmniAuth::Test::StrategyTestCase
- def post(*args)
- super.tap do
- @response = ActionDispatch::TestResponse.from_response(last_response)
- end
- end
-
def auth_hash
last_request.env['omniauth.auth']
end
@@ -21,7 +15,9 @@ module StrategyHelpers
original_on_failure = OmniAuth.config.on_failure
OmniAuth.config.test_mode = false
- OmniAuth.config.on_failure = OmniAuth::FailureEndpoint
+ OmniAuth.config.on_failure = proc do |env|
+ OmniAuth::FailureEndpoint.new(env).redirect_to_failure
+ end
yield
ensure
@@ -33,7 +29,7 @@ end
RSpec.configure do |config|
config.include StrategyHelpers, type: :strategy
- config.around(:all, type: :strategy) do |example|
+ config.around(type: :strategy) do |example|
StrategyHelpers.without_test_mode do
example.run
end
diff --git a/spec/support/redis/redis_helpers.rb b/spec/support/redis/redis_helpers.rb
index b8118bf94cc..3511d906203 100644
--- a/spec/support/redis/redis_helpers.rb
+++ b/spec/support/redis/redis_helpers.rb
@@ -5,21 +5,21 @@ module RedisHelpers
# Usage: performance enhancement
def redis_cache_cleanup!
- Gitlab::Redis::Cache.with(&:flushall)
+ Gitlab::Redis::Cache.with(&:flushdb)
end
# Usage: SideKiq, Mailroom, CI Runner, Workhorse, push services
def redis_queues_cleanup!
- Gitlab::Redis::Queues.with(&:flushall)
+ Gitlab::Redis::Queues.with(&:flushdb)
end
# Usage: session state, rate limiting
def redis_shared_state_cleanup!
- Gitlab::Redis::SharedState.with(&:flushall)
+ Gitlab::Redis::SharedState.with(&:flushdb)
end
# Usage: CI trace chunks
def redis_trace_chunks_cleanup!
- Gitlab::Redis::TraceChunks.with(&:flushall)
+ Gitlab::Redis::TraceChunks.with(&:flushdb)
end
end
diff --git a/spec/support/services/issuable_import_csv_service_shared_examples.rb b/spec/support/services/issuable_import_csv_service_shared_examples.rb
index f68750bec32..07118198969 100644
--- a/spec/support/services/issuable_import_csv_service_shared_examples.rb
+++ b/spec/support/services/issuable_import_csv_service_shared_examples.rb
@@ -67,10 +67,7 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type|
it 'correctly sets the issuable attributes' do
expect { subject }.to change { issuables.count }.by 4
- expect(issuables.reload.last).to have_attributes(
- title: 'Test Title',
- description: 'Test Description'
- )
+ expect(issuables.reload).to include(have_attributes({ title: 'Test Title', description: 'Test Description' }))
end
it_behaves_like 'importer with email notification'
@@ -89,10 +86,7 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type|
it 'correctly sets the issuable attributes' do
expect { subject }.to change { issuables.count }.by 3
- expect(issuables.reload.last).to have_attributes(
- title: 'Title with quote"',
- description: 'Description'
- )
+ expect(issuables.reload).to include(have_attributes(title: 'Title with quote"', description: 'Description'))
end
it_behaves_like 'importer with email notification'
@@ -111,10 +105,7 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type|
it 'correctly sets the issuable attributes' do
expect { subject }.to change { issuables.count }.by 2
- expect(issuables.reload.last).to have_attributes(
- title: 'Hello',
- description: 'World'
- )
+ expect(issuables.reload).to include(have_attributes(title: 'Hello', description: 'World'))
end
it_behaves_like 'importer with email notification'
@@ -133,10 +124,7 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type|
it 'correctly sets the issuable attributes' do
expect { subject }.to change { issuables.count }.by 3
- expect(issuables.reload.last).to have_attributes(
- title: 'Hello',
- description: 'World'
- )
+ expect(issuables.reload).to include(have_attributes(title: 'Hello', description: 'World'))
end
it_behaves_like 'importer with email notification'
diff --git a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
index e532b42fd1c..3d2b0433b21 100644
--- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
+++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
@@ -1,36 +1,38 @@
# frozen_string_literal: true
-Integration.available_services_names.each do |service|
- RSpec.shared_context service do
- include JiraServiceHelper if service == 'jira'
+Integration.available_integration_names.each do |integration|
+ RSpec.shared_context integration do
+ include JiraServiceHelper if integration == 'jira'
- let(:dashed_service) { service.dasherize }
- let(:service_method) { Project.integration_association_name(service) }
- let(:service_klass) { Integration.integration_name_to_model(service) }
- let(:service_instance) { service_klass.new }
- let(:service_fields) { service_instance.fields }
- let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
- let(:service_attrs) do
- service_attrs_list.inject({}) do |hash, k|
+ let(:dashed_integration) { integration.dasherize }
+ let(:integration_method) { Project.integration_association_name(integration) }
+ let(:integration_klass) { Integration.integration_name_to_model(integration) }
+ let(:integration_instance) { integration_klass.new }
+ let(:integration_fields) { integration_instance.fields }
+ let(:integration_attrs_list) { integration_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
+ let(:integration_attrs) do
+ integration_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/
hash.merge!(k => 'secrettoken')
- elsif service == 'confluence' && k == :confluence_url
+ elsif integration == 'confluence' && k == :confluence_url
hash.merge!(k => 'https://example.atlassian.net/wiki')
- elsif service == 'datadog' && k == :datadog_site
+ elsif integration == 'datadog' && k == :datadog_site
hash.merge!(k => 'datadoghq.com')
+ elsif integration == 'packagist' && k == :server
+ hash.merge!(k => 'https://packagist.example.com')
elsif k =~ /^(.*_url|url|webhook)/
hash.merge!(k => "http://example.com")
- elsif service_klass.method_defined?("#{k}?")
+ elsif integration_klass.method_defined?("#{k}?")
hash.merge!(k => true)
- elsif service == 'irker' && k == :recipients
+ elsif integration == 'irker' && k == :recipients
hash.merge!(k => 'irc://irc.network.net:666/#channel')
- elsif service == 'irker' && k == :server_port
+ elsif integration == 'irker' && k == :server_port
hash.merge!(k => 1234)
- elsif service == 'jira' && k == :jira_issue_transition_id
+ elsif integration == 'jira' && k == :jira_issue_transition_id
hash.merge!(k => '1,2,3')
- elsif service == 'emails_on_push' && k == :recipients
+ elsif integration == 'emails_on_push' && k == :recipients
hash.merge!(k => 'foo@bar.com')
- elsif service == 'slack' || service == 'mattermost' && k == :labels_to_be_notified_behavior
+ elsif integration == 'slack' || integration == 'mattermost' && k == :labels_to_be_notified_behavior
hash.merge!(k => "match_any")
else
hash.merge!(k => "someword")
@@ -45,28 +47,28 @@ Integration.available_services_names.each do |service|
end
before do
- enable_license_for_service(service)
- stub_jira_service_test if service == 'jira'
+ enable_license_for_integration(integration)
+ stub_jira_integration_test if integration == 'jira'
end
- def initialize_service(service, attrs = {})
- service_item = project.find_or_initialize_service(service)
- service_item.attributes = attrs
- service_item.properties = service_attrs
- service_item.save!
- service_item
+ def initialize_integration(integration, attrs = {})
+ record = project.find_or_initialize_integration(integration)
+ record.attributes = attrs
+ record.properties = integration_attrs
+ record.save!
+ record
end
private
- def enable_license_for_service(service)
+ def enable_license_for_integration(integration)
return unless respond_to?(:stub_licensed_features)
- licensed_feature = licensed_features[service]
+ licensed_feature = licensed_features[integration]
return unless licensed_feature
stub_licensed_features(licensed_feature => true)
- project.clear_memoization(:disabled_services)
+ project.clear_memoization(:disabled_integrations)
end
end
end
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index c00b7203af6..b7eb03de8f0 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -1,19 +1,6 @@
# frozen_string_literal: true
RSpec.shared_context 'project navbar structure' do
- let(:analytics_nav_item) do
- {
- nav_item: _('Analytics'),
- nav_sub_items: [
- _('CI/CD'),
- (_('Code Review') if Gitlab.ee?),
- (_('Merge Request') if Gitlab.ee?),
- _('Repository'),
- _('Value Stream')
- ]
- }
- end
-
let(:security_and_compliance_nav_item) do
{
nav_item: _('Security & Compliance'),
@@ -24,64 +11,20 @@ RSpec.shared_context 'project navbar structure' do
}
end
- let(:monitor_nav_item) do
- {
- nav_item: _('Operations'),
- nav_sub_items: monitor_menu_items
- }
- end
-
- let(:monitor_menu_items) do
- [
- _('Metrics'),
- _('Logs'),
- _('Tracing'),
- _('Error Tracking'),
- _('Alerts'),
- _('Incidents'),
- _('Serverless'),
- _('Terraform'),
- _('Kubernetes'),
- _('Environments'),
- _('Feature Flags'),
- _('Product Analytics')
- ]
- end
-
- let(:project_information_nav_item) do
- {
- nav_item: _('Project overview'),
- nav_sub_items: [
- _('Details'),
- _('Activity'),
- _('Releases')
- ]
- }
- end
-
- let(:settings_menu_items) do
- [
- _('General'),
- _('Integrations'),
- _('Webhooks'),
- _('Access Tokens'),
- _('Repository'),
- _('CI/CD'),
- _('Operations')
- ]
- end
-
- let(:project_context_nav_item) do
- {
- nav_item: "#{project.name[0, 1].upcase} #{project.name}",
- nav_sub_items: []
- }
- end
-
let(:structure) do
[
- project_context_nav_item,
- project_information_nav_item,
+ {
+ nav_item: "#{project.name[0, 1].upcase} #{project.name}",
+ nav_sub_items: []
+ },
+ {
+ nav_item: _('Project information'),
+ nav_sub_items: [
+ _('Activity'),
+ _('Labels'),
+ _('Members')
+ ]
+ },
{
nav_item: _('Repository'),
nav_sub_items: [
@@ -120,8 +63,44 @@ RSpec.shared_context 'project navbar structure' do
]
},
security_and_compliance_nav_item,
- monitor_nav_item,
- analytics_nav_item,
+ {
+ nav_item: _('Deployments'),
+ nav_sub_items: [
+ _('Feature Flags'),
+ _('Environments'),
+ _('Releases')
+ ]
+ },
+ {
+ nav_item: _('Monitor'),
+ nav_sub_items: [
+ _('Metrics'),
+ _('Logs'),
+ _('Tracing'),
+ _('Error Tracking'),
+ _('Alerts'),
+ _('Incidents'),
+ _('Product Analytics')
+ ]
+ },
+ {
+ nav_item: _('Infrastructure'),
+ nav_sub_items: [
+ _('Kubernetes clusters'),
+ _('Serverless platform'),
+ _('Terraform')
+ ]
+ },
+ {
+ nav_item: _('Analytics'),
+ nav_sub_items: [
+ _('CI/CD'),
+ (_('Code review') if Gitlab.ee?),
+ (_('Merge request') if Gitlab.ee?),
+ _('Repository'),
+ _('Value stream')
+ ]
+ },
{
nav_item: _('Wiki'),
nav_sub_items: []
@@ -132,7 +111,15 @@ RSpec.shared_context 'project navbar structure' do
},
{
nav_item: _('Settings'),
- nav_sub_items: settings_menu_items
+ nav_sub_items: [
+ _('General'),
+ _('Integrations'),
+ _('Webhooks'),
+ _('Access Tokens'),
+ _('Repository'),
+ _('CI/CD'),
+ _('Monitor')
+ ]
}
].compact
end
@@ -189,17 +176,6 @@ RSpec.shared_context 'group navbar structure' do
}
end
- let(:group_information_nav_item) do
- {
- nav_item: _('Group information'),
- nav_sub_items: [
- _('Activity'),
- _('Labels'),
- _('Members')
- ]
- }
- end
-
let(:issues_nav_items) do
[
_('List'),
@@ -208,17 +184,20 @@ RSpec.shared_context 'group navbar structure' do
]
end
- let(:group_context_nav_item) do
- {
- nav_item: "#{group.name[0, 1].upcase} #{group.name}",
- nav_sub_items: []
- }
- end
-
let(:structure) do
[
- group_context_nav_item,
- group_information_nav_item,
+ {
+ nav_item: "#{group.name[0, 1].upcase} #{group.name}",
+ nav_sub_items: []
+ },
+ {
+ nav_item: _('Group information'),
+ nav_sub_items: [
+ _('Activity'),
+ _('Labels'),
+ _('Members')
+ ]
+ },
{
nav_item: _('Issues'),
nav_sub_items: issues_nav_items
@@ -227,7 +206,7 @@ RSpec.shared_context 'group navbar structure' do
nav_item: _('Merge requests'),
nav_sub_items: []
},
- security_and_compliance_nav_item,
+ (security_and_compliance_nav_item if Gitlab.ee?),
(push_rules_nav_item if Gitlab.ee?),
{
nav_item: _('Kubernetes'),
diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
index d638ffcf8fa..de1b46c65ad 100644
--- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -48,7 +48,7 @@ RSpec.shared_context 'ProjectPolicy context' do
destroy_container_image push_code read_pod_logs read_terraform_state
resolve_note update_build update_commit_status update_container_image
update_deployment update_environment update_merge_request
- update_metrics_dashboard_annotation update_pipeline update_release
+ update_metrics_dashboard_annotation update_pipeline update_release destroy_release
]
end
@@ -57,7 +57,7 @@ RSpec.shared_context 'ProjectPolicy context' do
add_cluster admin_build admin_commit_status admin_container_image
admin_deployment admin_environment admin_note admin_pipeline
admin_project admin_project_member admin_snippet admin_terraform_state
- admin_wiki create_deploy_token destroy_deploy_token destroy_release
+ admin_wiki create_deploy_token destroy_deploy_token
push_to_delete_protected_branch read_deploy_token update_snippet
]
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 de40b926a1c..6d34675e8e5 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
@@ -4,8 +4,8 @@ RSpec.shared_context 'Jira projects request context' do
let(:url) { 'https://jira.example.com' }
let(:username) { 'jira-username' }
let(:password) { 'jira-password' }
- let!(:jira_service) do
- create(:jira_service,
+ let!(:jira_integration) do
+ create(:jira_integration,
project: project,
url: url,
username: username,
diff --git a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb
new file mode 100644
index 00000000000..ea72398010c
--- /dev/null
+++ b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'stubbed service ping metrics definitions' do
+ include UsageDataHelpers
+
+ let(:metrics_definitions) { standard_metrics + subscription_metrics + operational_metrics + optional_metrics }
+ let(:standard_metrics) do
+ [
+ metric_attributes('uuid', "Standard")
+ ]
+ end
+
+ let(:operational_metrics) do
+ [
+ metric_attributes('counts.merge_requests', "Operational"),
+ metric_attributes('counts.todos', "Operational")
+ ]
+ end
+
+ let(:optional_metrics) do
+ [
+ metric_attributes('counts.boards', "Optional"),
+ metric_attributes('gitaly.filesystems', '').except('data_category')
+ ]
+ end
+
+ before do
+ stub_usage_data_connections
+ stub_object_store_settings
+
+ allow(Gitlab::Usage::MetricDefinition).to(
+ receive(:definitions)
+ .and_return(metrics_definitions.to_h { |definition| [definition['key_path'], Gitlab::Usage::MetricDefinition.new('', definition.symbolize_keys)] })
+ )
+ end
+
+ def metric_attributes(key_path, category)
+ {
+ 'key_path' => key_path,
+ 'data_category' => category
+ }
+ end
+end
diff --git a/spec/support/shared_contexts/unique_ip_check_shared_context.rb b/spec/support/shared_contexts/unique_ip_check_shared_context.rb
index f6bedb6cada..8d199df1c10 100644
--- a/spec/support/shared_contexts/unique_ip_check_shared_context.rb
+++ b/spec/support/shared_contexts/unique_ip_check_shared_context.rb
@@ -5,9 +5,9 @@ RSpec.shared_context 'unique ips sign in limit' do
let(:request_context) { Gitlab::RequestContext.instance }
before do
- Gitlab::Redis::Cache.with(&:flushall)
- Gitlab::Redis::Queues.with(&:flushall)
- Gitlab::Redis::SharedState.with(&:flushall)
+ redis_cache_cleanup!
+ redis_queues_cleanup!
+ redis_shared_state_cleanup!
end
before do
diff --git a/spec/support/shared_examples/ci/edit_job_token_scope_shared_examples.rb b/spec/support/shared_examples/ci/edit_job_token_scope_shared_examples.rb
new file mode 100644
index 00000000000..05b2b5f5de1
--- /dev/null
+++ b/spec/support/shared_examples/ci/edit_job_token_scope_shared_examples.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'editable job token scope' do
+ shared_examples 'returns error' do |error|
+ it 'returns an error response', :aggregate_failures do
+ expect(result).to be_error
+ expect(result.message).to eq(error)
+ end
+ end
+
+ context 'when job token scope is disabled for the given project' do
+ before do
+ allow(project).to receive(:ci_job_token_scope_enabled?).and_return(false)
+ end
+
+ it_behaves_like 'returns error', 'Job token scope is disabled for this project'
+ end
+
+ context 'when user does not have permissions to edit the job token scope' do
+ it_behaves_like 'returns error', 'Insufficient permissions to modify the job token scope'
+ end
+
+ context 'when user has permissions to edit the job token scope' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ context 'when target project is not provided' do
+ let(:target_project) { nil }
+
+ it_behaves_like 'returns error', Ci::JobTokenScope::EditScopeValidations::TARGET_PROJECT_UNAUTHORIZED_OR_UNFOUND
+ end
+
+ context 'when target project is provided' do
+ context 'when user does not have permissions to read the target project' do
+ it_behaves_like 'returns error', Ci::JobTokenScope::EditScopeValidations::TARGET_PROJECT_UNAUTHORIZED_OR_UNFOUND
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb
index 70a684c12bf..017e55309f7 100644
--- a/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb
@@ -44,11 +44,13 @@ RSpec.shared_examples 'project access tokens available #create' do
end
it 'creates project access token' do
+ access_level = access_token_params[:access_level] || Gitlab::Access::MAINTAINER
subject
expect(created_token.name).to eq(access_token_params[:name])
expect(created_token.scopes).to eq(access_token_params[:scopes])
expect(created_token.expires_at).to eq(access_token_params[:expires_at])
+ expect(project.project_member(created_token.user).access_level).to eq(access_level)
end
it 'creates project bot user' do
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 9af35c189d0..e8f7e62d0d7 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
RSpec.shared_examples 'wiki controller actions' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:other_user) { create(:user) }
+
let(:container) { raise NotImplementedError }
let(:routing_params) { raise NotImplementedError }
-
- let_it_be(:user) { create(:user) }
let(:wiki) { Wiki.for_container(container, user) }
let(:wiki_title) { 'page title test' }
@@ -458,6 +459,7 @@ RSpec.shared_examples 'wiki controller actions' do
describe 'DELETE #destroy' do
let(:id_param) { wiki_title }
+ let(:delete_user) { user }
subject(:request) do
delete(:destroy,
@@ -466,13 +468,21 @@ RSpec.shared_examples 'wiki controller actions' do
))
end
+ before do
+ sign_in(delete_user)
+ end
+
context 'when page exists' do
- it 'deletes the page' do
- expect do
- request
- end.to change { wiki.list_pages.size }.by(-1)
+ shared_examples 'deletes the page' do
+ specify do
+ expect do
+ request
+ end.to change { wiki.list_pages.size }.by(-1)
+ end
end
+ it_behaves_like 'deletes the page'
+
context 'but page cannot be deleted' do
before do
allow_next_instance_of(WikiPage) do |page|
@@ -489,6 +499,28 @@ RSpec.shared_examples 'wiki controller actions' do
expect(assigns(:error)).to eq('Could not delete wiki page')
end
end
+
+ context 'when user is a developer' do
+ let(:delete_user) { other_user }
+
+ before do
+ container.add_developer(other_user)
+ end
+
+ it_behaves_like 'deletes the page'
+ end
+
+ context 'when user is a reporter' do
+ let(:delete_user) { other_user }
+
+ before do
+ container.add_reporter(other_user)
+ end
+
+ it 'returns 404' do
+ is_expected.to have_gitlab_http_status(:not_found)
+ end
+ end
end
context 'when page does not exist' do
diff --git a/spec/support/shared_examples/features/cascading_settings_shared_examples.rb b/spec/support/shared_examples/features/cascading_settings_shared_examples.rb
index 29ef3da9a85..395f4fc54e0 100644
--- a/spec/support/shared_examples/features/cascading_settings_shared_examples.rb
+++ b/spec/support/shared_examples/features/cascading_settings_shared_examples.rb
@@ -13,10 +13,22 @@ RSpec.shared_examples 'a cascading setting' do
click_save_button
end
- it 'disables setting in subgroups' do
- visit subgroup_path
+ shared_examples 'subgroup settings are disabled' do
+ it 'disables setting in subgroups' do
+ visit subgroup_path
+
+ expect(find("#{setting_field_selector}[disabled]")).to be_checked
+ end
+ end
+
+ include_examples 'subgroup settings are disabled'
+
+ context 'when use_traversal_ids_for_ancestors is disabled' do
+ before do
+ stub_feature_flags(use_traversal_ids_for_ancestors: false)
+ end
- expect(find("#{setting_field_selector}[disabled]")).to be_checked
+ include_examples 'subgroup settings are disabled'
end
it 'does not show enforcement checkbox in subgroups' do
diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb
index 4d2e13aa5bc..9e88db2e1c0 100644
--- a/spec/support/shared_examples/features/packages_shared_examples.rb
+++ b/spec/support/shared_examples/features/packages_shared_examples.rb
@@ -32,11 +32,9 @@ RSpec.shared_examples 'package details link' do |property|
expect(page).to have_current_path(project_package_path(package.project, package))
- page.within('[data-qa-selector="package_title"]') do
- expect(page).to have_content(package.name)
- end
+ expect(page).to have_css('.packages-app h1[data-testid="title"]', text: package.name)
- page.within('[data-qa-selector="package_information_content"]') do
+ page.within(%Q([name="#{package.name}"])) do
expect(page).to have_content('Installation')
expect(page).to have_content('Registry setup')
end
diff --git a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
new file mode 100644
index 00000000000..bb5460e2a6f
--- /dev/null
+++ b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'search timeouts' do |scope|
+ context 'when search times out' do
+ before do
+ allow_next_instance_of(SearchService) do |service|
+ allow(service).to receive(:search_objects).and_raise(ActiveRecord::QueryCanceled)
+ end
+
+ visit(search_path(search: 'test', scope: scope))
+ end
+
+ it 'renders timeout information' do
+ expect(page).to have_content('Your search timed out')
+ end
+
+ it 'sets tab count to 0' do
+ expect(page.find('.search-filter .active')).to have_text('0')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb
index c9508818f74..5bfe929e957 100644
--- a/spec/support/shared_examples/features/sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb
@@ -175,12 +175,4 @@ RSpec.shared_examples 'issue boards sidebar' do
end
end
end
-
- def refresh_and_click_first_card
- page.refresh
-
- wait_for_requests
-
- first_card.click
- end
end
diff --git a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
index f2576931642..dfc9a45bd0d 100644
--- a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
@@ -20,17 +20,6 @@ RSpec.shared_examples 'User creates wiki page' do
click_link "Create your first page"
end
- it "shows validation error message if the form is force submitted", :js do
- page.within(".wiki-form") do
- fill_in(:wiki_content, with: "")
-
- page.execute_script("document.querySelector('.wiki-form').submit()")
- page.accept_alert # manually force form submit
- end
-
- expect(page).to have_content("The form contains the following error:").and have_content("Content can't be blank")
- end
-
it "disables the submit button", :js do
page.within(".wiki-form") do
fill_in(:wiki_content, with: "")
diff --git a/spec/support/shared_examples/features/wiki/user_deletes_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_deletes_wiki_page_shared_examples.rb
index ee0261771f9..55c89977a99 100644
--- a/spec/support/shared_examples/features/wiki/user_deletes_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_deletes_wiki_page_shared_examples.rb
@@ -7,18 +7,34 @@
RSpec.shared_examples 'User deletes wiki page' do
include WikiHelpers
+ let_it_be(:developer) { create(:user) }
+
let(:wiki_page) { create(:wiki_page, wiki: wiki) }
before do
+ wiki.container.add_developer(developer)
+
sign_in(user)
visit wiki_page_path(wiki, wiki_page)
end
- it 'deletes a page', :js do
- click_on('Edit')
- click_on('Delete')
- find('[data-testid="confirm_deletion_button"]').click
+ shared_examples 'deletes a wiki page' do
+ specify 'deletes a page', :js do
+ click_on('Edit')
+ click_on('Delete')
+ find('[data-testid="confirm_deletion_button"]').click
+
+ expect(page).to have_content('Wiki page was successfully deleted.')
+ end
+ end
+
+ context 'when user is the owner or maintainer' do
+ it_behaves_like 'deletes a wiki page'
+ end
+
+ context 'when user is a developer' do
+ let(:user) { developer }
- expect(page).to have_content('Wiki page was successfully deleted.')
+ it_behaves_like 'deletes a wiki page'
end
end
diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
index db2a96d9649..9587da0233e 100644
--- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
@@ -90,19 +90,6 @@ RSpec.shared_examples 'User updates wiki page' do
expect(page).to have_field('wiki[message]', with: 'Update Wiki title')
end
- it 'shows a validation error message if the form is force submitted', :js do
- fill_in(:wiki_content, with: '')
-
- page.execute_script("document.querySelector('.wiki-form').submit()")
- page.accept_alert # manually force form submit
-
- expect(page).to have_selector('.wiki-form')
- expect(page).to have_content('Edit Page')
- expect(page).to have_content('The form contains the following error:')
- expect(page).to have_content("Content can't be blank")
- expect(find('textarea#wiki_content').value).to eq('')
- end
-
it "disables the submit button", :js do
page.within(".wiki-form") do
fill_in(:wiki_content, with: "")
diff --git a/spec/support/shared_examples/graphql/design_fields_shared_examples.rb b/spec/support/shared_examples/graphql/design_fields_shared_examples.rb
index 9c2eb3e5a5c..efbcfaf0e91 100644
--- a/spec/support/shared_examples/graphql/design_fields_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/design_fields_shared_examples.rb
@@ -27,6 +27,7 @@ RSpec.shared_examples 'a GraphQL type with design fields' do
describe '#image' do
let_it_be(:current_user) { create(:user) }
+
let(:schema) { GitlabSchema }
let(:query) { GraphQL::Query.new(schema) }
let(:context) { query.context }
diff --git a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb
index 5e15c91cd41..011a2157f24 100644
--- a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb
@@ -3,17 +3,13 @@
require 'spec_helper'
RSpec.shared_examples 'a mutation which can mutate a spammable' do
- describe "#additional_spam_params" do
- it 'passes additional spam params to the service' do
+ describe "#spam_params" do
+ it 'passes spam params to the service constructor' do
args = [
project: anything,
current_user: anything,
- params: hash_including(
- api: true,
- request: instance_of(ActionDispatch::Request),
- captcha_response: captcha_response,
- spam_log_id: spam_log_id
- )
+ params: anything,
+ spam_params: instance_of(::Spam::SpamParams)
]
expect(service).to receive(:new).with(*args).and_call_original
diff --git a/spec/support/shared_examples/graphql/spam_protection_shared_examples.rb b/spec/support/shared_examples/graphql/spam_protection_shared_examples.rb
index 8fb89a4f80e..c0b71a494d0 100644
--- a/spec/support/shared_examples/graphql/spam_protection_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/spam_protection_shared_examples.rb
@@ -57,7 +57,7 @@ RSpec.shared_examples 'has spam protection' do
context 'and no CAPTCHA is required' do
let(:render_captcha) { false }
- it 'does not return a to-level error' do
+ it 'does not return a top-level error' do
send_request
expect(graphql_errors).to be_blank
diff --git a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
new file mode 100644
index 00000000000..845fa78a827
--- /dev/null
+++ b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'object cache helper' do
+ it { is_expected.to be_a(Gitlab::Json::PrecompiledJson) }
+
+ it "uses the presenter" do
+ expect(presenter).to receive(:represent).with(presentable, project: project)
+
+ subject
+ end
+
+ it "is valid JSON" do
+ parsed = Gitlab::Json.parse(subject.to_s)
+
+ expect(parsed).to be_a(Hash)
+ expect(parsed["id"]).to eq(presentable.id)
+ end
+
+ it "fetches from the cache" do
+ expect(instance.cache).to receive(:fetch).with("#{presenter.class.name}:#{presentable.cache_key}:#{user.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once
+
+ subject
+ end
+
+ context "when a cache context is supplied" do
+ before do
+ kwargs[:cache_context] = -> (item) { item.project.cache_key }
+ end
+
+ it "uses the context to augment the cache key" do
+ expect(instance.cache).to receive(:fetch).with("#{presenter.class.name}:#{presentable.cache_key}:#{project.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once
+
+ subject
+ end
+ end
+
+ context "when expires_in is supplied" do
+ it "sets the expiry when accessing the cache" do
+ kwargs[:expires_in] = 7.days
+
+ expect(instance.cache).to receive(:fetch).with("#{presenter.class.name}:#{presentable.cache_key}:#{user.cache_key}", expires_in: 7.days).once
+
+ subject
+ end
+ end
+end
+
+RSpec.shared_examples_for 'collection cache helper' do
+ it { is_expected.to be_an(Gitlab::Json::PrecompiledJson) }
+
+ it "uses the presenter" do
+ presentable.each do |item|
+ expect(presenter).to receive(:represent).with(item, project: project)
+ end
+
+ subject
+ end
+
+ it "is valid JSON" do
+ parsed = Gitlab::Json.parse(subject.to_s)
+
+ expect(parsed).to be_an(Array)
+
+ presentable.each_with_index do |item, i|
+ expect(parsed[i]["id"]).to eq(item.id)
+ end
+ end
+
+ it "fetches from the cache" do
+ keys = presentable.map { |item| "#{presenter.class.name}:#{item.cache_key}:#{user.cache_key}" }
+
+ expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: described_class::DEFAULT_EXPIRY).once.and_call_original
+
+ subject
+ end
+
+ context "when a cache context is supplied" do
+ before do
+ kwargs[:cache_context] = -> (item) { item.project.cache_key }
+ end
+
+ it "uses the context to augment the cache key" do
+ keys = presentable.map { |item| "#{presenter.class.name}:#{item.cache_key}:#{project.cache_key}" }
+
+ expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: described_class::DEFAULT_EXPIRY).once.and_call_original
+
+ subject
+ end
+ end
+
+ context "expires_in is supplied" do
+ it "sets the expiry when accessing the cache" do
+ keys = presentable.map { |item| "#{presenter.class.name}:#{item.cache_key}:#{user.cache_key}" }
+ kwargs[:expires_in] = 7.days
+
+ expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: 7.days).once.and_call_original
+
+ subject
+ end
+ end
+end
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 7d341d79bae..6e12b5a0e85 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
@@ -3,6 +3,7 @@
RSpec.shared_examples_for 'value stream analytics event' do
let(:params) { {} }
let(:instance) { described_class.new(params) }
+ let(:expected_hash_code) { Digest::SHA256.hexdigest(instance.class.identifier.to_s) }
it { expect(described_class.name).to be_a_kind_of(String) }
it { expect(described_class.identifier).to be_a_kind_of(Symbol) }
@@ -19,4 +20,16 @@ RSpec.shared_examples_for 'value stream analytics event' do
expect(output_query).to be_a_kind_of(ActiveRecord::Relation)
end
end
+
+ describe '#hash_code' do
+ it 'returns a hash that uniquely identifies an event' do
+ expect(instance.hash_code).to eq(expected_hash_code)
+ end
+
+ it 'does not differ when the same object is built with the same params' do
+ another_instance_with_same_params = described_class.new(params)
+
+ expect(another_instance_with_same_params.hash_code).to eq(instance.hash_code)
+ end
+ end
end
diff --git a/spec/support/shared_examples/lib/gitlab/import_export/relation_factory_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/relation_factory_shared_examples.rb
index 33061f17bde..3c5c65f0690 100644
--- a/spec/support/shared_examples/lib/gitlab/import_export/relation_factory_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import_export/relation_factory_shared_examples.rb
@@ -12,7 +12,7 @@ RSpec.shared_examples 'Notes user references' do
'id' => 111,
'access_level' => 30,
'source_id' => 1,
- 'source_type' => importable.class.name == 'Project' ? 'Project' : 'Namespace',
+ 'source_type' => importable.instance_of?(Project) ? 'Project' : 'Namespace',
'user_id' => 3,
'notification_level' => 3,
'created_at' => '2016-11-18T09:29:42.634Z',
diff --git a/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb
index f018ece0d46..2633a89eeee 100644
--- a/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb
@@ -19,7 +19,8 @@ RSpec.shared_examples 'network policy common specs' do
creation_timestamp: nil,
manifest: YAML.dump(policy.resource.deep_stringify_keys),
is_autodevops: false,
- is_enabled: true
+ is_enabled: true,
+ environment_ids: []
}
end
diff --git a/spec/support/shared_examples/lib/gitlab/search_results_sorted_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_results_sorted_shared_examples.rb
index eafb49cef71..e4f09dfa0b0 100644
--- a/spec/support/shared_examples/lib/gitlab/search_results_sorted_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/search_results_sorted_shared_examples.rb
@@ -33,3 +33,21 @@ RSpec.shared_examples 'search results sorted' do
end
end
end
+
+RSpec.shared_examples 'search results sorted by popularity' do
+ context 'sort: popularity_desc' do
+ let(:sort) { 'popularity_desc' }
+
+ it 'sorts results by upvotes' do
+ expect(results_popular.objects(scope).map(&:id)).to eq([popular_result.id, less_popular_result.id, non_popular_result.id])
+ end
+ end
+
+ context 'sort: popularity_asc' do
+ let(:sort) { 'popularity_asc' }
+
+ it 'sorts results by created_at' do
+ expect(results_popular.objects(scope).map(&:id)).to eq([non_popular_result.id, less_popular_result.id, popular_result.id])
+ end
+ end
+end
diff --git a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
index 1b110ab02b5..a84658780b9 100644
--- a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
+++ b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
@@ -5,9 +5,10 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
2.times do
Gitlab::WithRequestStore.with_request_store do
subscriber.sql(event)
+ connection = event.payload[:connection]
if db_role == :primary
- expect(described_class.db_counter_payload).to eq(
+ expected = {
db_count: record_query ? 1 : 0,
db_write_count: record_write_query ? 1 : 0,
db_cached_count: record_cached_query ? 1 : 0,
@@ -18,10 +19,13 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_replica_count: 0,
db_replica_duration_s: 0.0,
db_primary_wal_count: record_wal_query ? 1 : 0,
+ db_primary_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
+ db_replica_wal_cached_count: 0,
db_replica_wal_count: 0
- )
+ }
+ expected[:"db_primary_#{::Gitlab::Database.dbname(connection)}_duration_s"] = 0.002 if record_query
elsif db_role == :replica
- expect(described_class.db_counter_payload).to eq(
+ expected = {
db_count: record_query ? 1 : 0,
db_write_count: record_write_query ? 1 : 0,
db_cached_count: record_cached_query ? 1 : 0,
@@ -32,15 +36,35 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_replica_count: record_query ? 1 : 0,
db_replica_duration_s: record_query ? 0.002 : 0,
db_replica_wal_count: record_wal_query ? 1 : 0,
+ db_replica_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
+ db_primary_wal_cached_count: 0,
db_primary_wal_count: 0
- )
+ }
+ expected[:"db_replica_#{::Gitlab::Database.dbname(connection)}_duration_s"] = 0.002 if record_query
else
- expect(described_class.db_counter_payload).to eq(
+ expected = {
db_count: record_query ? 1 : 0,
db_write_count: record_write_query ? 1 : 0,
db_cached_count: record_cached_query ? 1 : 0
- )
+ }
end
+
+ expect(described_class.db_counter_payload).to eq(expected)
+ end
+ end
+ end
+
+ context 'when multiple_database_metrics is disabled' do
+ before do
+ stub_feature_flags(multiple_database_metrics: false)
+ end
+
+ it 'does not include per database metrics' do
+ Gitlab::WithRequestStore.with_request_store do
+ subscriber.sql(event)
+ connection = event.payload[:connection]
+
+ expect(described_class.db_counter_payload).not_to include(:"db_replica_#{::Gitlab::Database.dbname(connection)}_duration_s")
end
end
end
@@ -71,7 +95,10 @@ RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do
end
if record_wal_query
- expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1) if db_role
+ if db_role
+ expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1)
+ expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_cached_count_total".to_sym, 1) if record_cached_query
+ end
else
expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1) if db_role
end
diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
index 42f82987989..03f565e0aac 100644
--- a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
+++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
@@ -165,9 +165,9 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
3.times { supply.next_value }
end
- current_value = described_class.public_send(method_name, scope_value, &:current_value)
-
- expect(current_value).to eq(iid + 3)
+ described_class.public_send(method_name, scope_value) do |supply|
+ expect(supply.next_value).to eq(iid + 4)
+ end
end
end
diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
index 9f3be3e2e06..72659dd5f3b 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -13,7 +13,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
end
it { is_expected.to validate_presence_of(:webhook) }
- it_behaves_like "issue tracker service URL attribute", :webhook
+ it_behaves_like "issue tracker integration URL attribute", :webhook
end
context "when integration is inactive" do
@@ -163,7 +163,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
context "with issue events" do
let(:opts) { { title: "Awesome issue", description: "please fix" } }
let(:sample_data) do
- service = Issues::CreateService.new(project: project, current_user: user, params: opts)
+ service = Issues::CreateService.new(project: project, current_user: user, params: opts, spam_params: nil)
issue = service.execute
service.hook_data(issue, "open")
end
diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
index 66448aca2c5..2d4c0b60f2b 100644
--- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
@@ -8,7 +8,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name|
def execute_with_options(options)
receive(:new).with(webhook_url, options.merge(http_client: Integrations::SlackMattermostNotifier::HTTPClient))
- .and_return(double(:slack_service).as_null_object)
+ .and_return(double(:slack_integration).as_null_object)
end
describe "Associations" do
@@ -23,7 +23,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name|
end
it { is_expected.to validate_presence_of(:webhook) }
- it_behaves_like 'issue tracker service URL attribute', :webhook
+ it_behaves_like 'issue tracker integration URL attribute', :webhook
end
context 'when service is inactive' do
diff --git a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
index d23f95b2e9e..cf38a583944 100644
--- a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
+++ b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
@@ -122,6 +122,22 @@ RSpec.shared_examples 'value stream analytics stage' do
expect(stage.parent_id).to eq(parent.id)
end
end
+
+ describe '#hash_code' do
+ it 'does not differ when the same object is built with the same params' do
+ stage_1 = build(factory)
+ stage_2 = build(factory)
+
+ expect(stage_1.events_hash_code).to eq(stage_2.events_hash_code)
+ end
+
+ it 'differs when the stage events are different' do
+ stage_1 = build(factory, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged)
+ stage_2 = build(factory, start_event_identifier: :issue_created, end_event_identifier: :issue_first_mentioned_in_commit)
+
+ expect(stage_1.events_hash_code).not_to eq(stage_2.events_hash_code)
+ end
+ end
end
RSpec.shared_examples 'value stream analytics label based stage' do
diff --git a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
index 128999d02fa..e35ac9c0d0d 100644
--- a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
@@ -66,14 +66,14 @@ RSpec.shared_examples Integrations::BaseSlashCommands do
}
end
- let(:service) do
- project.create_mattermost_slash_commands_service(
+ let(:integration) do
+ project.create_mattermost_slash_commands_integration(
properties: { token: 'token' }
)
end
it 'generates the url' do
- response = service.trigger(params)
+ response = integration.trigger(params)
expect(response[:text]).to start_with(':wave: Hi there!')
end
diff --git a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
new file mode 100644
index 00000000000..1fa340a0cf4
--- /dev/null
+++ b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::HasWebHook do
+ include AfterNextHelpers
+
+ describe 'callbacks' do
+ it 'calls #update_web_hook! when enabled' do
+ expect(integration).to receive(:update_web_hook!)
+
+ integration.active = true
+ integration.save!
+ end
+
+ it 'does not call #update_web_hook! when disabled' do
+ expect(integration).not_to receive(:update_web_hook!)
+
+ integration.active = false
+ integration.save!
+ end
+
+ it 'does not call #update_web_hook! when validation fails' do
+ expect(integration).not_to receive(:update_web_hook!)
+
+ integration.active = true
+ integration.project = nil
+ expect(integration.save).to be(false)
+ end
+ end
+
+ describe '#hook_url' do
+ it 'returns a string' do
+ expect(integration.hook_url).to be_a(String)
+ end
+ end
+
+ describe '#hook_ssl_verification' do
+ it 'returns a boolean' do
+ expect(integration.hook_ssl_verification).to be_in([true, false])
+ end
+ end
+
+ describe '#update_web_hook!' do
+ def call
+ integration.update_web_hook!
+ end
+
+ it 'creates or updates a service hook' do
+ expect { call }.to change(ServiceHook, :count).by(1)
+ expect(integration.service_hook.url).to eq(hook_url)
+
+ integration.service_hook.update!(url: 'http://other.com')
+
+ expect { call }.to change { integration.service_hook.reload.url }.from('http://other.com').to(hook_url)
+ end
+
+ it 'raises an error if the service hook could not be saved' do
+ call
+ integration.service_hook.integration = nil
+
+ expect { call }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+
+ it 'does not attempt to save the service hook if there are no changes' do
+ call
+
+ expect(integration.service_hook).not_to receive(:save!)
+
+ call
+ end
+ end
+
+ describe '#execute_web_hook!' do
+ let(:args) { ['foo', [1, 2, 3]] }
+
+ def call
+ integration.execute_web_hook!(*args)
+ end
+
+ it 'creates the webhook if necessary and executes it' do
+ expect_next(ServiceHook).to receive(:execute).with(*args)
+ expect { call }.to change(ServiceHook, :count).by(1)
+
+ expect(integration.service_hook).to receive(:execute).with(*args)
+ expect { call }.not_to change(ServiceHook, :count)
+ end
+
+ it 'raises an error if the service hook could not be saved' do
+ expect_next(ServiceHook).to receive(:execute).with(*args)
+
+ call
+ integration.service_hook.integration = nil
+
+ expect(integration.service_hook).not_to receive(:execute)
+ expect { call }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb b/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb
index b275d594792..6d519e561ee 100644
--- a/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb
+++ b/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr|
+RSpec.shared_examples 'issue tracker integration URL attribute' do |url_attr|
it { is_expected.to allow_value('https://example.com').for(url_attr) }
it { is_expected.not_to allow_value('example.com').for(url_attr) }
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index 7ede6f0d8d4..c111d250d34 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -75,3 +75,259 @@ RSpec.shared_examples '#valid_level_roles' do |entity_name|
expect(presenter.valid_level_roles).to eq(expected_roles)
end
end
+
+RSpec.shared_examples_for "member creation" do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:admin) { create(:admin) }
+
+ describe '#execute' do
+ it 'returns a Member object', :aggregate_failures do
+ member = described_class.new(source, user, :maintainer).execute
+
+ expect(member).to be_a member_type
+ expect(member).to be_persisted
+ end
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it 'sets members.created_by to the given admin current_user' do
+ member = described_class.new(source, user, :maintainer, current_user: admin).execute
+
+ expect(member.created_by).to eq(admin)
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it 'rejects setting members.created_by to the given admin current_user' do
+ member = described_class.new(source, user, :maintainer, current_user: admin).execute
+
+ expect(member.created_by).to be_nil
+ end
+ end
+
+ it 'sets members.expires_at to the given expires_at' do
+ member = described_class.new(source, user, :maintainer, expires_at: Date.new(2016, 9, 22)).execute
+
+ expect(member.expires_at).to eq(Date.new(2016, 9, 22))
+ end
+
+ described_class.access_levels.each do |sym_key, int_access_level|
+ it "accepts the :#{sym_key} symbol as access level", :aggregate_failures do
+ expect(source.users).not_to include(user)
+
+ member = described_class.new(source, user.id, sym_key).execute
+
+ expect(member.access_level).to eq(int_access_level)
+ expect(source.users.reload).to include(user)
+ end
+
+ it "accepts the #{int_access_level} integer as access level", :aggregate_failures do
+ expect(source.users).not_to include(user)
+
+ member = described_class.new(source, user.id, int_access_level).execute
+
+ expect(member.access_level).to eq(int_access_level)
+ expect(source.users.reload).to include(user)
+ end
+ end
+
+ context 'with no current_user' do
+ context 'when called with a known user id' do
+ it 'adds the user as a member' do
+ expect(source.users).not_to include(user)
+
+ described_class.new(source, user.id, :maintainer).execute
+
+ expect(source.users.reload).to include(user)
+ end
+ end
+
+ context 'when called with an unknown user id' do
+ it 'adds the user as a member' do
+ expect(source.users).not_to include(user)
+
+ described_class.new(source, non_existing_record_id, :maintainer).execute
+
+ expect(source.users.reload).not_to include(user)
+ end
+ end
+
+ context 'when called with a user object' do
+ it 'adds the user as a member' do
+ expect(source.users).not_to include(user)
+
+ described_class.new(source, user, :maintainer).execute
+
+ expect(source.users.reload).to include(user)
+ end
+ end
+
+ context 'when called with a requester user object' do
+ before do
+ source.request_access(user)
+ end
+
+ it 'adds the requester as a member', :aggregate_failures do
+ expect(source.users).not_to include(user)
+ expect(source.requesters.exists?(user_id: user)).to be_truthy
+
+ expect do
+ described_class.new(source, user, :maintainer).execute
+ end.to raise_error(Gitlab::Access::AccessDeniedError)
+
+ expect(source.users.reload).not_to include(user)
+ expect(source.requesters.reload.exists?(user_id: user)).to be_truthy
+ end
+ end
+
+ context 'when called with a known user email' do
+ it 'adds the user as a member' do
+ expect(source.users).not_to include(user)
+
+ described_class.new(source, user.email, :maintainer).execute
+
+ expect(source.users.reload).to include(user)
+ end
+ end
+
+ context 'when called with an unknown user email' do
+ it 'creates an invited member' do
+ expect(source.users).not_to include(user)
+
+ described_class.new(source, 'user@example.com', :maintainer).execute
+
+ expect(source.members.invite.pluck(:invite_email)).to include('user@example.com')
+ end
+ end
+
+ context 'when called with an unknown user email starting with a number' do
+ it 'creates an invited member', :aggregate_failures do
+ email_starting_with_number = "#{user.id}_email@example.com"
+
+ described_class.new(source, email_starting_with_number, :maintainer).execute
+
+ expect(source.members.invite.pluck(:invite_email)).to include(email_starting_with_number)
+ expect(source.users.reload).not_to include(user)
+ end
+ end
+ end
+
+ context 'when current_user can update member', :enable_admin_mode do
+ it 'creates the member' do
+ expect(source.users).not_to include(user)
+
+ described_class.new(source, user, :maintainer, current_user: admin).execute
+
+ expect(source.users.reload).to include(user)
+ end
+
+ context 'when called with a requester user object' do
+ before do
+ source.request_access(user)
+ end
+
+ it 'adds the requester as a member', :aggregate_failures do
+ expect(source.users).not_to include(user)
+ expect(source.requesters.exists?(user_id: user)).to be_truthy
+
+ described_class.new(source, user, :maintainer, current_user: admin).execute
+
+ expect(source.users.reload).to include(user)
+ expect(source.requesters.reload.exists?(user_id: user)).to be_falsy
+ end
+ end
+ end
+
+ context 'when current_user cannot update member' do
+ it 'does not create the member', :aggregate_failures do
+ expect(source.users).not_to include(user)
+
+ member = described_class.new(source, user, :maintainer, current_user: user).execute
+
+ expect(source.users.reload).not_to include(user)
+ expect(member).not_to be_persisted
+ end
+
+ context 'when called with a requester user object' do
+ before do
+ source.request_access(user)
+ end
+
+ it 'does not destroy the requester', :aggregate_failures do
+ expect(source.users).not_to include(user)
+ expect(source.requesters.exists?(user_id: user)).to be_truthy
+
+ described_class.new(source, user, :maintainer, current_user: user).execute
+
+ expect(source.users.reload).not_to include(user)
+ expect(source.requesters.exists?(user_id: user)).to be_truthy
+ end
+ end
+ end
+
+ context 'when member already exists' do
+ before do
+ source.add_user(user, :developer)
+ end
+
+ context 'with no current_user' do
+ it 'updates the member' do
+ expect(source.users).to include(user)
+
+ described_class.new(source, user, :maintainer).execute
+
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
+ end
+
+ context 'when current_user can update member', :enable_admin_mode do
+ it 'updates the member' do
+ expect(source.users).to include(user)
+
+ described_class.new(source, user, :maintainer, current_user: admin).execute
+
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
+ end
+
+ context 'when current_user cannot update member' do
+ it 'does not update the member' do
+ expect(source.users).to include(user)
+
+ described_class.new(source, user, :maintainer, current_user: user).execute
+
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+ end
+ end
+
+ describe '.add_users' do
+ let_it_be(:user1) { create(:user) }
+ let_it_be(:user2) { create(:user) }
+
+ it 'returns a Member objects' do
+ members = described_class.add_users(source, [user1, user2], :maintainer)
+
+ expect(members).to be_a Array
+ expect(members.size).to eq(2)
+ expect(members.first).to be_a member_type
+ expect(members.first).to be_persisted
+ end
+
+ it 'returns an empty array' do
+ members = described_class.add_users(source, [], :maintainer)
+
+ expect(members).to be_a Array
+ expect(members).to be_empty
+ end
+
+ it 'supports different formats' do
+ list = ['joe@local.test', admin, user1.id, user2.id.to_s]
+
+ members = described_class.add_users(source, list, :maintainer)
+
+ expect(members.size).to eq(4)
+ expect(members.first).to be_invite
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb b/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb
new file mode 100644
index 00000000000..c92e819db19
--- /dev/null
+++ b/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'ci_cd_settings delegation' do
+ let(:exclude_attributes) { [] }
+
+ context 'when ci_cd_settings is destroyed but project is not' do
+ it 'allows methods delegated to ci_cd_settings to be nil', :aggregate_failures do
+ project = create(:project)
+ attributes = project.ci_cd_settings.attributes.keys - %w(id project_id) - exclude_attributes
+ project.ci_cd_settings.destroy!
+ project.reload
+ attributes.each do |attr|
+ method = project.respond_to?("ci_#{attr}") ? "ci_#{attr}" : attr
+ expect(project.send(method)).to be_nil, "#{attr} was not nil"
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'a ci_cd_settings predicate method' do |prefix: ''|
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:project) { create(:project) }
+
+ context 'when ci_cd_settings is nil' do
+ before do
+ allow(project).to receive(:ci_cd_settings).and_return(nil)
+ end
+
+ it 'returns false' do
+ expect(project.send("#{prefix}#{delegated_method}")).to be(false)
+ end
+ end
+
+ context 'when ci_cd_settings is not nil' do
+ where(:delegated_method_return, :subject_return) do
+ true | true
+ false | false
+ end
+
+ with_them do
+ let(:ci_cd_settings_double) { double('ProjectCiCdSetting') }
+
+ before do
+ allow(project).to receive(:ci_cd_settings).and_return(ci_cd_settings_double)
+ allow(ci_cd_settings_double).to receive(delegated_method).and_return(delegated_method_return)
+ end
+
+ it 'returns the expected boolean value' do
+ expect(project.send("#{prefix}#{delegated_method}")).to be(subject_return)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index 2498bf35a09..bc5956e3eec 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -2,6 +2,7 @@
RSpec.shared_examples 'wiki model' do
let_it_be(:user) { create(:user, :commit_email) }
+
let(:wiki_container) { raise NotImplementedError }
let(:wiki_container_without_repo) { raise NotImplementedError }
let(:wiki_lfs_enabled) { false }
@@ -536,4 +537,98 @@ RSpec.shared_examples 'wiki model' do
expect(subject.hook_attrs.keys).to contain_exactly(:web_url, :git_ssh_url, :git_http_url, :path_with_namespace, :default_branch)
end
end
+
+ describe '#default_branch' do
+ subject { wiki.default_branch }
+
+ before do
+ allow(Gitlab::DefaultBranch).to receive(:value).and_return('main')
+ end
+
+ context 'when repository is not created' do
+ let(:wiki_container) { wiki_container_without_repo }
+
+ it 'returns the instance default branch' do
+ expect(subject).to eq 'main'
+ end
+ end
+
+ context 'when repository is empty' do
+ let(:wiki_container) { wiki_container_without_repo }
+
+ before do
+ wiki.repository.create_if_not_exists
+ end
+
+ it 'returns the instance default branch' do
+ expect(subject).to eq 'main'
+ end
+ end
+
+ context 'when repository is not empty' do
+ it 'returns the repository default branch' do
+ wiki.create_page('index', 'test content')
+
+ expect(subject).to eq wiki.repository.root_ref
+ end
+ end
+ end
+
+ describe '#create_wiki_repository' do
+ let(:head_path) { Rails.root.join(TestEnv.repos_path, "#{wiki.disk_path}.git", 'HEAD') }
+ let(:default_branch) { 'foo' }
+
+ before do
+ allow(Gitlab::CurrentSettings).to receive(:default_branch_name).and_return(default_branch)
+ end
+
+ subject { wiki.create_wiki_repository }
+
+ context 'when repository is not created' do
+ let(:wiki_container) { wiki_container_without_repo }
+
+ it 'changes the HEAD reference to the default branch' do
+ expect(wiki.empty?).to eq true
+
+ subject
+
+ expect(File.read(head_path).squish).to eq "ref: refs/heads/#{default_branch}"
+ end
+ end
+
+ context 'when repository is empty' do
+ let(:wiki_container) { wiki_container_without_repo }
+
+ it 'changes the HEAD reference to the default branch' do
+ wiki.repository.create_if_not_exists
+ wiki.repository.raw_repository.write_ref('HEAD', 'refs/heads/bar')
+
+ subject
+
+ expect(File.read(head_path).squish).to eq "ref: refs/heads/#{default_branch}"
+ end
+ end
+
+ context 'when repository is not empty' do
+ before do
+ wiki.create_page('index', 'test content')
+ end
+
+ it 'does nothing when HEAD points to the right branch' do
+ expect(wiki.repository.raw_repository).not_to receive(:write_ref)
+
+ subject
+ end
+
+ context 'when HEAD points to the wrong branch' do
+ it 'rewrites HEAD with the right branch' do
+ wiki.repository.raw_repository.write_ref('HEAD', 'refs/heads/bar')
+
+ subject
+
+ expect(File.read(head_path).squish).to eq "ref: refs/heads/#{default_branch}"
+ end
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb
index ccc64c80fd4..f09634556c3 100644
--- a/spec/support/shared_examples/namespaces/traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_examples.rb
@@ -12,16 +12,18 @@ RSpec.shared_examples 'namespace traversal' do
it "makes a recursive query" do
groups.each do |group|
- expect { group.public_send(recursive_method).load }.to make_queries_matching(/WITH RECURSIVE/)
+ expect { group.public_send(recursive_method).try(:load) }.to make_queries_matching(/WITH RECURSIVE/)
end
end
end
- describe '#root_ancestor' do
- let_it_be(:group) { create(:group) }
- let_it_be(:nested_group) { create(:group, parent: group) }
- let_it_be(:deep_nested_group) { create(:group, parent: nested_group) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:nested_group) { create(:group, parent: group) }
+ let_it_be(:deep_nested_group) { create(:group, parent: nested_group) }
+ let_it_be(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
+ let_it_be(:groups) { [group, nested_group, deep_nested_group, very_deep_nested_group] }
+ describe '#root_ancestor' do
it 'returns the correct root ancestor' do
expect(group.root_ancestor).to eq(group)
expect(nested_group.root_ancestor).to eq(group)
@@ -29,8 +31,6 @@ RSpec.shared_examples 'namespace traversal' do
end
describe '#recursive_root_ancestor' do
- let(:groups) { [group, nested_group, deep_nested_group] }
-
it "is equivalent to #recursive_root_ancestor" do
groups.each do |group|
expect(group.root_ancestor).to eq(group.recursive_root_ancestor)
@@ -40,12 +40,8 @@ RSpec.shared_examples 'namespace traversal' do
end
describe '#self_and_hierarchy' do
- let!(:group) { create(:group, path: 'git_lab') }
- let!(:nested_group) { create(:group, parent: group) }
- let!(:deep_nested_group) { create(:group, parent: nested_group) }
- let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
- let!(:another_group) { create(:group, path: 'gitllab') }
- let!(:another_group_nested) { create(:group, path: 'foo', parent: another_group) }
+ let!(:another_group) { create(:group) }
+ let!(:another_group_nested) { create(:group, parent: another_group) }
it 'returns the correct tree' do
expect(group.self_and_hierarchy).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group)
@@ -54,18 +50,11 @@ RSpec.shared_examples 'namespace traversal' do
end
describe '#recursive_self_and_hierarchy' do
- let(:groups) { [group, nested_group, very_deep_nested_group] }
-
it_behaves_like 'recursive version', :self_and_hierarchy
end
end
describe '#ancestors' do
- let_it_be(:group) { create(:group) }
- let_it_be(:nested_group) { create(:group, parent: group) }
- let_it_be(:deep_nested_group) { create(:group, parent: nested_group) }
- let_it_be(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
-
it 'returns the correct ancestors' do
# #reload is called to make sure traversal_ids are reloaded
expect(very_deep_nested_group.reload.ancestors).to contain_exactly(group, nested_group, deep_nested_group)
@@ -75,18 +64,28 @@ RSpec.shared_examples 'namespace traversal' do
end
describe '#recursive_ancestors' do
- let(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
+ let_it_be(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
it_behaves_like 'recursive version', :ancestors
end
end
- describe '#self_and_ancestors' do
- let(:group) { create(:group) }
- let(:nested_group) { create(:group, parent: group) }
- let(:deep_nested_group) { create(:group, parent: nested_group) }
- let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
+ describe '#ancestor_ids' do
+ it 'returns the correct ancestor ids' do
+ expect(very_deep_nested_group.ancestor_ids).to contain_exactly(group.id, nested_group.id, deep_nested_group.id)
+ expect(deep_nested_group.ancestor_ids).to contain_exactly(group.id, nested_group.id)
+ expect(nested_group.ancestor_ids).to contain_exactly(group.id)
+ expect(group.ancestor_ids).to be_empty
+ end
+
+ describe '#recursive_ancestor_ids' do
+ let_it_be(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
+
+ it_behaves_like 'recursive version', :ancestor_ids
+ end
+ end
+ describe '#self_and_ancestors' do
it 'returns the correct ancestors' do
expect(very_deep_nested_group.self_and_ancestors).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group)
expect(deep_nested_group.self_and_ancestors).to contain_exactly(group, nested_group, deep_nested_group)
@@ -95,19 +94,30 @@ RSpec.shared_examples 'namespace traversal' do
end
describe '#recursive_self_and_ancestors' do
- let(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
+ let_it_be(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
it_behaves_like 'recursive version', :self_and_ancestors
end
end
+ describe '#self_and_ancestor_ids' do
+ it 'returns the correct ancestor ids' do
+ expect(very_deep_nested_group.self_and_ancestor_ids).to contain_exactly(group.id, nested_group.id, deep_nested_group.id, very_deep_nested_group.id)
+ expect(deep_nested_group.self_and_ancestor_ids).to contain_exactly(group.id, nested_group.id, deep_nested_group.id)
+ expect(nested_group.self_and_ancestor_ids).to contain_exactly(group.id, nested_group.id)
+ expect(group.self_and_ancestor_ids).to contain_exactly(group.id)
+ end
+
+ describe '#recursive_self_and_ancestor_ids' do
+ let_it_be(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
+
+ it_behaves_like 'recursive version', :self_and_ancestor_ids
+ end
+ end
+
describe '#descendants' do
- let!(:group) { create(:group, path: 'git_lab') }
- let!(:nested_group) { create(:group, parent: group) }
- let!(:deep_nested_group) { create(:group, parent: nested_group) }
- let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
- let!(:another_group) { create(:group, path: 'gitllab') }
- let!(:another_group_nested) { create(:group, path: 'foo', parent: another_group) }
+ let!(:another_group) { create(:group) }
+ let!(:another_group_nested) { create(:group, parent: another_group) }
it 'returns the correct descendants' do
expect(very_deep_nested_group.descendants.to_a).to eq([])
@@ -117,19 +127,13 @@ RSpec.shared_examples 'namespace traversal' do
end
describe '#recursive_descendants' do
- let(:groups) { [group, nested_group, deep_nested_group, very_deep_nested_group] }
-
it_behaves_like 'recursive version', :descendants
end
end
describe '#self_and_descendants' do
- let!(:group) { create(:group, path: 'git_lab') }
- let!(:nested_group) { create(:group, parent: group) }
- let!(:deep_nested_group) { create(:group, parent: nested_group) }
- let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
- let!(:another_group) { create(:group, path: 'gitllab') }
- let!(:another_group_nested) { create(:group, path: 'foo', parent: another_group) }
+ let!(:another_group) { create(:group) }
+ let!(:another_group_nested) { create(:group, parent: another_group) }
it 'returns the correct descendants' do
expect(very_deep_nested_group.self_and_descendants).to contain_exactly(very_deep_nested_group)
@@ -139,24 +143,18 @@ RSpec.shared_examples 'namespace traversal' do
end
describe '#recursive_self_and_descendants' do
- let(:groups) { [group, nested_group, deep_nested_group, very_deep_nested_group] }
+ let_it_be(:groups) { [group, nested_group, deep_nested_group] }
it_behaves_like 'recursive version', :self_and_descendants
end
end
describe '#self_and_descendant_ids' do
- let!(:group) { create(:group, path: 'git_lab') }
- let!(:nested_group) { create(:group, parent: group) }
- let!(:deep_nested_group) { create(:group, parent: nested_group) }
-
subject { group.self_and_descendant_ids.pluck(:id) }
- it { is_expected.to contain_exactly(group.id, nested_group.id, deep_nested_group.id) }
+ it { is_expected.to contain_exactly(group.id, nested_group.id, deep_nested_group.id, very_deep_nested_group.id) }
describe '#recursive_self_and_descendant_ids' do
- let(:groups) { [group, nested_group, deep_nested_group] }
-
it_behaves_like 'recursive version', :self_and_descendant_ids
end
end
diff --git a/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb
index 3cdba315d1f..4d142199c95 100644
--- a/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb
@@ -233,6 +233,7 @@ RSpec.shared_examples 'issuable quick actions' do
context 'when user can update issuable' do
let_it_be(:developer) { create(:user) }
+
let(:note_author) { developer }
before do
@@ -260,6 +261,7 @@ RSpec.shared_examples 'issuable quick actions' do
context 'when user cannot update issuable' do
let_it_be(:non_member) { create(:user) }
+
let(:note_author) { non_member }
it 'applies commands that user can execute' do
diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
index 92849ddf1cb..052fd0622d0 100644
--- a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
@@ -72,6 +72,24 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type|
end
end
+ it 'shows the time tracking report when link is clicked' do
+ submit_time('/estimate 1w')
+ submit_time('/spend 1d')
+
+ wait_for_requests
+
+ page.within '.time-tracking-component-wrap' do
+ click_link 'Time tracking report'
+
+ wait_for_requests
+ end
+
+ page.within '#time-tracking-report' do
+ expect(find('tbody')).to have_content maintainer.name
+ expect(find('tbody')).to have_content '1d'
+ end
+ end
+
it 'hides the help state when close icon is clicked' do
page.within '.time-tracking-component-wrap' do
find('.help-button').click
diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
index 0530aa8c760..1f68dd7a382 100644
--- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
@@ -12,15 +12,17 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_
let_it_be(:user, freeze: true) { create(:user) }
let_it_be(:personal_access_token, freeze: true) { create(:personal_access_token, user: user) }
- let_it_be(:private_distribution, freeze: true) { create("debian_#{container_type}_distribution", container: private_container, codename: 'existing-codename') }
+ let_it_be(:private_distribution, freeze: true) { create("debian_#{container_type}_distribution", :with_file, container: private_container, codename: 'existing-codename') }
let_it_be(:private_component, freeze: true) { create("debian_#{container_type}_component", distribution: private_distribution, name: 'existing-component') }
let_it_be(:private_architecture_all, freeze: true) { create("debian_#{container_type}_architecture", distribution: private_distribution, name: 'all') }
let_it_be(:private_architecture, freeze: true) { create("debian_#{container_type}_architecture", distribution: private_distribution, name: 'existing-arch') }
+ let_it_be(:private_component_file) { create("debian_#{container_type}_component_file", component: private_component, architecture: private_architecture) }
- let_it_be(:public_distribution, freeze: true) { create("debian_#{container_type}_distribution", container: public_container, codename: 'existing-codename') }
+ let_it_be(:public_distribution, freeze: true) { create("debian_#{container_type}_distribution", :with_file, container: public_container, codename: 'existing-codename') }
let_it_be(:public_component, freeze: true) { create("debian_#{container_type}_component", distribution: public_distribution, name: 'existing-component') }
let_it_be(:public_architecture_all, freeze: true) { create("debian_#{container_type}_architecture", distribution: public_distribution, name: 'all') }
let_it_be(:public_architecture, freeze: true) { create("debian_#{container_type}_architecture", distribution: public_distribution, name: 'existing-arch') }
+ let_it_be(:public_component_file) { create("debian_#{container_type}_component_file", component: public_component, architecture: public_architecture) }
if container_type == :group
let_it_be(:private_project) { create(:project, :private, group: private_container) }
@@ -40,14 +42,15 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_
let(:visibility_level) { :public }
let(:distribution) { { private: private_distribution, public: public_distribution }[visibility_level] }
+ let(:architecture) { { private: private_architecture, public: public_architecture }[visibility_level] }
+ let(:component) { { private: private_component, public: public_component }[visibility_level] }
+ let(:component_file) { { private: private_component_file, public: public_component_file }[visibility_level] }
- let(:component) { 'main' }
- let(:architecture) { 'amd64' }
let(:source_package) { 'sample' }
let(:letter) { source_package[0..2] == 'lib' ? source_package[0..3] : source_package[0] }
let(:package_name) { 'libsample0' }
let(:package_version) { '1.2.3~alpha2' }
- let(:file_name) { "#{package_name}_#{package_version}_#{architecture}.deb" }
+ let(:file_name) { "#{package_name}_#{package_version}_#{architecture.name}.deb" }
let(:method) { :get }
diff --git a/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb
index 9cf5bc04f65..7e1f4500779 100644
--- a/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb
@@ -31,6 +31,28 @@ RSpec.shared_examples 'a noteable graphql type we can query' do
expect(graphql_data_at(*path_to_noteable, :discussions, :nodes))
.to match_array(expected)
end
+
+ it 'can fetch discussion noteable' do
+ create(discussion_factory, project: project, noteable: noteable)
+ fields =
+ <<-QL.strip_heredoc
+ discussions {
+ nodes {
+ noteable {
+ __typename
+ ... on #{noteable.class.name.demodulize} {
+ id
+ }
+ }
+ }
+ }
+ QL
+
+ post_graphql(query(fields), current_user: current_user)
+
+ data = graphql_data_at(*path_to_noteable, :discussions, :nodes, :noteable, :id)
+ expect(data[0]).to eq(global_id_of(noteable))
+ end
end
describe '.notes' do
diff --git a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
index 585c4fb8a4e..1ad38a17f9c 100644
--- a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-RSpec.shared_examples 'rejects helm packages access' do |user_type, status, add_member = true|
+RSpec.shared_examples 'rejects helm packages access' do |user_type, status|
context "for user type #{user_type}" do
before do
- project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if user_type != :anonymous && user_type != :not_a_member
end
it_behaves_like 'returning response status', status
@@ -18,19 +18,170 @@ RSpec.shared_examples 'rejects helm packages access' do |user_type, status, add_
end
end
-RSpec.shared_examples 'process helm download content request' do |user_type, status, add_member = true|
+RSpec.shared_examples 'process helm service index request' do |user_type, status|
context "for user type #{user_type}" do
before do
- project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if user_type != :anonymous && user_type != :not_a_member
end
- it_behaves_like 'returning response status', status
+ it 'returns a valid YAML response', :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+
+ expect(response.media_type).to eq('text/yaml')
+ expect(response.body).to start_with("---\napiVersion: v1\nentries:\n")
+
+ yaml_response = YAML.safe_load(response.body)
+
+ expect(yaml_response.keys).to contain_exactly('apiVersion', 'entries', 'generated', 'serverInfo')
+ expect(yaml_response['entries']).to be_a(Hash)
+ expect(yaml_response['entries'].keys).to contain_exactly(package.name)
+ expect(yaml_response['serverInfo']).to eq({ 'contextPath' => "/api/v4/projects/#{project.id}/packages/helm" })
+
+ package_entry = yaml_response['entries'][package.name]
+
+ expect(package_entry.length).to eq(1)
+ expect(package_entry.first.keys).to contain_exactly('name', 'version', 'apiVersion', 'created', 'digest', 'urls')
+ expect(package_entry.first['digest']).to eq('fd2b2fa0329e80a2a602c2bb3b40608bcd6ee5cf96cf46fd0d2800a4c129c9db')
+ expect(package_entry.first['urls']).to eq(["charts/#{package.name}-#{package.version}.tgz"])
+ end
+ end
+end
+
+RSpec.shared_examples 'process helm workhorse authorization' do |user_type, status, test_bypass: false|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if user_type != :anonymous && user_type != :not_a_member
+ end
+
+ it 'has the proper status and content type' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ 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
+ basic_auth_header(user.username, personal_access_token.token)
+ .merge(workhorse_headers)
+ .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 helm upload' do |user_type, status|
+ shared_examples 'creates helm package files' do
+ it 'creates package files' do
+ expect(::Packages::Helm::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.tgz')
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if user_type != :anonymous && user_type != :not_a_member
+ 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 helm package files'
+ it_behaves_like 'a package tracking event', 'API::HelmPackages', 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create( # rubocop:disable Rails/SaveBang
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { { chart: fog_file, 'chart.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 helm package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) do
+ {
+ chart: fog_file,
+ 'chart.remote_id' => remote_id
+ }
+ end
+
+ 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 'creates helm 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 helm package files'
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+end
+
+RSpec.shared_examples 'process helm download content request' do |user_type, status|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if user_type != :anonymous && user_type != :not_a_member
+ end
it_behaves_like 'a package tracking event', 'API::HelmPackages', 'pull_package'
- it 'returns a valid package archive' do
+ it 'returns expected status and a valid package archive' do
subject
+ expect(response).to have_gitlab_http_status(status)
expect(response.media_type).to eq('application/octet-stream')
end
end
@@ -51,3 +202,69 @@ RSpec.shared_examples 'rejects helm access with unknown project id' do
end
end
end
+
+RSpec.shared_examples 'handling helm chart index requests' do
+ context 'with valid project' do
+ subject { get api(url), headers: headers }
+
+ using RSpec::Parameterized::TableSyntax
+
+ context 'personal token' do
+ where(:visibility, :user_role, :shared_examples_name, :expected_status) do
+ :public | :guest | 'process helm service index request' | :success
+ :public | :not_a_member | 'process helm service index request' | :success
+ :public | :anonymous | 'process helm service index request' | :success
+ :private | :reporter | 'process helm service index request' | :success
+ :private | :guest | 'rejects helm packages access' | :forbidden
+ :private | :not_a_member | 'rejects helm packages access' | :not_found
+ :private | :anonymous | 'rejects helm packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) }
+
+ before do
+ project.update!(visibility: visibility.to_s)
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status]
+ end
+ end
+
+ context 'when an invalid token is passed' do
+ let(:headers) { basic_auth_header(user.username, 'wrong') }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+
+ context 'with job token' do
+ where(:visibility, :user_role, :shared_examples_name, :expected_status) do
+ :public | :guest | 'process helm service index request' | :success
+ :public | :not_a_member | 'process helm service index request' | :success
+ :public | :anonymous | 'process helm service index request' | :success
+ :private | :reporter | 'process helm service index request' | :success
+ :private | :guest | 'rejects helm packages access' | :forbidden
+ :private | :not_a_member | 'rejects helm packages access' | :not_found
+ :private | :anonymous | 'rejects helm packages access' | :unauthorized
+ end
+
+ with_them do
+ let_it_be(:ci_build) { create(:ci_build, project: project, user: user, status: :running) }
+
+ let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(ci_build) }
+
+ before do
+ project.update!(visibility: visibility.to_s)
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status]
+ end
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects helm access with unknown project id' do
+ subject { get api(url) }
+ end
+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
index 617fdecbb5b..878cbc10a24 100644
--- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -136,8 +136,8 @@ RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, sta
end
end
-RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = true|
- RSpec.shared_examples 'creates nuget package files' do
+RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = true, symbol_package = false|
+ shared_examples 'creates nuget package files' do
it 'creates package files' do
expect(::Packages::Nuget::ExtractionWorker).to receive(:perform_async).once
expect { subject }
@@ -146,7 +146,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
expect(response).to have_gitlab_http_status(status)
package_file = target.packages.last.package_files.reload.last
- expect(package_file.file_name).to eq('package.nupkg')
+ expect(package_file.file_name).to eq(file_name)
end
end
@@ -169,7 +169,12 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
context 'with correct params' do
it_behaves_like 'package workhorse uploads'
it_behaves_like 'creates nuget package files'
- it_behaves_like 'a package tracking event', 'API::NugetPackages', 'push_package'
+
+ if symbol_package
+ it_behaves_like 'a package tracking event', 'API::NugetPackages', 'push_symbol_package'
+ else
+ it_behaves_like 'a package tracking event', 'API::NugetPackages', 'push_package'
+ end
end
end
@@ -300,6 +305,18 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st
it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
end
+ context 'with symbol package' do
+ let(:format) { 'snupkg' }
+
+ it 'returns a valid package archive' do
+ subject
+
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it_behaves_like 'a package tracking event', 'API::NugetPackages', 'pull_symbol_package'
+ end
+
context 'with lower case package name' do
let_it_be(:package_name) { 'dummy.package' }
@@ -407,3 +424,114 @@ RSpec.shared_examples 'rejects nuget access with unknown target id' do
end
end
end
+
+RSpec.shared_examples 'nuget authorize upload endpoint' do
+ using RSpec::Parameterized::TableSyntax
+
+ context 'with valid project' do
+ where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success
+ 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_headers) }
+
+ before do
+ update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'job token for package uploads', authorize_endpoint: true do
+ let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
+ end
+
+ it_behaves_like 'rejects nuget access with unknown target id'
+
+ it_behaves_like 'rejects nuget access with invalid target id'
+end
+
+RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
+ using RSpec::Parameterized::TableSyntax
+
+ context 'with valid project' do
+ where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget upload' | :created
+ 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process nuget upload' | :created
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_headers) }
+ let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace } }
+
+ before do
+ update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member], symbol_package
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'job token for package uploads' do
+ let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
+ end
+
+ it_behaves_like 'rejects nuget access with unknown target id'
+
+ it_behaves_like 'rejects nuget access with invalid target id'
+
+ context 'file size above maximum limit' do
+ let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_headers) }
+
+ before do
+ allow_next_instance_of(UploadedFile) do |uploaded_file|
+ allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1)
+ end
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ 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
index 42c29084d7b..ecde4ee8565 100644
--- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -100,7 +100,7 @@ RSpec.shared_examples 'job token for package GET requests' do
end
end
-RSpec.shared_examples 'job token for package uploads' do |authorize_endpoint: false|
+RSpec.shared_examples 'job token for package uploads' do |authorize_endpoint: false, accept_invalid_username: false|
context 'with job token headers' do
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token).merge(workhorse_headers) }
@@ -133,7 +133,11 @@ RSpec.shared_examples 'job token for package uploads' do |authorize_endpoint: fa
context 'invalid user' do
let(:headers) { basic_auth_header('foo', job.token).merge(workhorse_headers) }
- it_behaves_like 'returning response status', :unauthorized
+ if accept_invalid_username
+ it_behaves_like 'returning response status', :success
+ else
+ it_behaves_like 'returning response status', :unauthorized
+ end
end
end
end
@@ -143,7 +147,7 @@ RSpec.shared_examples 'a package tracking event' do |category, action|
stub_feature_flags(collect_package_events: true)
end
- it "creates a gitlab tracking event #{action}", :snowplow do
+ it "creates a gitlab tracking event #{action}", :snowplow, :aggregate_failures do
expect { subject }.to change { Packages::Event.count }.by(1)
expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context)
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
index 218a3462c35..92a7d7ab3a3 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
@@ -13,7 +13,7 @@ RSpec.shared_examples 'creates an alert management alert or errors' do
it 'executes the alert service hooks' do
expect_next_instance_of(AlertManagement::Alert) do |alert|
- expect(alert).to receive(:execute_services)
+ expect(alert).to receive(:execute_integrations)
end
subject
@@ -84,7 +84,7 @@ end
# - `alert`, the alert for which events should be incremented
RSpec.shared_examples 'adds an alert management alert event' do
specify do
- expect(alert).not_to receive(:execute_services)
+ expect(alert).not_to receive(:execute_integrations)
expect { subject }.to change { alert.reload.events }.by(1)
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb
index c6ac07b6dd5..98834f01ce2 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb
@@ -20,7 +20,7 @@ end
RSpec.shared_examples 'processes incident issues' do |with_issue: false|
before do
allow_next_instance_of(AlertManagement::Alert) do |alert|
- allow(alert).to receive(:execute_services)
+ allow(alert).to receive(:execute_integrations)
end
end
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
index ba176b616c3..eafcbd77040 100644
--- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -157,8 +157,13 @@ end
RSpec.shared_examples 'a container registry auth service' do
include_context 'container registry auth service context'
+ before do
+ stub_feature_flags(container_registry_migration_phase1: false)
+ end
+
describe '#full_access_token' do
let_it_be(:project) { create(:project) }
+
let(:token) { described_class.full_access_token(project.full_path) }
subject { { token: token } }
@@ -172,6 +177,7 @@ RSpec.shared_examples 'a container registry auth service' do
describe '#pull_access_token' do
let_it_be(:project) { create(:project) }
+
let(:token) { described_class.pull_access_token(project.full_path) }
subject { { token: token } }
@@ -432,6 +438,7 @@ RSpec.shared_examples 'a container registry auth service' do
context 'for external user' do
context 'disallow anyone to pull or push images' do
let_it_be(:current_user) { create(:user, external: true) }
+
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull,push"] }
end
@@ -442,6 +449,7 @@ RSpec.shared_examples 'a container registry auth service' do
context 'disallow anyone to delete images' do
let_it_be(:current_user) { create(:user, external: true) }
+
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:*"] }
end
@@ -452,6 +460,7 @@ RSpec.shared_examples 'a container registry auth service' do
context 'disallow anyone to delete images since registry 2.7' do
let_it_be(:current_user) { create(:user, external: true) }
+
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:delete"] }
end
@@ -620,6 +629,22 @@ RSpec.shared_examples 'a container registry auth service' do
end
end
end
+
+ context 'for project with private container registry' do
+ let_it_be(:project, reload: true) { create(:project, :public) }
+
+ before do
+ project.project_feature.update!(container_registry_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it_behaves_like 'pullable for being team member'
+
+ context 'when you are admin' do
+ let_it_be(:current_user) { create(:admin) }
+
+ it_behaves_like 'pullable for being team member'
+ end
+ end
end
context 'when pushing' do
diff --git a/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb b/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb
index cbe5c7d89db..0151723793e 100644
--- a/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb
+++ b/spec/support/shared_examples/services/jira_import/user_mapper_services_shared_examples.rb
@@ -3,7 +3,7 @@
RSpec.shared_examples 'mapping jira users' do
let(:client) { double }
- let_it_be(:jira_service) { create(:jira_service, project: project, active: true) }
+ let_it_be(:jira_integration) { create(:jira_integration, project: project, active: true) }
before do
allow(subject).to receive(:client).and_return(client)
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
index 72878e925dc..6bc4f171d9c 100644
--- a/spec/support/shared_examples/services/packages_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -43,6 +43,7 @@ end
RSpec.shared_examples 'assigns status to package' do
context 'with status param' do
let_it_be(:status) { 'hidden' }
+
let(:params) { super().merge(status: status) }
it 'assigns the status to the package' do
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 275ddebc18c..14af35e58b7 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
@@ -123,9 +123,10 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
.with(repository.raw)
.and_raise(Gitlab::Git::CommandError)
- result = subject.execute
+ expect do
+ subject.execute
+ end.to raise_error(Gitlab::Git::CommandError)
- expect(result).to be_error
expect(project).not_to be_repository_read_only
expect(project.repository_storage).to eq('default')
expect(repository_storage_move).to be_failed
@@ -149,9 +150,10 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
expect(original_repository_double).to receive(:remove)
.and_raise(Gitlab::Git::CommandError)
- result = subject.execute
+ expect do
+ subject.execute
+ end.to raise_error(Gitlab::Git::CommandError)
- expect(result).to be_error
expect(repository_storage_move).to be_cleanup_failed
end
end
@@ -170,9 +172,10 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
allow(repository_double).to receive(:checksum)
.and_return('not matching checksum')
- result = subject.execute
+ expect do
+ subject.execute
+ end.to raise_error(UpdateRepositoryStorageMethods::Error, /Failed to verify \w+ repository checksum from \w+ to not matching checksum/)
- expect(result).to be_error
expect(project).not_to be_repository_read_only
expect(project.repository_storage).to eq('default')
end
diff --git a/spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb b/spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb
new file mode 100644
index 00000000000..8dcff99fb6f
--- /dev/null
+++ b/spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'complete service ping payload' do
+ it_behaves_like 'service ping payload with all expected metrics' do
+ let(:expected_metrics) do
+ standard_metrics + subscription_metrics + operational_metrics + optional_metrics
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/service_ping/service_ping_payload_with_all_expected_metrics_shared_examples.rb b/spec/support/shared_examples/services/service_ping/service_ping_payload_with_all_expected_metrics_shared_examples.rb
new file mode 100644
index 00000000000..535e7291b7e
--- /dev/null
+++ b/spec/support/shared_examples/services/service_ping/service_ping_payload_with_all_expected_metrics_shared_examples.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'service ping payload with all expected metrics' do
+ specify do
+ aggregate_failures do
+ expected_metrics.each do |metric|
+ is_expected.to have_usage_metric metric['key_path']
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/service_ping/service_ping_payload_without_restricted_metrics_shared_examples.rb b/spec/support/shared_examples/services/service_ping/service_ping_payload_without_restricted_metrics_shared_examples.rb
new file mode 100644
index 00000000000..9f18174cbc7
--- /dev/null
+++ b/spec/support/shared_examples/services/service_ping/service_ping_payload_without_restricted_metrics_shared_examples.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'service ping payload without restricted metrics' do
+ specify do
+ aggregate_failures do
+ restricted_metrics.each do |metric|
+ is_expected.not_to have_usage_metric metric['key_path']
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/snippets_shared_examples.rb b/spec/support/shared_examples/services/snippets_shared_examples.rb
index 0c4db7ded69..5a44f739b27 100644
--- a/spec/support/shared_examples/services/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/services/snippets_shared_examples.rb
@@ -1,23 +1,6 @@
# frozen_string_literal: true
RSpec.shared_examples 'checking spam' do
- let(:request) { double(:request, headers: headers) }
- let(:headers) { nil }
- let(:api) { true }
- let(:captcha_response) { 'abc123' }
- let(:spam_log_id) { 1 }
- let(:disable_spam_action_service) { false }
-
- let(:extra_opts) do
- {
- request: request,
- api: api,
- captcha_response: captcha_response,
- spam_log_id: spam_log_id,
- disable_spam_action_service: disable_spam_action_service
- }
- end
-
before do
allow_next_instance_of(UserAgentDetailService) do |instance|
allow(instance).to receive(:create)
@@ -25,73 +8,20 @@ RSpec.shared_examples 'checking spam' do
end
it 'executes SpamActionService' do
- spam_params = Spam::SpamParams.new(
- api: api,
- captcha_response: captcha_response,
- spam_log_id: spam_log_id
- )
expect_next_instance_of(
Spam::SpamActionService,
{
spammable: kind_of(Snippet),
- request: request,
+ spam_params: spam_params,
user: an_instance_of(User),
action: action
}
) do |instance|
- expect(instance).to receive(:execute).with(spam_params: spam_params)
+ expect(instance).to receive(:execute)
end
subject
end
-
- context 'when CAPTCHA arguments are passed in the headers' do
- let(:headers) do
- {
- 'X-GitLab-Spam-Log-Id' => spam_log_id,
- 'X-GitLab-Captcha-Response' => captcha_response
- }
- end
-
- let(:extra_opts) do
- {
- request: request,
- api: api,
- disable_spam_action_service: disable_spam_action_service
- }
- end
-
- it 'executes the SpamActionService correctly' do
- spam_params = Spam::SpamParams.new(
- api: api,
- captcha_response: captcha_response,
- spam_log_id: spam_log_id
- )
- expect_next_instance_of(
- Spam::SpamActionService,
- {
- spammable: kind_of(Snippet),
- request: request,
- user: an_instance_of(User),
- action: action
- }
- ) do |instance|
- expect(instance).to receive(:execute).with(spam_params: spam_params)
- end
-
- subject
- end
- end
-
- context 'when spam action service is disabled' do
- let(:disable_spam_action_service) { true }
-
- it 'request parameter is not passed to the service' do
- expect(Spam::SpamActionService).not_to receive(:new)
-
- subject
- end
- end
end
shared_examples 'invalid params error response' do
diff --git a/spec/support/shared_examples/services/wikis/create_attachment_service_shared_examples.rb b/spec/support/shared_examples/services/wikis/create_attachment_service_shared_examples.rb
index 555a6d5eed0..1646c18a0ed 100644
--- a/spec/support/shared_examples/services/wikis/create_attachment_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/wikis/create_attachment_service_shared_examples.rb
@@ -25,6 +25,25 @@ RSpec.shared_examples 'Wikis::CreateAttachmentService#execute' do |container_typ
container.add_developer(user)
end
+ context 'creates wiki repository if it does not exist' do
+ let(:container) { create(container_type) } # rubocop:disable Rails/SaveBang
+
+ it 'creates wiki repository' do
+ expect { service.execute }.to change { container.wiki.repository.exists? }.to(true)
+ end
+
+ context 'if an error is raised creating the repository' do
+ it 'catches error and return gracefully' do
+ allow(container.wiki).to receive(:repository_exists?).and_return(false)
+
+ result = service.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'Error creating the wiki repository'
+ end
+ end
+ end
+
context 'creates branch if it does not exists' do
let(:branch_name) { 'new_branch' }
let(:opts) { file_opts.merge(branch_name: branch_name) }
diff --git a/spec/support/shared_examples/workers/in_product_marketing_email_shared_example.rb b/spec/support/shared_examples/workers/in_product_marketing_email_shared_example.rb
deleted file mode 100644
index c4391f61369..00000000000
--- a/spec/support/shared_examples/workers/in_product_marketing_email_shared_example.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'in-product marketing email' do
- before do
- stub_application_setting(in_product_marketing_emails_enabled: in_product_marketing_emails_enabled)
- stub_experiment(in_product_marketing_emails: experiment_active)
- allow(::Gitlab).to receive(:com?).and_return(is_gitlab_com)
- end
-
- it 'executes the email service service' do
- expect(Namespaces::InProductMarketingEmailsService).to receive(:send_for_all_tracks_and_intervals).exactly(executes_service).times
-
- subject.perform
- end
-end
diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb
index 374997af1ec..dc475b92c0b 100644
--- a/spec/support/sidekiq.rb
+++ b/spec/support/sidekiq.rb
@@ -20,4 +20,25 @@ RSpec.configure do |config|
config.around(:example, :sidekiq_inline) do |example|
gitlab_sidekiq_inline { example.run }
end
+
+ # Some specs need to run mailers through Sidekiq explicitly, rather
+ # than the ActiveJob test adapter. There is a Rails bug that means we
+ # have to do some extra steps to make this happen:
+ # https://github.com/rails/rails/issues/37270
+ #
+ # In particular, we can't use an `around` hook because then the 'before' part
+ # of that will run before the `before_setup` hook in ActiveJob::TestHelper,
+ # which doesn't do what we want.
+ #
+ config.before(:example, :sidekiq_mailers) do
+ queue_adapter_changed_jobs.each { |k| k.queue_adapter = :sidekiq }
+ queue_adapter_changed_jobs.each(&:disable_test_adapter)
+ end
+
+ config.after(:example, :sidekiq_mailers) do
+ queue_adapter_changed_jobs.each do |klass|
+ klass.queue_adapter = :test
+ klass.enable_test_adapter(ActiveJob::QueueAdapters::TestAdapter.new)
+ end
+ end
end