summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-03-18 20:02:30 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-18 20:02:30 +0000
commit41fe97390ceddf945f3d967b8fdb3de4c66b7dea (patch)
tree9c8d89a8624828992f06d892cd2f43818ff5dcc8 /spec/support
parent0804d2dc31052fb45a1efecedc8e06ce9bc32862 (diff)
downloadgitlab-ce-7fd8f62e898848bf3d8f058077d7756742ae3bb0.tar.gz
Add latest changes from gitlab-org/gitlab@14-9-stable-eev14.9.0-rc42
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/enable_multiple_database_metrics_by_default.rb8
-rw-r--r--spec/support/event_store.rb7
-rw-r--r--spec/support/helpers/ci/template_helpers.rb2
-rw-r--r--spec/support/helpers/content_security_policy_helpers.rb20
-rw-r--r--spec/support/helpers/database_connection_helpers.rb11
-rw-r--r--spec/support/helpers/graphql_helpers.rb6
-rw-r--r--spec/support/helpers/migrations_helpers.rb8
-rw-r--r--spec/support/helpers/navbar_structure_helper.rb8
-rw-r--r--spec/support/helpers/next_found_instance_of.rb32
-rw-r--r--spec/support/helpers/search_helpers.rb6
-rw-r--r--spec/support/helpers/sorting_helper.rb1
-rw-r--r--spec/support/helpers/stub_configuration.rb16
-rw-r--r--spec/support/helpers/terms_helper.rb4
-rw-r--r--spec/support/helpers/test_env.rb5
-rw-r--r--spec/support/helpers/usage_data_helpers.rb1
-rw-r--r--spec/support/matchers/be_color.rb20
-rw-r--r--spec/support/matchers/event_store.rb37
-rw-r--r--spec/support/matchers/pushed_frontend_feature_flags_matcher.rb8
-rw-r--r--spec/support/sentry.rb13
-rw-r--r--spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb4
-rw-r--r--spec/support/shared_contexts/container_repositories_shared_context.rb9
-rw-r--r--spec/support/shared_contexts/lib/container_registry/client_stubs_shared_context.rb20
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb3
-rw-r--r--spec/support/shared_contexts/spam_constants.rb5
-rw-r--r--spec/support/shared_examples/attention_request_cache_invalidation_examples.rb15
-rw-r--r--spec/support/shared_examples/blocks_unsafe_serialization_shared_examples.rb26
-rw-r--r--spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb30
-rw-r--r--spec/support/shared_examples/controllers/rate_limited_endpoint_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/controllers/unique_hll_events_examples.rb6
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb30
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/clusters_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/features/container_registry_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/manage_applications_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb47
-rw-r--r--spec/support/shared_examples/features/project_upload_files_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/members_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb57
-rw-r--r--spec/support/shared_examples/integrations/integration_settings_form.rb11
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/usage_counter_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/lib/wikis_api_examples.rb76
-rw-r--r--spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/models/concerns/limitable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/concerns/timebox_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/models/concerns/update_namespace_statistics_shared_examples.rb54
-rw-r--r--spec/support/shared_examples/models/issuable_link_shared_examples.rb65
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb45
-rw-r--r--spec/support/shared_examples/models/resource_event_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/models/runners_token_prefix_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/namespaces/traversal_scope_examples.rb26
-rw-r--r--spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/requests/clusters/certificate_based_clusters_feature_flag_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/row_lock_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/sends_git_audit_streaming_event_shared_examples.rb61
-rw-r--r--spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/serializers/note_entity_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/services/incident_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb133
-rw-r--r--spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/services/rate_limited_service_shared_examples.rb73
-rw-r--r--spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb198
-rw-r--r--spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb70
-rw-r--r--spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb15
-rw-r--r--spec/support/silence_stdout.rb12
-rw-r--r--spec/support/view_component.rb7
78 files changed, 1473 insertions, 294 deletions
diff --git a/spec/support/enable_multiple_database_metrics_by_default.rb b/spec/support/enable_multiple_database_metrics_by_default.rb
deleted file mode 100644
index 6eeb4acd3d6..00000000000
--- a/spec/support/enable_multiple_database_metrics_by_default.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.configure do |config|
- config.before do
- # Enable this by default in all tests so it behaves like a FF
- stub_env('GITLAB_MULTIPLE_DATABASE_METRICS', '1')
- end
-end
diff --git a/spec/support/event_store.rb b/spec/support/event_store.rb
new file mode 100644
index 00000000000..057a5550746
--- /dev/null
+++ b/spec/support/event_store.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ config.before(:each, :event_store_publisher) do
+ allow(Gitlab::EventStore).to receive(:publish)
+ end
+end
diff --git a/spec/support/helpers/ci/template_helpers.rb b/spec/support/helpers/ci/template_helpers.rb
index 7bab58a574e..598a5a0becc 100644
--- a/spec/support/helpers/ci/template_helpers.rb
+++ b/spec/support/helpers/ci/template_helpers.rb
@@ -3,7 +3,7 @@
module Ci
module TemplateHelpers
def secure_analyzers_prefix
- 'registry.gitlab.com/gitlab-org/security-products/analyzers'
+ 'registry.gitlab.com/security-products'
end
end
end
diff --git a/spec/support/helpers/content_security_policy_helpers.rb b/spec/support/helpers/content_security_policy_helpers.rb
new file mode 100644
index 00000000000..c9f15e65c74
--- /dev/null
+++ b/spec/support/helpers/content_security_policy_helpers.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module ContentSecurityPolicyHelpers
+ # Expecting 2 calls to current_content_security_policy by default, once for
+ # the call that's being tested and once for the call in ApplicationController
+ def setup_csp_for_controller(controller_class, times = 2)
+ expect_next_instance_of(controller_class) do |controller|
+ expect(controller).to receive(:current_content_security_policy)
+ .and_return(ActionDispatch::ContentSecurityPolicy.new).exactly(times).times
+ end
+ end
+
+ # Expecting 2 calls to current_content_security_policy by default, once for
+ # the call that's being tested and once for the call in ApplicationController
+ def setup_existing_csp_for_controller(controller_class, csp, times = 2)
+ expect_next_instance_of(controller_class) do |controller|
+ expect(controller).to receive(:current_content_security_policy).and_return(csp).exactly(times).times
+ end
+ end
+end
diff --git a/spec/support/helpers/database_connection_helpers.rb b/spec/support/helpers/database_connection_helpers.rb
deleted file mode 100644
index 10ea7b5de91..00000000000
--- a/spec/support/helpers/database_connection_helpers.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module DatabaseConnectionHelpers
- def run_with_new_database_connection
- pool = ActiveRecord::Base.connection_pool
- conn = pool.checkout
- yield conn
- ensure
- pool.checkin(conn)
- end
-end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 8b7d1c753d5..ff8908e531a 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -552,6 +552,12 @@ module GraphqlHelpers
expect(flattened_errors).to be_empty
end
+ # Helps migrate to the new GraphQL interpreter,
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/210556
+ def expect_graphql_error_to_be_created(error_class, match_message = nil)
+ expect { yield }.to raise_error(error_class, match_message)
+ end
+
def flattened_errors
Array.wrap(graphql_errors).flatten.compact
end
diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb
index 0c5bf09f6b7..afa7ee84bda 100644
--- a/spec/support/helpers/migrations_helpers.rb
+++ b/spec/support/helpers/migrations_helpers.rb
@@ -13,6 +13,8 @@ module MigrationsHelpers
def self.name
table_name.singularize.camelcase
end
+
+ yield self if block_given?
end
end
@@ -104,9 +106,9 @@ module MigrationsHelpers
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
- def previous_migration
- migrations.each_cons(2) do |previous, migration|
- break previous if migration.name == described_class.name
+ def previous_migration(steps_back = 2)
+ migrations.each_cons(steps_back) do |cons|
+ break cons.first if cons.last.name == described_class.name
end
end
diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb
index 6fa69cbd6ad..fb06ebfdae2 100644
--- a/spec/support/helpers/navbar_structure_helper.rb
+++ b/spec/support/helpers/navbar_structure_helper.rb
@@ -77,6 +77,14 @@ module NavbarStructureHelper
)
end
+ def insert_harbor_registry_nav(within)
+ insert_after_sub_nav_item(
+ within,
+ within: _('Packages & Registries'),
+ new_sub_nav_item_name: _('Harbor Registry')
+ )
+ end
+
def insert_infrastructure_google_cloud_nav
insert_after_sub_nav_item(
_('Terraform'),
diff --git a/spec/support/helpers/next_found_instance_of.rb b/spec/support/helpers/next_found_instance_of.rb
index c8cdbaf2c5d..c7079e64ffd 100644
--- a/spec/support/helpers/next_found_instance_of.rb
+++ b/spec/support/helpers/next_found_instance_of.rb
@@ -2,19 +2,36 @@
module NextFoundInstanceOf
ERROR_MESSAGE = 'NextFoundInstanceOf mock helpers can only be used with ActiveRecord targets'
+ HELPER_METHOD_PATTERN = /(?:allow|expect)_next_found_(?<number>\d+)_instances_of/.freeze
- def expect_next_found_instance_of(klass)
+ def method_missing(method_name, ...)
+ return super unless match_data = method_name.match(HELPER_METHOD_PATTERN)
+
+ helper_method = method_name.to_s.sub("_#{match_data[:number]}", '')
+
+ public_send(helper_method, *args, match_data[:number].to_i, &block)
+ end
+
+ def expect_next_found_instance_of(klass, &block)
+ expect_next_found_instances_of(klass, nil, &block)
+ end
+
+ def expect_next_found_instances_of(klass, number)
check_if_active_record!(klass)
- stub_allocate(expect(klass), klass) do |expectation|
+ stub_allocate(expect(klass), klass, number) do |expectation|
yield(expectation)
end
end
- def allow_next_found_instance_of(klass)
+ def allow_next_found_instance_of(klass, &block)
+ allow_next_found_instances_of(klass, nil, &block)
+ end
+
+ def allow_next_found_instances_of(klass, number)
check_if_active_record!(klass)
- stub_allocate(allow(klass), klass) do |allowance|
+ stub_allocate(allow(klass), klass, number) do |allowance|
yield(allowance)
end
end
@@ -25,8 +42,11 @@ module NextFoundInstanceOf
raise ArgumentError, ERROR_MESSAGE unless klass < ActiveRecord::Base
end
- def stub_allocate(target, klass)
- target.to receive(:allocate).and_wrap_original do |method|
+ def stub_allocate(target, klass, number)
+ stub = receive(:allocate)
+ stub.exactly(number).times if number
+
+ target.to stub.and_wrap_original do |method|
method.call.tap do |allocation|
# ActiveRecord::Core.allocate returns a frozen object:
# https://github.com/rails/rails/blob/291a3d2ef29a3842d1156ada7526f4ee60dd2b59/activerecord/lib/active_record/core.rb#L620
diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb
index 3d4ff4801a7..f5a1a97a1d0 100644
--- a/spec/support/helpers/search_helpers.rb
+++ b/spec/support/helpers/search_helpers.rb
@@ -11,8 +11,12 @@ module SearchHelpers
end
def submit_search(query)
- page.within('.search-form, .search-page-form') do
+ # Once the `new_header_search` feature flag has been removed
+ # We can remove the `.search-form` selector
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/339348
+ page.within('.header-search, .search-form, .search-page-form') do
field = find_field('search')
+ field.click
field.fill_in(with: query)
if javascript_test?
diff --git a/spec/support/helpers/sorting_helper.rb b/spec/support/helpers/sorting_helper.rb
index f19f8c12928..6ff6dbb7800 100644
--- a/spec/support/helpers/sorting_helper.rb
+++ b/spec/support/helpers/sorting_helper.rb
@@ -26,6 +26,7 @@ module SortingHelper
include Comparable
attr_reader :value
+
delegate :==, :eql?, :hash, to: :value
def initialize(value)
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 8c60dc30cdb..20f46396424 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -90,10 +90,18 @@ module StubConfiguration
allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages))
end
- def stub_sentry_settings
- allow(Gitlab.config.sentry).to receive(:enabled).and_return(true)
- allow(Gitlab.config.sentry).to receive(:dsn).and_return('dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42')
- allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return('dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/43')
+ def stub_sentry_settings(enabled: true)
+ allow(Gitlab.config.sentry).to receive(:enabled) { enabled }
+ allow(Gitlab::CurrentSettings).to receive(:sentry_enabled?) { enabled }
+
+ dsn = 'dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42'
+ allow(Gitlab.config.sentry).to receive(:dsn) { dsn }
+ allow(Gitlab::CurrentSettings).to receive(:sentry_dsn) { dsn }
+
+ clientside_dsn = 'dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/43'
+ allow(Gitlab.config.sentry).to receive(:clientside_dsn) { clientside_dsn }
+ allow(Gitlab::CurrentSettings)
+ .to receive(:sentry_clientside_dsn) { clientside_dsn }
end
def stub_kerberos_setting(messages)
diff --git a/spec/support/helpers/terms_helper.rb b/spec/support/helpers/terms_helper.rb
index a61bae18f9a..2547ea62e37 100644
--- a/spec/support/helpers/terms_helper.rb
+++ b/spec/support/helpers/terms_helper.rb
@@ -15,7 +15,9 @@ module TermsHelper
end
def expect_to_be_on_terms_page
- expect(current_path).to eq terms_path
+ expect(page).to have_current_path terms_path, ignore_query: true
expect(page).to have_content('Please accept the Terms of Service before continuing.')
end
end
+
+TermsHelper.prepend_mod
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 18c25f4b770..587d4e22828 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -54,7 +54,7 @@ module TestEnv
'wip' => 'b9238ee',
'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3',
- 'add-ipython-files' => '532c837',
+ 'add-ipython-files' => 'a867a602',
'add-pdf-file' => 'e774ebd',
'squash-large-files' => '54cec52',
'add-pdf-text-binary' => '79faa7b',
@@ -80,7 +80,8 @@ module TestEnv
'invalid-utf8-diff-paths' => '99e4853',
'compare-with-merge-head-source' => 'f20a03d',
'compare-with-merge-head-target' => '2f1e176',
- 'trailers' => 'f0a5ed6'
+ 'trailers' => 'f0a5ed6',
+ 'add_commit_with_5mb_subject' => '8cf8e80'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index 776ea37ffdc..b9f90b11a69 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -129,6 +129,7 @@ module UsageDataHelpers
uploads
web_hooks
user_preferences_user_gitpod_enabled
+ service_usage_data_download_payload_click
).push(*SMAU_KEYS)
USAGE_DATA_KEYS = %i(
diff --git a/spec/support/matchers/be_color.rb b/spec/support/matchers/be_color.rb
new file mode 100644
index 00000000000..8fe29d003f9
--- /dev/null
+++ b/spec/support/matchers/be_color.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# Assert that this value is a valid color equal to the argument
+#
+# ```
+# expect(value).to be_color('#fff')
+# ```
+RSpec::Matchers.define :be_color do |expected|
+ match do |actual|
+ next false unless actual.present?
+
+ if expected
+ ::Gitlab::Color.of(actual) == ::Gitlab::Color.of(expected)
+ else
+ ::Gitlab::Color.of(actual).valid?
+ end
+ end
+end
+
+RSpec::Matchers.alias_matcher :a_valid_color, :be_color
diff --git a/spec/support/matchers/event_store.rb b/spec/support/matchers/event_store.rb
index 96a71ae3c22..eb5b37f39e5 100644
--- a/spec/support/matchers/event_store.rb
+++ b/spec/support/matchers/event_store.rb
@@ -1,12 +1,39 @@
# frozen_string_literal: true
-RSpec::Matchers.define :event_type do |event_class|
- match do |actual|
- actual.instance_of?(event_class) &&
- actual.data == @expected_data
+RSpec::Matchers.define :publish_event do |expected_event_class|
+ supports_block_expectations
+
+ match do |proc|
+ raise ArgumentError, 'This matcher only supports block expectation' unless proc.respond_to?(:call)
+
+ @events ||= []
+
+ allow(Gitlab::EventStore).to receive(:publish) do |published_event|
+ @events << published_event
+ end
+
+ proc.call
+
+ @events.any? do |event|
+ event.instance_of?(expected_event_class) && event.data == @expected_data
+ end
end
- chain :containing do |expected_data|
+ chain :with do |expected_data|
@expected_data = expected_data
end
+
+ failure_message do
+ "expected #{expected_event_class} with #{@expected_data} to be published, but got #{@events}"
+ end
+
+ match_when_negated do |proc|
+ raise ArgumentError, 'This matcher only supports block expectation' unless proc.respond_to?(:call)
+
+ allow(Gitlab::EventStore).to receive(:publish)
+
+ proc.call
+
+ expect(Gitlab::EventStore).not_to have_received(:publish).with(instance_of(expected_event_class))
+ end
end
diff --git a/spec/support/matchers/pushed_frontend_feature_flags_matcher.rb b/spec/support/matchers/pushed_frontend_feature_flags_matcher.rb
index b49d4da8cda..ecd174edec9 100644
--- a/spec/support/matchers/pushed_frontend_feature_flags_matcher.rb
+++ b/spec/support/matchers/pushed_frontend_feature_flags_matcher.rb
@@ -5,15 +5,19 @@ RSpec::Matchers.define :have_pushed_frontend_feature_flags do |expected|
"\"#{key}\":#{value}"
end
+ def html(actual)
+ actual.try(:html) || actual
+ end
+
match do |actual|
expected.all? do |feature_flag_name, enabled|
- page.html.include?(to_js(feature_flag_name, enabled))
+ html(actual).include?(to_js(feature_flag_name, enabled))
end
end
failure_message do |actual|
missing = expected.select do |feature_flag_name, enabled|
- !page.html.include?(to_js(feature_flag_name, enabled))
+ !html(actual).include?(to_js(feature_flag_name, enabled))
end
formatted_missing_flags = missing.map { |feature_flag_name, enabled| to_js(feature_flag_name, enabled) }.join("\n")
diff --git a/spec/support/sentry.rb b/spec/support/sentry.rb
new file mode 100644
index 00000000000..c439b6c0fd9
--- /dev/null
+++ b/spec/support/sentry.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ config.around(:example, :sentry) do |example|
+ dsn = Sentry.get_current_client.configuration.dsn
+ Sentry.get_current_client.configuration.dsn = 'dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42'
+ begin
+ example.run
+ ensure
+ Sentry.get_current_client.configuration.dsn = dsn.to_s.presence
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb b/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
index bfb719fd840..f5aa4178ae6 100644
--- a/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
+++ b/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
@@ -10,7 +10,7 @@ RSpec.shared_examples 'allowed user IDs are cached' do
end
it 'caches the allowed user IDs in L1 cache for 1 minute', :use_clean_rails_memory_store_caching do
- Timecop.travel 2.minutes do
+ travel_to 2.minutes.from_now do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).to receive(:fetch).and_call_original
@@ -20,7 +20,7 @@ RSpec.shared_examples 'allowed user IDs are cached' do
end
it 'caches the allowed user IDs in L2 cache for 5 minutes', :use_clean_rails_memory_store_caching do
- Timecop.travel 6.minutes do
+ travel_to 6.minutes.from_now do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).to receive(:fetch).and_call_original
diff --git a/spec/support/shared_contexts/container_repositories_shared_context.rb b/spec/support/shared_contexts/container_repositories_shared_context.rb
index 7f61631dce0..9a9f80a3cbd 100644
--- a/spec/support/shared_contexts/container_repositories_shared_context.rb
+++ b/spec/support/shared_contexts/container_repositories_shared_context.rb
@@ -1,13 +1,16 @@
# frozen_string_literal: true
RSpec.shared_context 'importable repositories' do
- let_it_be(:project) { create(:project) }
+ let_it_be(:root_group) { create(:group) }
+ let_it_be(:group) { create(:group, parent_id: root_group.id) }
+ let_it_be(:project) { create(:project, namespace: group) }
let_it_be(:valid_container_repository) { create(:container_repository, project: project, created_at: 2.days.ago) }
let_it_be(:valid_container_repository2) { create(:container_repository, project: project, created_at: 1.year.ago) }
let_it_be(:importing_container_repository) { create(:container_repository, :importing, project: project, created_at: 2.days.ago) }
let_it_be(:new_container_repository) { create(:container_repository, project: project) }
- let_it_be(:denied_group) { create(:group) }
+ let_it_be(:denied_root_group) { create(:group) }
+ let_it_be(:denied_group) { create(:group, parent_id: denied_root_group.id) }
let_it_be(:denied_project) { create(:project, group: denied_group) }
let_it_be(:denied_container_repository) { create(:container_repository, project: denied_project, created_at: 2.days.ago) }
@@ -21,7 +24,7 @@ RSpec.shared_context 'importable repositories' do
Feature::FlipperGate.create!(
feature_key: 'container_registry_phase_2_deny_list',
key: 'actors',
- value: "Group:#{denied_group.id}"
+ value: "Group:#{denied_root_group.id}"
)
end
end
diff --git a/spec/support/shared_contexts/lib/container_registry/client_stubs_shared_context.rb b/spec/support/shared_contexts/lib/container_registry/client_stubs_shared_context.rb
new file mode 100644
index 00000000000..d857e683aa2
--- /dev/null
+++ b/spec/support/shared_contexts/lib/container_registry/client_stubs_shared_context.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'container registry client stubs' do
+ def stub_container_registry_gitlab_api_support(supported: true)
+ allow_next_instance_of(ContainerRegistry::GitlabApiClient) do |client|
+ allow(client).to receive(:supports_gitlab_api?).and_return(supported)
+ yield client if block_given?
+ end
+ end
+
+ def stub_container_registry_gitlab_api_repository_details(client, path:, size_bytes:)
+ allow(client).to receive(:repository_details).with(path, with_size: true).and_return('size_bytes' => size_bytes)
+ end
+
+ def stub_container_registry_gitlab_api_network_error(client_method: :supports_gitlab_api?)
+ allow_next_instance_of(ContainerRegistry::GitlabApiClient) do |client|
+ allow(client).to receive(client_method).and_raise(::Faraday::Error, nil, nil)
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 576a8aa44fa..b4a71f52092 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -22,7 +22,6 @@ RSpec.shared_context 'project navbar structure' do
nav_sub_items: [
_('Activity'),
_('Labels'),
- _('Planning hierarchy'),
_('Members')
]
},
@@ -204,7 +203,7 @@ RSpec.shared_context 'group navbar structure' do
nav_sub_items: []
},
{
- nav_item: _('Group information'),
+ nav_item: group.root? ? _('Group information') : _('Subgroup information'),
nav_sub_items: [
_('Activity'),
_('Labels'),
diff --git a/spec/support/shared_contexts/spam_constants.rb b/spec/support/shared_contexts/spam_constants.rb
index e88a7c1b0df..03c5caa13b2 100644
--- a/spec/support/shared_contexts/spam_constants.rb
+++ b/spec/support/shared_contexts/spam_constants.rb
@@ -2,10 +2,11 @@
RSpec.shared_context 'includes Spam constants' do
before do
- stub_const('CONDITIONAL_ALLOW', Spam::SpamConstants::CONDITIONAL_ALLOW)
+ stub_const('BLOCK_USER', Spam::SpamConstants::BLOCK_USER)
stub_const('DISALLOW', Spam::SpamConstants::DISALLOW)
+ stub_const('CONDITIONAL_ALLOW', Spam::SpamConstants::CONDITIONAL_ALLOW)
+ stub_const('OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM', Spam::SpamConstants::OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM)
stub_const('ALLOW', Spam::SpamConstants::ALLOW)
- stub_const('BLOCK_USER', Spam::SpamConstants::BLOCK_USER)
stub_const('NOOP', Spam::SpamConstants::NOOP)
end
end
diff --git a/spec/support/shared_examples/attention_request_cache_invalidation_examples.rb b/spec/support/shared_examples/attention_request_cache_invalidation_examples.rb
new file mode 100644
index 00000000000..7fe696abc69
--- /dev/null
+++ b/spec/support/shared_examples/attention_request_cache_invalidation_examples.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'invalidates attention request cache' do
+ it 'invalidates the merge requests requiring attention count' do
+ cache_mock = double
+
+ users.each do |user|
+ expect(cache_mock).to receive(:delete).with(['users', user.id, 'attention_requested_open_merge_requests_count'])
+ end
+
+ allow(Rails).to receive(:cache).and_return(cache_mock)
+
+ service.execute
+ end
+end
diff --git a/spec/support/shared_examples/blocks_unsafe_serialization_shared_examples.rb b/spec/support/shared_examples/blocks_unsafe_serialization_shared_examples.rb
new file mode 100644
index 00000000000..db42e41344f
--- /dev/null
+++ b/spec/support/shared_examples/blocks_unsafe_serialization_shared_examples.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# Requires a context with:
+# - object
+#
+RSpec.shared_examples 'blocks unsafe serialization' do
+ it 'blocks as_json' do
+ expect { object.as_json }.to raise_error(described_class::UnsafeSerializationError, /#{object.class.name}/)
+ end
+
+ it 'blocks to_json' do
+ expect { object.to_json }.to raise_error(described_class::UnsafeSerializationError, /#{object.class.name}/)
+ end
+end
+
+RSpec.shared_examples 'allows unsafe serialization' do
+ it 'allows as_json' do
+ expect { object.as_json }.not_to raise_error
+ end
+
+ it 'allows to_json' do
+ expect { object.to_json }.not_to raise_error
+ end
+end
diff --git a/spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb b/spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb
index aa17e72d08e..9fab7f3f94e 100644
--- a/spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb
@@ -27,3 +27,33 @@ RSpec.shared_examples 'GET new cluster shared examples' do
end
end
end
+
+RSpec.shared_examples ':certificate_based_clusters feature flag index responses' do
+ context 'feature flag is disabled' do
+ before do
+ stub_feature_flags(certificate_based_clusters: false)
+ end
+
+ it 'does not list any clusters' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ expect(assigns(:clusters)).to be_empty
+ end
+ end
+end
+
+RSpec.shared_examples ':certificate_based_clusters feature flag controller responses' do
+ context 'feature flag is disabled' do
+ before do
+ stub_feature_flags(certificate_based_clusters: false)
+ end
+
+ it 'responds with :not_found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/rate_limited_endpoint_shared_examples.rb b/spec/support/shared_examples/controllers/rate_limited_endpoint_shared_examples.rb
index bb2a4159071..20edca1ee9f 100644
--- a/spec/support/shared_examples/controllers/rate_limited_endpoint_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/rate_limited_endpoint_shared_examples.rb
@@ -13,10 +13,16 @@ RSpec.shared_examples 'rate limited endpoint' do |rate_limit_key:|
env: :"#{rate_limit_key}_request_limit",
remote_ip: kind_of(String),
request_method: kind_of(String),
- path: kind_of(String),
- user_id: current_user.id,
- username: current_user.username
- }
+ path: kind_of(String)
+ }.merge(expected_user_attributes)
+ end
+
+ let(:expected_user_attributes) do
+ if defined?(current_user) && current_user.present?
+ { user_id: current_user.id, username: current_user.username }
+ else
+ {}
+ end
end
let(:error_message) { _('This endpoint has been requested too many times. Try again later.') }
diff --git a/spec/support/shared_examples/controllers/unique_hll_events_examples.rb b/spec/support/shared_examples/controllers/unique_hll_events_examples.rb
index 842ad89bafd..38c3157e898 100644
--- a/spec/support/shared_examples/controllers/unique_hll_events_examples.rb
+++ b/spec/support/shared_examples/controllers/unique_hll_events_examples.rb
@@ -2,14 +2,14 @@
#
# Requires a context containing:
# - request
-# - expected_type
-# - target_id
+# - expected_value
+# - target_event
RSpec.shared_examples 'tracking unique hll events' do
it 'tracks unique event' do
expect(Gitlab::UsageDataCounters::HLLRedisCounter).to(
receive(:track_event)
- .with(target_id, values: expected_type)
+ .with(target_event, values: expected_value)
.and_call_original # we call original to trigger additional validations; otherwise the method is stubbed
)
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 5ed8dc7ce98..6dca94ecf0a 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -211,10 +211,22 @@ RSpec.shared_examples 'handle uploads' do
stub_feature_flags(enforce_auth_checks_on_uploads: true)
end
- it "responds with status 302" do
+ it "responds with appropriate status" do
show_upload
- expect(response).to have_gitlab_http_status(:redirect)
+ # We're switching here based on the class due to the feature
+ # flag :enforce_auth_checks_on_uploads switching on project.
+ # When it is enabled fully, we will apply the code it guards
+ # to both Projects::UploadsController as well as
+ # Groups::UploadsController.
+ #
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/352291
+ #
+ if model.instance_of?(Group)
+ expect(response).to have_gitlab_http_status(:ok)
+ else
+ expect(response).to have_gitlab_http_status(:redirect)
+ end
end
end
@@ -305,7 +317,19 @@ RSpec.shared_examples 'handle uploads' do
it "responds with status 404" do
show_upload
- expect(response).to have_gitlab_http_status(:not_found)
+ # We're switching here based on the class due to the feature
+ # flag :enforce_auth_checks_on_uploads switching on
+ # project. When it is enabled fully, we will apply the
+ # code it guards to both Projects::UploadsController as
+ # well as Groups::UploadsController.
+ #
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/352291
+ #
+ if model.instance_of?(Group)
+ expect(response).to have_gitlab_http_status(:ok)
+ else
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
index 1cb52c07069..bf26922d9c5 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -220,8 +220,8 @@ RSpec.shared_examples 'wiki controller actions' do
context 'page view tracking' do
it_behaves_like 'tracking unique hll events' do
- let(:target_id) { 'wiki_action' }
- let(:expected_type) { instance_of(String) }
+ let(:target_event) { 'wiki_action' }
+ let(:expected_value) { instance_of(String) }
end
it 'increases the page view counter' do
diff --git a/spec/support/shared_examples/features/clusters_shared_examples.rb b/spec/support/shared_examples/features/clusters_shared_examples.rb
new file mode 100644
index 00000000000..6ee60f20b2e
--- /dev/null
+++ b/spec/support/shared_examples/features/clusters_shared_examples.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples "user disables a cluster" do
+ context 'when user disables the cluster' do
+ before do
+ page.find(:css, '.js-cluster-enable-toggle-area .js-project-feature-toggle').click
+ page.within('.js-cluster-details-form') { click_button 'Save changes' }
+ end
+
+ it 'user sees the successful message' do
+ expect(page).to have_content('Kubernetes cluster was successfully updated.')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/container_registry_shared_examples.rb b/spec/support/shared_examples/features/container_registry_shared_examples.rb
index 06b2b8c621c..6aa7e6e6270 100644
--- a/spec/support/shared_examples/features/container_registry_shared_examples.rb
+++ b/spec/support/shared_examples/features/container_registry_shared_examples.rb
@@ -7,3 +7,20 @@ RSpec.shared_examples 'handling feature network errors with the container regist
expect(page).to have_content 'We are having trouble connecting to the Container Registry'
end
end
+
+RSpec.shared_examples 'rejecting tags destruction for an importing repository on' do |tags: []|
+ it 'rejects the tag destruction operation' do
+ service = instance_double('Projects::ContainerRepository::DeleteTagsService')
+ expect(service).to receive(:execute).with(container_repository) { { status: :error, message: 'repository importing' } }
+ expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(container_repository.project, user, tags: tags) { service }
+
+ first('[data-testid="additional-actions"]').click
+ first('[data-testid="single-delete-button"]').click
+ expect(find('.modal .modal-title')).to have_content _('Remove tag')
+ find('.modal .modal-footer .btn-danger').click
+
+ alert_body = find('.gl-alert-body')
+ expect(alert_body).to have_content('Tags temporarily cannot be marked for deletion. Please try again in a few minutes.')
+ expect(alert_body).to have_link('More details', href: help_page_path('user/packages/container_registry/index', anchor: 'tags-temporarily-cannot-be-marked-for-deletion'))
+ end
+end
diff --git a/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb b/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb
index cfa043322db..4c312b42c0a 100644
--- a/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb
+++ b/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb
@@ -18,7 +18,7 @@ RSpec.shared_examples 'user activates the Mattermost Slash Command integration'
click_active_checkbox
click_save_integration
- expect(current_path).to eq(edit_path)
+ expect(page).to have_current_path(edit_path, ignore_query: true)
expect(page).to have_content('Mattermost slash commands settings saved, but not active.')
end
@@ -28,7 +28,7 @@ RSpec.shared_examples 'user activates the Mattermost Slash Command integration'
fill_in 'service_token', with: token
click_save_integration
- expect(current_path).to eq(edit_path)
+ expect(page).to have_current_path(edit_path, ignore_query: true)
expect(page).to have_content('Mattermost slash commands settings saved and active.')
end
end
diff --git a/spec/support/shared_examples/features/manage_applications_shared_examples.rb b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
index 27d50c67f24..3a8267b21da 100644
--- a/spec/support/shared_examples/features/manage_applications_shared_examples.rb
+++ b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
@@ -5,7 +5,7 @@ RSpec.shared_examples 'manage applications' do
let_it_be(:application_name_changed) { "#{application_name} changed" }
let_it_be(:application_redirect_uri) { 'https://foo.bar' }
- it 'allows user to manage applications' do
+ it 'allows user to manage applications', :js do
visit new_application_path
expect(page).to have_content 'Add new application'
diff --git a/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb b/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
new file mode 100644
index 00000000000..bbde448a1a1
--- /dev/null
+++ b/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'multiple assignees widget merge request' do |action, save_button_title|
+ it "#{action} a MR with multiple assignees", :js do
+ find('.js-assignee-search').click
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ click_link user2.name
+ end
+
+ # Extra click needed in order to toggle the dropdown
+ find('.js-assignee-search').click
+
+ expect(all('input[name="merge_request[assignee_ids][]"]', visible: false).map(&:value))
+ .to match_array([user.id.to_s, user2.id.to_s])
+
+ page.within '.js-assignee-search' do
+ expect(page).to have_content "#{user2.name} + 1 more"
+ end
+
+ click_button save_button_title
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content '2 Assignees'
+
+ click_button('Edit')
+
+ expect(page).to have_content user.name
+ expect(page).to have_content user2.name
+ end
+ end
+
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ # Closing dropdown to persist
+ click_button('Apply')
+
+ expect(page).to have_content user2.name
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
index 85434ba7afd..066c3e17a09 100644
--- a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
@@ -24,7 +24,7 @@ RSpec.shared_examples 'it uploads and commits a new text file' do |drop: false|
click_button('Upload file')
expect(page).to have_content('New commit message')
- expect(current_path).to eq(project_new_merge_request_path(project))
+ expect(page).to have_current_path(project_new_merge_request_path(project), ignore_query: true)
click_link('Changes')
find("a[data-action='diffs']", text: 'Changes').click
@@ -129,7 +129,7 @@ RSpec.shared_examples 'it uploads and commits a new file to a forked project' do
fork = user.fork_of(project2.reload)
- expect(current_path).to eq(project_new_merge_request_path(fork))
+ expect(page).to have_current_path(project_new_merge_request_path(fork), ignore_query: true)
find("a[data-action='diffs']", text: 'Changes').click
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 dfc9a45bd0d..f676b6aa60d 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
@@ -50,7 +50,7 @@ RSpec.shared_examples 'User creates wiki page' do
click_on("Create page")
end
- expect(current_path).to include("one/two/three-test")
+ expect(page).to have_current_path(%r(one/two/three-test), ignore_query: true)
expect(page).to have_link(href: wiki_page_path(wiki, 'one/two/three-test'))
end
@@ -68,7 +68,7 @@ RSpec.shared_examples 'User creates wiki page' do
click_button("Create page")
end
- expect(current_path).to eq(wiki_page_path(wiki, "home"))
+ expect(page).to have_current_path(wiki_page_path(wiki, "home"), ignore_query: true)
expect(page).to have_content("test GitLab API doc Rake tasks Wiki header")
.and have_content("Home")
.and have_content("Last edited by #{user.name}")
@@ -76,7 +76,7 @@ RSpec.shared_examples 'User creates wiki page' do
click_link("test")
- expect(current_path).to eq(wiki_page_path(wiki, "test"))
+ expect(page).to have_current_path(wiki_page_path(wiki, "test"), ignore_query: true)
page.within(:css, ".wiki-page-header") do
expect(page).to have_content("Create New Page")
@@ -84,11 +84,11 @@ RSpec.shared_examples 'User creates wiki page' do
click_link("Home")
- expect(current_path).to eq(wiki_page_path(wiki, "home"))
+ expect(page).to have_current_path(wiki_page_path(wiki, "home"), ignore_query: true)
click_link("GitLab API")
- expect(current_path).to eq(wiki_page_path(wiki, "api"))
+ expect(page).to have_current_path(wiki_page_path(wiki, "api"), ignore_query: true)
page.within(:css, ".wiki-page-header") do
expect(page).to have_content("Create")
@@ -96,11 +96,11 @@ RSpec.shared_examples 'User creates wiki page' do
click_link("Home")
- expect(current_path).to eq(wiki_page_path(wiki, "home"))
+ expect(page).to have_current_path(wiki_page_path(wiki, "home"), ignore_query: true)
click_link("Rake tasks")
- expect(current_path).to eq(wiki_page_path(wiki, "raketasks"))
+ expect(page).to have_current_path(wiki_page_path(wiki, "raketasks"), ignore_query: true)
page.within(:css, ".wiki-page-header") do
expect(page).to have_content("Create")
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 a456b76b324..85490bffc0e 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
@@ -25,7 +25,7 @@ RSpec.shared_examples 'User updates wiki page' do
click_on('Cancel')
end
- expect(current_path).to eq wiki_path(wiki)
+ expect(page).to have_current_path wiki_path(wiki), ignore_query: true
end
it 'updates a page that has a path', :js do
@@ -36,7 +36,7 @@ RSpec.shared_examples 'User updates wiki page' do
click_on('Create page')
end
- expect(current_path).to include('one/two/three-test')
+ expect(page).to have_current_path(%r(one/two/three-test), ignore_query: true)
expect(find('.wiki-pages')).to have_content('three')
first(:link, text: 'three').click
@@ -45,7 +45,7 @@ RSpec.shared_examples 'User updates wiki page' do
click_on('Edit')
- expect(current_path).to include('one/two/three-test')
+ expect(page).to have_current_path(%r(one/two/three-test), ignore_query: true)
expect(page).to have_content('Edit Page')
fill_in('Content', with: 'Updated Wiki Content')
@@ -120,7 +120,7 @@ RSpec.shared_examples 'User updates wiki page' do
click_on('Cancel')
end
- expect(current_path).to eq(wiki_page_path(wiki, wiki_page))
+ expect(page).to have_current_path(wiki_page_path(wiki, wiki_page), ignore_query: true)
end
it_behaves_like 'wiki file attachments'
@@ -175,7 +175,7 @@ RSpec.shared_examples 'User updates wiki page' do
click_button('Save changes')
- expect(current_path).to eq(wiki_page_path(wiki, page_name))
+ expect(page).to have_current_path(wiki_page_path(wiki, page_name), ignore_query: true)
end
it 'moves the page to other dir', :js do
@@ -185,7 +185,7 @@ RSpec.shared_examples 'User updates wiki page' do
click_button('Save changes')
- expect(current_path).to eq(wiki_page_path(wiki, new_page_dir))
+ expect(page).to have_current_path(wiki_page_path(wiki, new_page_dir), ignore_query: true)
end
it 'remains in the same place if title has not changed', :js do
@@ -195,7 +195,7 @@ RSpec.shared_examples 'User updates wiki page' do
click_button('Save changes')
- expect(current_path).to eq(original_path)
+ expect(page).to have_current_path(original_path, ignore_query: true)
end
it 'can be moved to a different dir with a different name', :js do
@@ -205,7 +205,7 @@ RSpec.shared_examples 'User updates wiki page' do
click_button('Save changes')
- expect(current_path).to eq(wiki_page_path(wiki, new_page_dir))
+ expect(page).to have_current_path(wiki_page_path(wiki, new_page_dir), ignore_query: true)
end
it 'can be renamed and moved to the root folder', :js do
@@ -215,7 +215,7 @@ RSpec.shared_examples 'User updates wiki page' do
click_button('Save changes')
- expect(current_path).to eq(wiki_page_path(wiki, new_name))
+ expect(page).to have_current_path(wiki_page_path(wiki, new_name), ignore_query: true)
end
it 'squishes the title before creating the page', :js do
@@ -225,7 +225,7 @@ RSpec.shared_examples 'User updates wiki page' do
click_button('Save changes')
- expect(current_path).to eq(wiki_page_path(wiki, "foo1/bar1/#{page_name}"))
+ expect(page).to have_current_path(wiki_page_path(wiki, "foo1/bar1/#{page_name}"), ignore_query: true)
end
it_behaves_like 'wiki file attachments'
diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
index eec911f3b6f..a7c32932ba7 100644
--- a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
@@ -37,12 +37,12 @@ RSpec.shared_examples 'User views a wiki page' do
end
it 'shows the history of a page that has a path' do
- expect(current_path).to include('one/two/three-test')
+ expect(page).to have_current_path(%r(one/two/three-test))
first(:link, text: 'three').click
click_on('Page history')
- expect(current_path).to include('one/two/three-test')
+ expect(page).to have_current_path(%r(one/two/three-test))
page.within(:css, '.wiki-page-header') do
expect(page).to have_content('History')
@@ -50,7 +50,7 @@ RSpec.shared_examples 'User views a wiki page' do
end
it 'shows an old version of a page', :js do
- expect(current_path).to include('one/two/three-test')
+ expect(page).to have_current_path(%r(one/two/three-test))
expect(find('.wiki-pages')).to have_content('three')
first(:link, text: 'three').click
@@ -59,7 +59,7 @@ RSpec.shared_examples 'User views a wiki page' do
click_on('Edit')
- expect(current_path).to include('one/two/three-test')
+ expect(page).to have_current_path(%r(one/two/three-test))
expect(page).to have_content('Edit Page')
fill_in('Content', with: 'Updated Wiki Content')
@@ -93,13 +93,12 @@ RSpec.shared_examples 'User views a wiki page' do
let(:path) { upload_file_to_wiki(wiki, user, 'dk.png') }
it do
- expect(page).to have_xpath("//img[@data-src='#{wiki.wiki_base_path}/#{path}']")
+ expect(page).to have_xpath("//img[@src='#{wiki.wiki_base_path}/#{path}']")
expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}")
click_on('image')
- expect(current_path).to match("wikis/#{path}")
- expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved
+ expect(page).to have_current_path(%r(wikis/#{path}))
end
end
@@ -108,7 +107,7 @@ RSpec.shared_examples 'User views a wiki page' do
click_on('image')
- expect(current_path).to match("wikis/#{path}")
+ expect(page).to have_current_path(%r(wikis/#{path}))
expect(page).to have_content('Create New Page')
end
end
diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb
index 314c2074eee..32cb2b1d187 100644
--- a/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb
@@ -60,7 +60,7 @@ RSpec.shared_examples 'User views wiki pages' do
before do
page.within('.wiki-sort-dropdown') do
click_button('Title')
- click_link('Created date')
+ click_button('Created date')
end
end
diff --git a/spec/support/shared_examples/graphql/members_shared_examples.rb b/spec/support/shared_examples/graphql/members_shared_examples.rb
index b0bdd27a95f..8e9e22f4359 100644
--- a/spec/support/shared_examples/graphql/members_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/members_shared_examples.rb
@@ -76,8 +76,10 @@ RSpec.shared_examples 'querying members with a group' do
resolve(described_class, obj: resource, args: base_args.merge(args), ctx: { current_user: other_user })
end
- it 'raises an error' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ it 'generates an error' do
+ expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do
+ subject
+ end
end
end
end
diff --git a/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb
index 14b2663a72c..21260e4d954 100644
--- a/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb
@@ -29,8 +29,10 @@ RSpec.shared_examples_for 'graphql mutations security ci configuration' do
describe '#resolve' do
let(:result) { subject }
- it 'raises an error if the resource is not accessible to the user' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ it 'generates an error if the resource is not accessible to the user' do
+ expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do
+ subject
+ end
end
context 'when user does not have enough permissions' do
@@ -38,8 +40,10 @@ RSpec.shared_examples_for 'graphql mutations security ci configuration' do
project.add_guest(user)
end
- it 'raises an error' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ it 'generates an error' do
+ expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do
+ subject
+ end
end
end
@@ -48,8 +52,10 @@ RSpec.shared_examples_for 'graphql mutations security ci configuration' do
create(:project_empty_repo).add_maintainer(user)
end
- it 'raises an error' do
- expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ it 'generates an error' do
+ expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do
+ subject
+ end
end
end
diff --git a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb
new file mode 100644
index 00000000000..0d0dbb112de
--- /dev/null
+++ b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples "a user type with merge request interaction type" do
+ specify { expect(described_class).to require_graphql_authorizations(:read_user) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ id
+ bot
+ user_permissions
+ snippets
+ name
+ username
+ email
+ publicEmail
+ avatarUrl
+ webUrl
+ webPath
+ todos
+ state
+ status
+ location
+ authoredMergeRequests
+ assignedMergeRequests
+ reviewRequestedMergeRequests
+ groupMemberships
+ groupCount
+ projectMemberships
+ starredProjects
+ callouts
+ merge_request_interaction
+ namespace
+ timelogs
+ groups
+ gitpodEnabled
+ preferencesGitpodPath
+ profileEnableGitpodPath
+ savedReplies
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+
+ describe '#merge_request_interaction' do
+ subject { described_class.fields['mergeRequestInteraction'] }
+
+ it 'returns the correct type' do
+ is_expected.to have_graphql_type(Types::UserMergeRequestInteractionType)
+ end
+
+ it 'has the correct arguments' do
+ is_expected.to have_attributes(arguments: be_empty)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/integrations/integration_settings_form.rb b/spec/support/shared_examples/integrations/integration_settings_form.rb
index d0bb40e43ee..d8a46180796 100644
--- a/spec/support/shared_examples/integrations/integration_settings_form.rb
+++ b/spec/support/shared_examples/integrations/integration_settings_form.rb
@@ -22,10 +22,7 @@ RSpec.shared_examples 'integration settings form' do
events = parse_json(trigger_events_for_integration(integration))
events.each do |trigger|
- # normalizing the title because capybara location is case sensitive
- title = normalize_title trigger[:title], integration
-
- expect(page).to have_field(title, type: 'checkbox', wait: 0),
+ expect(page).to have_field(trigger[:title], type: 'checkbox', wait: 0),
"#{integration.title} field #{title} checkbox not present"
end
end
@@ -35,12 +32,6 @@ RSpec.shared_examples 'integration settings form' do
private
- def normalize_title(title, integration)
- return 'Merge request' if integration.is_a?(Integrations::Jira) && title == 'merge_request'
-
- title.titlecase
- end
-
def parse_json(json)
Gitlab::Json.parse(json, symbolize_names: true)
end
diff --git a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
index 213f084be17..771ab89972c 100644
--- a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'marks background migration job records' do
it 'marks each job record as succeeded after processing' do
- create(:background_migration_job, class_name: "::#{described_class.name}",
+ create(:background_migration_job, class_name: "::#{described_class.name.demodulize}",
arguments: arguments)
expect(::Gitlab::Database::BackgroundMigrationJob).to receive(:mark_all_as_succeeded).and_call_original
@@ -13,7 +13,7 @@ RSpec.shared_examples 'marks background migration job records' do
end
it 'returns the number of job records marked as succeeded' do
- create(:background_migration_job, class_name: "::#{described_class.name}",
+ create(:background_migration_job, class_name: "::#{described_class.name.demodulize}",
arguments: arguments)
jobs_updated = subject.perform(*arguments)
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/usage_counter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/usage_counter_shared_examples.rb
new file mode 100644
index 00000000000..848437577d7
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/usage_counter_shared_examples.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a usage counter' do
+ describe '.increment' do
+ let(:project_id) { 12 }
+
+ it 'intializes and increments the counter for the project by 1' do
+ expect do
+ described_class.increment(project_id)
+ end.to change { described_class.usage_totals[project_id] }.from(nil).to(1)
+ end
+ end
+
+ describe '.usage_totals' do
+ let(:usage_totals) { described_class.usage_totals }
+
+ context 'when the feature has not been used' do
+ it 'returns the total counts and counts per project' do
+ expect(usage_totals.keys).to eq([:total])
+ expect(usage_totals[:total]).to eq(0)
+ end
+ end
+
+ context 'when the feature has been used in multiple projects' do
+ let(:project1_id) { 12 }
+ let(:project2_id) { 16 }
+
+ before do
+ described_class.increment(project1_id)
+ described_class.increment(project2_id)
+ end
+
+ it 'returns the total counts and counts per project' do
+ expect(usage_totals[project1_id]).to eq(1)
+ expect(usage_totals[project2_id]).to eq(1)
+ expect(usage_totals[:total]).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/wikis_api_examples.rb b/spec/support/shared_examples/lib/wikis_api_examples.rb
index 2e4c667d37e..f068a7676ad 100644
--- a/spec/support/shared_examples/lib/wikis_api_examples.rb
+++ b/spec/support/shared_examples/lib/wikis_api_examples.rb
@@ -44,13 +44,70 @@ RSpec.shared_examples_for 'wikis API returns list of wiki pages' do
end
RSpec.shared_examples_for 'wikis API returns wiki page' do
- it 'returns the wiki page' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(4)
- expect(json_response.keys).to match_array(expected_keys_with_content)
- expect(json_response['content']).to eq(page.content)
- expect(json_response['slug']).to eq(page.slug)
- expect(json_response['title']).to eq(page.title)
+ subject(:request) { get api(url, user), params: params }
+
+ shared_examples 'returns wiki page' do
+ before do
+ request
+ end
+
+ specify do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(5)
+ expect(json_response.keys).to match_array(expected_keys_with_content)
+ expect(json_response['content']).to eq(expected_content)
+ expect(json_response['slug']).to eq(page.slug)
+ expect(json_response['title']).to eq(page.title)
+ end
+ end
+
+ let(:expected_content) { page.content }
+
+ it_behaves_like 'returns wiki page'
+
+ context 'when render param is false' do
+ let(:params) { { render_html: false } }
+
+ it_behaves_like 'returns wiki page'
+ end
+
+ context 'when render param is true' do
+ let(:params) { { render_html: true } }
+ let(:expected_content) { '<p data-sourcepos="1:1-1:21" dir="auto">Content for wiki page</p>' }
+
+ it_behaves_like 'returns wiki page'
+ end
+
+ context 'when wiki page has versions' do
+ let(:new_content) { 'New content' }
+
+ before do
+ wiki.update_page(page.page, content: new_content, message: 'updated page')
+
+ expect(page.count_versions).to eq(2)
+
+ request
+ end
+
+ context 'when version param is not present' do
+ it 'retrieves the last version' do
+ expect(json_response['content']).to eq(new_content)
+ end
+ end
+
+ context 'when version param is set' do
+ let(:params) { { version: page.version.id } }
+
+ it 'retrieves the specific page version' do
+ expect(json_response['content']).to eq(page.content)
+ end
+
+ context 'when version param is not valid or inexistent' do
+ let(:params) { { version: 'foobar' } }
+
+ it_behaves_like 'wiki API 404 Wiki Page Not Found'
+ end
+ end
end
end
@@ -59,12 +116,13 @@ RSpec.shared_examples_for 'wikis API creates wiki page' do
post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(:created)
- expect(json_response.size).to eq(4)
+ expect(json_response.size).to eq(5)
expect(json_response.keys).to match_array(expected_keys_with_content)
expect(json_response['content']).to eq(payload[:content])
expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
expect(json_response['title']).to eq(payload[:title])
expect(json_response['rdoc']).to eq(payload[:rdoc])
+ expect(json_response['encoding']).to eq('UTF-8')
end
[:title, :content].each do |part|
@@ -85,7 +143,7 @@ RSpec.shared_examples_for 'wikis API updates wiki page' do
put(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(4)
+ expect(json_response.size).to eq(5)
expect(json_response.keys).to match_array(expected_keys_with_content)
expect(json_response['content']).to eq(payload[:content])
expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
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 6e8c340582a..3f187a7e9e4 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
@@ -91,21 +91,6 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
end
end
end
-
- context 'when the GITLAB_MULTIPLE_DATABASE_METRICS env var is disabled' do
- before do
- stub_env('GITLAB_MULTIPLE_DATABASE_METRICS', nil)
- end
-
- it 'does not include per database metrics' do
- Gitlab::WithRequestStore.with_request_store do
- subscriber.sql(event)
-
- expect(described_class.db_counter_payload).not_to include(:"db_replica_#{db_config_name}_duration_s")
- expect(described_class.db_counter_payload).not_to include(:"db_replica_#{db_config_name}_count")
- end
- end
- end
end
RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do |db_role|
@@ -160,26 +145,6 @@ RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do
subscriber.sql(event)
end
-
- context 'when the GITLAB_MULTIPLE_DATABASE_METRICS env var is disabled' do
- before do
- stub_env('GITLAB_MULTIPLE_DATABASE_METRICS', nil)
- end
-
- it 'does not include db_config_name label' do
- allow(transaction).to receive(:increment) do |*args|
- labels = args[2] || {}
- expect(labels).not_to include(:db_config_name)
- end
-
- allow(transaction).to receive(:observe) do |*args|
- labels = args[2] || {}
- expect(labels).not_to include(:db_config_name)
- end
-
- subscriber.sql(event)
- end
- end
end
RSpec.shared_examples 'record ActiveRecord metrics' do |db_role|
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 fe85daa7235..bb15a3054ac 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
@@ -155,7 +155,7 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
end
def expect_iid_to_be_set_and_rollback
- ActiveRecord::Base.transaction(requires_new: true) do
+ instance.transaction(requires_new: true) do
instance.save!
expect(read_internal_id).not_to be_nil
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 2a976fb7421..d6415e98289 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
@@ -692,16 +692,6 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
context 'notification enabled for all branches' do
it_behaves_like "triggered #{integration_name} integration", event_type: "pipeline", branches_to_be_notified: "all"
end
-
- context 'when chat_notification_deployment_protected_branch_filter is disabled' do
- before do
- stub_feature_flags(chat_notification_deployment_protected_branch_filter: false)
- end
-
- context 'notification enabled only for default branch' do
- it_behaves_like "triggered #{integration_name} integration", event_type: "pipeline", branches_to_be_notified: "default"
- end
- end
end
end
end
diff --git a/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb b/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb
index 07d687147bc..0ff0895b861 100644
--- a/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb
@@ -23,7 +23,7 @@ RSpec.shared_examples 'includes Limitable concern' do
context 'with an existing model' do
before do
- subject.dup.save!
+ subject.clone.save!
end
it 'cannot create new models exceeding the plan limits' do
diff --git a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
index 39121b73bc5..a2b4cdc33d0 100644
--- a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
@@ -66,17 +66,6 @@ RSpec.shared_examples 'a timebox' do |timebox_type|
end
end
- describe 'title' do
- it { is_expected.to validate_presence_of(:title) }
-
- it 'is invalid if title would be empty after sanitation' do
- timebox = build(timebox_type, *timebox_args, project: project, title: '<img src=x onerror=prompt(1)>')
-
- expect(timebox).not_to be_valid
- expect(timebox.errors[:title]).to include("can't be blank")
- end
- end
-
describe '#timebox_type_check' do
it 'is invalid if it has both project_id and group_id' do
timebox = build(timebox_type, *timebox_args, group: group)
diff --git a/spec/support/shared_examples/models/concerns/update_namespace_statistics_shared_examples.rb b/spec/support/shared_examples/models/concerns/update_namespace_statistics_shared_examples.rb
new file mode 100644
index 00000000000..255b6efa518
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/update_namespace_statistics_shared_examples.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'updates namespace statistics' do
+ let(:namespace_statistics_name) { described_class.namespace_statistics_name }
+ let(:statistic_attribute) { described_class.statistic_attribute }
+
+ context 'when creating' do
+ before do
+ statistic_source.send("#{statistic_attribute}=", 10)
+ end
+
+ it 'schedules a statistic refresh' do
+ expect(Groups::UpdateStatisticsWorker)
+ .to receive(:perform_async)
+
+ statistic_source.save!
+ end
+ end
+
+ context 'when updating' do
+ before do
+ statistic_source.save!
+
+ expect(statistic_source).to be_persisted
+ end
+
+ context 'when the statistic attribute has not changed' do
+ it 'does not schedule a statistic refresh' do
+ expect(Groups::UpdateStatisticsWorker)
+ .not_to receive(:perform_async)
+
+ statistic_source.update!(file_name: 'new-file-name.txt')
+ end
+ end
+
+ context 'when the statistic attribute has changed' do
+ it 'schedules a statistic refresh' do
+ expect(Groups::UpdateStatisticsWorker)
+ .to receive(:perform_async)
+
+ statistic_source.update!(statistic_attribute => 20)
+ end
+ end
+ end
+
+ context 'when deleting' do
+ it 'schedules a statistic refresh' do
+ expect(Groups::UpdateStatisticsWorker)
+ .to receive(:perform_async)
+
+ statistic_source.destroy!
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/issuable_link_shared_examples.rb b/spec/support/shared_examples/models/issuable_link_shared_examples.rb
new file mode 100644
index 00000000000..ca98c2597a2
--- /dev/null
+++ b/spec/support/shared_examples/models/issuable_link_shared_examples.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+# This shared example requires the following variables
+# issuable_link
+# issuable
+# issuable_class
+# issuable_link_factory
+RSpec.shared_examples 'issuable link' do
+ describe 'Associations' do
+ it { is_expected.to belong_to(:source).class_name(issuable.class.name) }
+ it { is_expected.to belong_to(:target).class_name(issuable.class.name) }
+ end
+
+ describe 'Validation' do
+ subject { issuable_link }
+
+ it { is_expected.to validate_presence_of(:source) }
+ it { is_expected.to validate_presence_of(:target) }
+ it do
+ is_expected.to validate_uniqueness_of(:source)
+ .scoped_to(:target_id)
+ .with_message(/already related/)
+ end
+
+ it 'is not valid if an opposite link already exists' do
+ issuable_link = create_issuable_link(subject.target, subject.source)
+
+ expect(issuable_link).to be_invalid
+ expect(issuable_link.errors[:source]).to include("is already related to this #{issuable.class.name.downcase}")
+ end
+
+ context 'when it relates to itself' do
+ context 'when target is nil' do
+ it 'does not invalidate object with self relation error' do
+ issuable_link = create_issuable_link(issuable, nil)
+
+ issuable_link.valid?
+
+ expect(issuable_link.errors[:source]).to be_empty
+ end
+ end
+
+ context 'when source and target are present' do
+ it 'invalidates object' do
+ issuable_link = create_issuable_link(issuable, issuable)
+
+ expect(issuable_link).to be_invalid
+ expect(issuable_link.errors[:source]).to include('cannot be related to itself')
+ end
+ end
+ end
+
+ def create_issuable_link(source, target)
+ build(issuable_link_factory, source: source, target: target)
+ end
+ end
+
+ describe '.link_type' do
+ it { is_expected.to define_enum_for(:link_type).with_values(relates_to: 0, blocks: 1) }
+
+ it 'provides the "related" as default link_type' do
+ expect(issuable_link.link_type).to eq 'relates_to'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index f7e09cfca62..17026f085bb 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -371,8 +371,7 @@ RSpec.shared_examples_for "bulk member creation" do
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.map(&:user)).to contain_exactly(user1, user2)
expect(members).to all(be_a(member_type))
expect(members).to all(be_persisted)
end
@@ -394,20 +393,18 @@ RSpec.shared_examples_for "bulk member creation" do
end
context 'with de-duplication' do
- it 'with the same user by id and user' do
+ it 'has the same user by id and user' do
members = described_class.add_users(source, [user1.id, user1, user1.id, user2, user2.id, user2], :maintainer)
- expect(members).to be_a Array
- expect(members.size).to eq(2)
+ expect(members.map(&:user)).to contain_exactly(user1, user2)
expect(members).to all(be_a(member_type))
expect(members).to all(be_persisted)
end
- it 'with the same user sent more than once' do
+ it 'has the same user sent more than once' do
members = described_class.add_users(source, [user1, user1], :maintainer)
- expect(members).to be_a Array
- expect(members.size).to eq(1)
+ expect(members.map(&:user)).to contain_exactly(user1)
expect(members).to all(be_a(member_type))
expect(members).to all(be_persisted)
end
@@ -418,15 +415,35 @@ RSpec.shared_examples_for "bulk member creation" do
source.add_user(user1, :developer)
end
- it 'supports existing users as expected' do
+ it 'has the same user sent more than once with the member already existing' do
+ expect do
+ members = described_class.add_users(source, [user1, user1, user2], :maintainer)
+ expect(members.map(&:user)).to contain_exactly(user1, user2)
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
+ end.to change { Member.count }.by(1)
+ end
+
+ it 'supports existing users as expected with user_ids passed' do
user3 = create(:user)
- members = described_class.add_users(source, [user1.id, user2, user3.id], :maintainer)
+ expect do
+ members = described_class.add_users(source, [user1.id, user2, user3.id], :maintainer)
+ expect(members.map(&:user)).to contain_exactly(user1, user2, user3)
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
+ end.to change { Member.count }.by(2)
+ end
+
+ it 'supports existing users as expected without user ids passed' do
+ user3 = create(:user)
- expect(members).to be_a Array
- expect(members.size).to eq(3)
- expect(members).to all(be_a(member_type))
- expect(members).to all(be_persisted)
+ expect do
+ members = described_class.add_users(source, [user1, user2, user3], :maintainer)
+ expect(members.map(&:user)).to contain_exactly(user1, user2, user3)
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
+ end.to change { Member.count }.by(2)
end
end
diff --git a/spec/support/shared_examples/models/resource_event_shared_examples.rb b/spec/support/shared_examples/models/resource_event_shared_examples.rb
index c0158f9b24b..80806ee768a 100644
--- a/spec/support/shared_examples/models/resource_event_shared_examples.rb
+++ b/spec/support/shared_examples/models/resource_event_shared_examples.rb
@@ -62,15 +62,15 @@ RSpec.shared_examples 'a resource event for issues' do
let_it_be(:issue2) { create(:issue, author: user1) }
let_it_be(:issue3) { create(:issue, author: user2) }
+ let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue1) }
+ let_it_be(:event2) { create(described_class.name.underscore.to_sym, issue: issue2) }
+ let_it_be(:event3) { create(described_class.name.underscore.to_sym, issue: issue1) }
+
describe 'associations' do
it { is_expected.to belong_to(:issue) }
end
describe '.by_issue' do
- let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue1) }
- let_it_be(:event2) { create(described_class.name.underscore.to_sym, issue: issue2) }
- let_it_be(:event3) { create(described_class.name.underscore.to_sym, issue: issue1) }
-
it 'returns the expected records for an issue with events' do
events = described_class.by_issue(issue1)
@@ -84,21 +84,29 @@ RSpec.shared_examples 'a resource event for issues' do
end
end
- describe '.by_issue_ids_and_created_at_earlier_or_equal_to' do
+ describe '.by_issue_ids' do
+ it 'returns the expected events' do
+ events = described_class.by_issue_ids([issue1.id])
+
+ expect(events).to contain_exactly(event1, event3)
+ end
+ end
+
+ describe '.by_created_at_earlier_or_equal_to' do
let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue1, created_at: '2020-03-10') }
let_it_be(:event2) { create(described_class.name.underscore.to_sym, issue: issue2, created_at: '2020-03-10') }
let_it_be(:event3) { create(described_class.name.underscore.to_sym, issue: issue1, created_at: '2020-03-12') }
- it 'returns the expected records for an issue with events' do
- events = described_class.by_issue_ids_and_created_at_earlier_or_equal_to([issue1.id, issue2.id], '2020-03-11 23:59:59')
+ it 'returns the expected events' do
+ events = described_class.by_created_at_earlier_or_equal_to('2020-03-11 23:59:59')
expect(events).to contain_exactly(event1, event2)
end
- it 'returns the expected records for an issue with no events' do
- events = described_class.by_issue_ids_and_created_at_earlier_or_equal_to(issue3, '2020-03-12')
+ it 'returns the expected events' do
+ events = described_class.by_created_at_earlier_or_equal_to('2020-03-12')
- expect(events).to be_empty
+ expect(events).to contain_exactly(event1, event2, event3)
end
end
diff --git a/spec/support/shared_examples/models/runners_token_prefix_shared_examples.rb b/spec/support/shared_examples/models/runners_token_prefix_shared_examples.rb
deleted file mode 100644
index 4dce445ac73..00000000000
--- a/spec/support/shared_examples/models/runners_token_prefix_shared_examples.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'it has a prefixable runners_token' do
- describe '#runners_token' do
- it 'has a runners_token_prefix' do
- expect(subject.runners_token_prefix).not_to be_empty
- end
-
- it 'starts with the runners_token_prefix' do
- expect(subject.runners_token).to start_with(subject.runners_token_prefix)
- 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 bc5956e3eec..b3f79d9fe6e 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -599,36 +599,13 @@ RSpec.shared_examples 'wiki model' do
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')
+ it 'creates the repository with the default branch' do
+ wiki.repository.create_if_not_exists(default_branch)
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_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
index bcb5464ed5b..f1ace9878e9 100644
--- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
@@ -90,7 +90,7 @@ RSpec.shared_examples 'namespace traversal scopes' do
it_behaves_like '.roots'
- it 'make recursive queries' do
+ it 'makes recursive queries' do
expect { described_class.where(id: [nested_group_1]).roots.load }.to make_queries_matching(/WITH RECURSIVE/)
end
end
@@ -126,7 +126,7 @@ RSpec.shared_examples 'namespace traversal scopes' do
end
context 'with offset and limit' do
- subject { described_class.where(id: [deep_nested_group_1, deep_nested_group_2]).offset(1).limit(1).self_and_ancestors }
+ subject { described_class.where(id: [deep_nested_group_1, deep_nested_group_2]).order(:traversal_ids).offset(1).limit(1).self_and_ancestors }
it { is_expected.to contain_exactly(group_2, nested_group_2, deep_nested_group_2) }
end
@@ -159,7 +159,7 @@ RSpec.shared_examples 'namespace traversal scopes' do
it_behaves_like '.self_and_ancestors'
- it 'make recursive queries' do
+ it 'makes recursive queries' do
expect { described_class.where(id: [nested_group_1]).self_and_ancestors.load }.to make_queries_matching(/WITH RECURSIVE/)
end
end
@@ -185,6 +185,7 @@ RSpec.shared_examples 'namespace traversal scopes' do
subject do
described_class
.where(id: [deep_nested_group_1, deep_nested_group_2])
+ .order(:traversal_ids)
.limit(1)
.offset(1)
.self_and_ancestor_ids
@@ -204,7 +205,7 @@ RSpec.shared_examples 'namespace traversal scopes' do
it_behaves_like '.self_and_ancestor_ids'
- it 'make recursive queries' do
+ it 'makes recursive queries' do
expect { described_class.where(id: [nested_group_1]).self_and_ancestor_ids.load }.not_to make_queries_matching(/WITH RECURSIVE/)
end
end
@@ -216,7 +217,7 @@ RSpec.shared_examples 'namespace traversal scopes' do
it_behaves_like '.self_and_ancestor_ids'
- it 'make recursive queries' do
+ it 'makes recursive queries' do
expect { described_class.where(id: [nested_group_1]).self_and_ancestor_ids.load }.to make_queries_matching(/WITH RECURSIVE/)
end
end
@@ -240,10 +241,20 @@ RSpec.shared_examples 'namespace traversal scopes' do
end
context 'with offset and limit' do
- subject { described_class.where(id: [group_1, group_2]).offset(1).limit(1).self_and_descendants }
+ subject { described_class.where(id: [group_1, group_2]).order(:traversal_ids).offset(1).limit(1).self_and_descendants }
it { is_expected.to contain_exactly(group_2, nested_group_2, deep_nested_group_2) }
end
+
+ context 'with nested query groups' do
+ let!(:nested_group_1b) { create(:group, parent: group_1) }
+ let!(:deep_nested_group_1b) { create(:group, parent: nested_group_1b) }
+ let(:group1_hierarchy) { [group_1, nested_group_1, deep_nested_group_1, nested_group_1b, deep_nested_group_1b] }
+
+ subject { described_class.where(id: [group_1, nested_group_1]).self_and_descendants }
+
+ it { is_expected.to match_array group1_hierarchy }
+ end
end
describe '.self_and_descendants' do
@@ -278,6 +289,7 @@ RSpec.shared_examples 'namespace traversal scopes' do
subject do
described_class
.where(id: [group_1, group_2])
+ .order(:traversal_ids)
.limit(1)
.offset(1)
.self_and_descendant_ids
@@ -340,7 +352,7 @@ RSpec.shared_examples 'namespace traversal scopes' do
it_behaves_like '.self_and_hierarchy'
- it 'make recursive queries' do
+ it 'makes recursive queries' do
base_groups = Group.where(id: nested_group_1)
expect { base_groups.self_and_hierarchy.load }.to make_queries_matching(/WITH RECURSIVE/)
end
diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
index b30c4186f0d..82c34f0d6ad 100644
--- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
@@ -178,6 +178,25 @@ RSpec.shared_examples 'rejects invalid recipe' do
end
end
+RSpec.shared_examples 'handling validation error for package' do
+ context 'with validation error' do
+ before do
+ allow_next_instance_of(Packages::Package) do |instance|
+ instance.errors.add(:base, 'validation error')
+
+ allow(instance).to receive(:valid?).and_return(false)
+ end
+ end
+
+ it 'returns 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to include('Validation failed')
+ end
+ end
+end
+
RSpec.shared_examples 'handling empty values for username and channel' do
using RSpec::Parameterized::TableSyntax
@@ -678,6 +697,7 @@ RSpec.shared_examples 'workhorse recipe file upload endpoint' do
it_behaves_like 'uploads a package file'
it_behaves_like 'creates build_info when there is a job'
it_behaves_like 'handling empty values for username and channel'
+ it_behaves_like 'handling validation error for package'
end
RSpec.shared_examples 'workhorse package file upload endpoint' do
@@ -700,6 +720,7 @@ RSpec.shared_examples 'workhorse package file upload endpoint' do
it_behaves_like 'uploads a package file'
it_behaves_like 'creates build_info when there is a job'
it_behaves_like 'handling empty values for username and channel'
+ it_behaves_like 'handling validation error for package'
context 'tracking the conan_package.tgz upload' do
let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
diff --git a/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb b/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb
index 104e91add8b..381583ff2a9 100644
--- a/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb
@@ -86,7 +86,7 @@ RSpec.shared_examples 'time tracking endpoints' do |issuable_name|
end
it "add spent time for #{issuable_name}" do
- Timecop.travel(1.minute.from_now) do
+ travel_to(2.minutes.from_now) do
expect do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '2h' }
end.to change { issuable.reload.updated_at }
@@ -98,7 +98,7 @@ RSpec.shared_examples 'time tracking endpoints' do |issuable_name|
context 'when subtracting time' do
it 'subtracts time of the total spent time' do
- Timecop.travel(1.minute.from_now) do
+ travel_to(2.minutes.from_now) do
expect do
issuable.update!(spend_time: { duration: 7200, user_id: user.id })
end.to change { issuable.reload.updated_at }
@@ -115,7 +115,7 @@ RSpec.shared_examples 'time tracking endpoints' do |issuable_name|
it 'does not modify the total time spent' do
issuable.update!(spend_time: { duration: 7200, user_id: user.id })
- Timecop.travel(1.minute.from_now) do
+ travel_to(2.minutes.from_now) do
expect do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '-1w' }
end.not_to change { issuable.reload.updated_at }
@@ -160,7 +160,7 @@ RSpec.shared_examples 'time tracking endpoints' do |issuable_name|
end
it "resets spent time for #{issuable_name}" do
- Timecop.travel(1.minute.from_now) do
+ travel_to(2.minutes.from_now) do
expect do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_spent_time", user)
end.to change { issuable.reload.updated_at }
diff --git a/spec/support/shared_examples/requests/clusters/certificate_based_clusters_feature_flag_shared_examples.rb b/spec/support/shared_examples/requests/clusters/certificate_based_clusters_feature_flag_shared_examples.rb
new file mode 100644
index 00000000000..24d90bde814
--- /dev/null
+++ b/spec/support/shared_examples/requests/clusters/certificate_based_clusters_feature_flag_shared_examples.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples ':certificate_based_clusters feature flag API responses' do
+ context 'feature flag is disabled' do
+ before do
+ stub_feature_flags(certificate_based_clusters: false)
+ end
+
+ it 'responds with :not_found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/row_lock_shared_examples.rb b/spec/support/shared_examples/row_lock_shared_examples.rb
index 5e003172215..24fb2d41bdf 100644
--- a/spec/support/shared_examples/row_lock_shared_examples.rb
+++ b/spec/support/shared_examples/row_lock_shared_examples.rb
@@ -7,7 +7,7 @@
RSpec.shared_examples 'locked row' do
it "has locked row" do
table_name = row.class.table_name
- ids_regex = /SELECT.*FROM.*#{table_name}.*"#{table_name}"."id" = #{row.id}.+FOR UPDATE/m
+ ids_regex = /SELECT.*FROM.*#{table_name}.*"#{table_name}"."id" = #{row.id}.+FOR NO KEY UPDATE/m
expect(recorded_queries.log).to include a_string_matching 'SAVEPOINT'
expect(recorded_queries.log).to include a_string_matching ids_regex
diff --git a/spec/support/shared_examples/sends_git_audit_streaming_event_shared_examples.rb b/spec/support/shared_examples/sends_git_audit_streaming_event_shared_examples.rb
new file mode 100644
index 00000000000..2c2be0152a0
--- /dev/null
+++ b/spec/support/shared_examples/sends_git_audit_streaming_event_shared_examples.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'sends git audit streaming event' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ stub_licensed_features(external_audit_events: true)
+ end
+
+ subject {}
+
+ context 'for public groups and projects' do
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:project, :public, :repository, namespace: group) }
+
+ before do
+ group.external_audit_event_destinations.create!(destination_url: 'http://example.com')
+ project.add_developer(user)
+ end
+
+ context 'when user not logged in' do
+ let(:key) { create(:key) }
+
+ before do
+ if request
+ request.headers.merge! auth_env(user.username, nil, nil)
+ end
+ end
+ it 'sends the audit streaming event' do
+ expect(AuditEvents::AuditEventStreamingWorker).not_to receive(:perform_async)
+ subject
+ end
+ end
+ end
+
+ context 'for private groups and projects' do
+ let(:group) { create(:group, :private) }
+ let(:project) { create(:project, :private, :repository, namespace: group) }
+
+ before do
+ group.external_audit_event_destinations.create!(destination_url: 'http://example.com')
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ context 'when user logged in' do
+ let(:key) { create(:key, user: user) }
+
+ before do
+ if request
+ password = user.try(:password) || user.try(:token)
+ request.headers.merge! auth_env(user.username, password, nil)
+ end
+ end
+ it 'sends the audit streaming event' do
+ expect(AuditEvents::AuditEventStreamingWorker).to receive(:perform_async).once
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
index 9d7ae6bcb3d..87a33060435 100644
--- a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
+++ b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
@@ -1,13 +1,19 @@
# frozen_string_literal: true
-RSpec.shared_examples 'avoid N+1 on environments serialization' do
+RSpec.shared_examples 'avoid N+1 on environments serialization' do |ee: false|
+ # Investigating in https://gitlab.com/gitlab-org/gitlab/-/issues/353209
+ let(:query_threshold) { 1 + (ee ? 4 : 0) }
+
it 'avoids N+1 database queries with grouping', :request_store do
create_environment_with_associations(project)
control = ActiveRecord::QueryRecorder.new { serialize(grouping: true) }
create_environment_with_associations(project)
+ create_environment_with_associations(project)
- expect { serialize(grouping: true) }.not_to exceed_query_limit(control.count)
+ expect { serialize(grouping: true) }
+ .not_to exceed_query_limit(control.count)
+ .with_threshold(query_threshold)
end
it 'avoids N+1 database queries without grouping', :request_store do
@@ -16,8 +22,11 @@ RSpec.shared_examples 'avoid N+1 on environments serialization' do
control = ActiveRecord::QueryRecorder.new { serialize(grouping: false) }
create_environment_with_associations(project)
+ create_environment_with_associations(project)
- expect { serialize(grouping: false) }.not_to exceed_query_limit(control.count)
+ expect { serialize(grouping: false) }
+ .not_to exceed_query_limit(control.count)
+ .with_threshold(query_threshold)
end
it 'does not preload for environments that does not exist in the page', :request_store do
@@ -35,7 +44,7 @@ RSpec.shared_examples 'avoid N+1 on environments serialization' do
end
def serialize(grouping:, query: nil)
- query ||= { page: 1, per_page: 1 }
+ query ||= { page: 1, per_page: 20 }
request = double(url: "#{Gitlab.config.gitlab.url}:8080/api/v4/projects?#{query.to_query}", query_parameters: query)
EnvironmentSerializer.new(current_user: user, project: project).yield_self do |serializer|
diff --git a/spec/support/shared_examples/serializers/note_entity_shared_examples.rb b/spec/support/shared_examples/serializers/note_entity_shared_examples.rb
index 9af6ec45e49..2e557ca090c 100644
--- a/spec/support/shared_examples/serializers/note_entity_shared_examples.rb
+++ b/spec/support/shared_examples/serializers/note_entity_shared_examples.rb
@@ -68,6 +68,29 @@ RSpec.shared_examples 'note entity' do
end
end
+ describe ':outdated_line_change_path' do
+ before do
+ allow(note).to receive(:show_outdated_changes?).and_return(show_outdated_changes)
+ end
+
+ context 'when note shows outdated changes' do
+ let(:show_outdated_changes) { true }
+
+ it 'returns correct outdated_line_change_namespace_project_note_path' do
+ path = "/#{note.project.namespace.path}/#{note.project.path}/notes/#{note.id}/outdated_line_change"
+ expect(subject[:outdated_line_change_path]).to eq(path)
+ end
+ end
+
+ context 'when note does not show outdated changes' do
+ let(:show_outdated_changes) { false }
+
+ it 'does not expose outdated_line_change_path' do
+ expect(subject).not_to include(:outdated_line_change_path)
+ end
+ end
+ end
+
context 'when note was edited' do
before do
note.update!(updated_at: 1.minute.from_now, updated_by: user)
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 c808b9a5318..a780952d51b 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
@@ -69,10 +69,6 @@ RSpec.shared_examples 'a browsable' do
end
RSpec.shared_examples 'an accessible' do
- before do
- stub_feature_flags(container_registry_migration_phase1: false)
- end
-
let(:access) do
[{ 'type' => 'repository',
'name' => project.full_path,
@@ -161,10 +157,6 @@ 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) }
diff --git a/spec/support/shared_examples/services/incident_shared_examples.rb b/spec/support/shared_examples/services/incident_shared_examples.rb
index cc26cf87322..b533b095aac 100644
--- a/spec/support/shared_examples/services/incident_shared_examples.rb
+++ b/spec/support/shared_examples/services/incident_shared_examples.rb
@@ -70,7 +70,7 @@ RSpec.shared_examples 'incident management label service' do
expect(execute).to be_success
expect(execute.payload).to eq(label: label)
expect(label.title).to eq(title)
- expect(label.color).to eq(color)
+ expect(label.color).to be_color(color)
expect(label.description).to eq(description)
end
end
diff --git a/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb b/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb
new file mode 100644
index 00000000000..6146aae6b9b
--- /dev/null
+++ b/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+shared_examples 'issuable link creation' do
+ describe '#execute' do
+ subject { described_class.new(issuable, user, params).execute }
+
+ context 'when the reference list is empty' do
+ let(:params) do
+ { issuable_references: [] }
+ end
+
+ it 'returns error' do
+ is_expected.to eq(message: "No matching #{issuable_type} found. Make sure that you are adding a valid #{issuable_type} URL.", status: :error, http_status: 404)
+ end
+ end
+
+ context 'when Issuable not found' do
+ let(:params) do
+ { issuable_references: ["##{non_existing_record_iid}"] }
+ end
+
+ it 'returns error' do
+ is_expected.to eq(message: "No matching #{issuable_type} found. Make sure that you are adding a valid #{issuable_type} URL.", status: :error, http_status: 404)
+ end
+
+ it 'no relationship is created' do
+ expect { subject }.not_to change(issuable_link_class, :count)
+ end
+ end
+
+ context 'when user has no permission to target issuable' do
+ let(:params) do
+ { issuable_references: [guest_issuable.to_reference(issuable_parent)] }
+ end
+
+ it 'returns error' do
+ is_expected.to eq(message: "No matching #{issuable_type} found. Make sure that you are adding a valid #{issuable_type} URL.", status: :error, http_status: 404)
+ end
+
+ it 'no relationship is created' do
+ expect { subject }.not_to change(issuable_link_class, :count)
+ end
+ end
+
+ context 'source and target are the same issuable' do
+ let(:params) do
+ { issuable_references: [issuable.to_reference] }
+ end
+
+ it 'does not create notes' do
+ expect(SystemNoteService).not_to receive(:relate_issuable)
+
+ subject
+ end
+
+ it 'no relationship is created' do
+ expect { subject }.not_to change(issuable_link_class, :count)
+ end
+ end
+
+ context 'when there is an issuable to relate' do
+ let(:params) do
+ { issuable_references: [issuable2.to_reference, issuable3.to_reference(issuable_parent)] }
+ end
+
+ it 'creates relationships' do
+ expect { subject }.to change(issuable_link_class, :count).by(2)
+
+ expect(issuable_link_class.find_by!(target: issuable2)).to have_attributes(source: issuable, link_type: 'relates_to')
+ expect(issuable_link_class.find_by!(target: issuable3)).to have_attributes(source: issuable, link_type: 'relates_to')
+ end
+
+ it 'returns success status' do
+ is_expected.to eq(status: :success)
+ end
+
+ it 'creates notes' do
+ # First two-way relation notes
+ expect(SystemNoteService).to receive(:relate_issuable)
+ .with(issuable, issuable2, user)
+ expect(SystemNoteService).to receive(:relate_issuable)
+ .with(issuable2, issuable, user)
+
+ # Second two-way relation notes
+ expect(SystemNoteService).to receive(:relate_issuable)
+ .with(issuable, issuable3, user)
+ expect(SystemNoteService).to receive(:relate_issuable)
+ .with(issuable3, issuable, user)
+
+ subject
+ end
+ end
+
+ context 'when reference of any already related issue is present' do
+ let(:params) do
+ {
+ issuable_references: [
+ issuable_a.to_reference,
+ issuable_b.to_reference
+ ],
+ link_type: IssueLink::TYPE_RELATES_TO
+ }
+ end
+
+ it 'creates notes only for new relations' do
+ expect(SystemNoteService).to receive(:relate_issuable).with(issuable, issuable_a, anything)
+ expect(SystemNoteService).to receive(:relate_issuable).with(issuable_a, issuable, anything)
+ expect(SystemNoteService).not_to receive(:relate_issuable).with(issuable, issuable_b, anything)
+ expect(SystemNoteService).not_to receive(:relate_issuable).with(issuable_b, issuable, anything)
+
+ subject
+ end
+ end
+
+ context 'when there are invalid references' do
+ let(:params) do
+ { issuable_references: [issuable.to_reference, issuable_a.to_reference] }
+ end
+
+ it 'creates links only for valid references' do
+ expect { subject }.to change { issuable_link_class.count }.by(1)
+ end
+
+ it 'returns error status' do
+ expect(subject).to eq(
+ status: :error,
+ http_status: 422,
+ message: "#{issuable.to_reference} cannot be added: cannot be related to itself"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb b/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb
new file mode 100644
index 00000000000..53d637a9094
--- /dev/null
+++ b/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+shared_examples 'a destroyable issuable link' do
+ context 'when successfully removes an issuable link' do
+ before do
+ issuable_link.source.resource_parent.add_reporter(user)
+ issuable_link.target.resource_parent.add_reporter(user)
+ end
+
+ it 'removes related issue' do
+ expect { subject }.to change(issuable_link.class, :count).by(-1)
+ end
+
+ it 'creates notes' do
+ # Two-way notes creation
+ expect(SystemNoteService).to receive(:unrelate_issuable)
+ .with(issuable_link.source, issuable_link.target, user)
+ expect(SystemNoteService).to receive(:unrelate_issuable)
+ .with(issuable_link.target, issuable_link.source, user)
+
+ subject
+ end
+
+ it 'returns success message' do
+ is_expected.to eq(message: 'Relation was removed', status: :success)
+ end
+ end
+
+ context 'when failing to remove an issuable link' do
+ it 'does not remove relation' do
+ expect { subject }.not_to change(issuable_link.class, :count).from(1)
+ end
+
+ it 'does not create notes' do
+ expect(SystemNoteService).not_to receive(:unrelate_issuable)
+ end
+
+ it 'returns error message' do
+ is_expected.to eq(message: "No #{issuable_link.class.model_name.human.titleize} found", status: :error, http_status: 404)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/rate_limited_service_shared_examples.rb b/spec/support/shared_examples/services/rate_limited_service_shared_examples.rb
new file mode 100644
index 00000000000..b79f1a332a6
--- /dev/null
+++ b/spec/support/shared_examples/services/rate_limited_service_shared_examples.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+# shared examples for testing rate limited functionality of a service
+#
+# following resources are expected to be set (example):
+# it_behaves_like 'rate limited service' do
+# let(:key) { :issues_create }
+# let(:key_scope) { %i[project current_user external_author] }
+# let(:application_limit_key) { :issues_create_limit }
+# let(:service) { described_class.new(project: project, current_user: user, params: { title: 'title' }, spam_params: double) }
+# let(:created_model) { Issue }
+# end
+
+RSpec.shared_examples 'rate limited service' do
+ describe '.rate_limiter_scoped_and_keyed' do
+ it 'is set via the rate_limit call' do
+ expect(described_class.rate_limiter_scoped_and_keyed).to be_a(RateLimitedService::RateLimiterScopedAndKeyed)
+
+ expect(described_class.rate_limiter_scoped_and_keyed.key).to eq(key)
+ expect(described_class.rate_limiter_scoped_and_keyed.opts[:scope]).to eq(key_scope)
+ expect(described_class.rate_limiter_scoped_and_keyed.rate_limiter).to eq(Gitlab::ApplicationRateLimiter)
+ end
+ end
+
+ describe '#rate_limiter_bypassed' do
+ it 'is nil by default' do
+ expect(service.rate_limiter_bypassed).to be_nil
+ end
+ end
+
+ describe '#execute' do
+ before do
+ stub_spam_services
+ end
+
+ context 'when rate limiting is in effect', :freeze_time, :clean_gitlab_redis_rate_limiting do
+ let(:user) { create(:user) }
+
+ before do
+ stub_application_setting(application_limit_key => 1)
+ end
+
+ subject do
+ 2.times { service.execute }
+ end
+
+ context 'when too many requests are sent by one user' do
+ it 'raises an error' do
+ expect do
+ subject
+ end.to raise_error(RateLimitedService::RateLimitedError)
+ end
+
+ it 'creates 1 issue' do
+ expect do
+ subject
+ rescue RateLimitedService::RateLimitedError
+ end.to change { created_model.count }.by(1)
+ end
+ end
+
+ context 'when limit is higher than count of issues being created' do
+ before do
+ stub_application_setting(issues_create_limit: 2)
+ end
+
+ it 'creates 2 issues' do
+ expect { subject }.to change { created_model.count }.by(2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
index 538fd2bb513..105c4247ff7 100644
--- a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
@@ -76,6 +76,18 @@ RSpec.shared_examples_for 'services security ci configuration create service' do
end
end
+ context 'when the project has a non-default ci config file' do
+ before do
+ project.ci_config_path = 'non-default/.gitlab-ci.yml'
+ end
+
+ it 'does track the snowplow event' do
+ subject
+
+ expect_snowplow_event(**snowplow_event)
+ end
+ end
+
unless skip_w_params
context 'with parameters' do
let(:params) { non_empty_params }
diff --git a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
new file mode 100644
index 00000000000..d202c4e00f0
--- /dev/null
+++ b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
@@ -0,0 +1,198 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_database|
+ include ExclusiveLeaseHelpers
+
+ describe 'defining the job attributes' do
+ it 'defines the data_consistency as always' do
+ expect(described_class.get_data_consistency).to eq(:always)
+ end
+
+ it 'defines the feature_category as database' do
+ expect(described_class.get_feature_category).to eq(:database)
+ end
+
+ it 'defines the idempotency as true' do
+ expect(described_class.idempotent?).to be_truthy
+ end
+ end
+
+ describe '.tracking_database' do
+ it 'does not raise an error' do
+ expect { described_class.tracking_database }.not_to raise_error
+ end
+
+ it 'overrides the method to return the tracking database' do
+ expect(described_class.tracking_database).to eq(tracking_database)
+ end
+ end
+
+ describe '.lease_key' do
+ let(:lease_key) { described_class.name.demodulize.underscore }
+
+ it 'does not raise an error' do
+ expect { described_class.lease_key }.not_to raise_error
+ end
+
+ it 'returns the lease key' do
+ expect(described_class.lease_key).to eq(lease_key)
+ end
+ end
+
+ describe '#perform' do
+ subject(:worker) { described_class.new }
+
+ context 'when the base model does not exist' do
+ before do
+ if Gitlab::Database.has_config?(tracking_database)
+ skip "because the base model for #{tracking_database} exists"
+ end
+ end
+
+ it 'does nothing' do
+ expect(worker).not_to receive(:active_migration)
+ expect(worker).not_to receive(:run_active_migration)
+
+ expect { worker.perform }.not_to raise_error
+ end
+
+ it 'logs a message indicating execution is skipped' do
+ expect(Sidekiq.logger).to receive(:info) do |payload|
+ expect(payload[:class]).to eq(described_class.name)
+ expect(payload[:database]).to eq(tracking_database)
+ expect(payload[:message]).to match(/skipping migration execution/)
+ end
+
+ expect { worker.perform }.not_to raise_error
+ end
+ end
+
+ context 'when the base model does exist' do
+ before do
+ unless Gitlab::Database.has_config?(tracking_database)
+ skip "because the base model for #{tracking_database} does not exist"
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(execute_batched_migrations_on_schedule: false)
+ end
+
+ it 'does nothing' do
+ expect(worker).not_to receive(:active_migration)
+ expect(worker).not_to receive(:run_active_migration)
+
+ worker.perform
+ end
+ end
+
+ context 'when the feature flag is enabled' do
+ before do
+ stub_feature_flags(execute_batched_migrations_on_schedule: true)
+
+ allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration).and_return(nil)
+ end
+
+ context 'when no active migrations exist' do
+ it 'does nothing' do
+ expect(worker).not_to receive(:run_active_migration)
+
+ worker.perform
+ end
+ end
+
+ context 'when active migrations exist' do
+ let(:job_interval) { 5.minutes }
+ let(:lease_timeout) { 15.minutes }
+ let(:lease_key) { described_class.name.demodulize.underscore }
+ let(:migration) { build(:batched_background_migration, :active, interval: job_interval) }
+ let(:interval_variance) { described_class::INTERVAL_VARIANCE }
+
+ before do
+ allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration)
+ .and_return(migration)
+
+ allow(migration).to receive(:interval_elapsed?).with(variance: interval_variance).and_return(true)
+ allow(migration).to receive(:reload)
+ end
+
+ context 'when the reloaded migration is no longer active' do
+ it 'does not run the migration' do
+ expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout)
+
+ expect(migration).to receive(:reload)
+ expect(migration).to receive(:active?).and_return(false)
+
+ expect(worker).not_to receive(:run_active_migration)
+
+ worker.perform
+ end
+ end
+
+ context 'when the interval has not elapsed' do
+ it 'does not run the migration' do
+ expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout)
+
+ expect(migration).to receive(:interval_elapsed?).with(variance: interval_variance).and_return(false)
+
+ expect(worker).not_to receive(:run_active_migration)
+
+ worker.perform
+ end
+ end
+
+ context 'when the reloaded migration is still active and the interval has elapsed' do
+ it 'runs the migration' do
+ expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout)
+
+ expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |instance|
+ expect(instance).to receive(:run_migration_job).with(migration)
+ end
+
+ expect(worker).to receive(:run_active_migration).and_call_original
+
+ worker.perform
+ end
+ end
+
+ context 'when the calculated timeout is less than the minimum allowed' do
+ let(:minimum_timeout) { described_class::MINIMUM_LEASE_TIMEOUT }
+ let(:job_interval) { 2.minutes }
+
+ it 'sets the lease timeout to the minimum value' do
+ expect_to_obtain_exclusive_lease(lease_key, timeout: minimum_timeout)
+
+ expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |instance|
+ expect(instance).to receive(:run_migration_job).with(migration)
+ end
+
+ expect(worker).to receive(:run_active_migration).and_call_original
+
+ worker.perform
+ end
+ end
+
+ it 'always cleans up the exclusive lease' do
+ lease = stub_exclusive_lease_taken(lease_key, timeout: lease_timeout)
+
+ expect(lease).to receive(:try_obtain).and_return(true)
+
+ expect(worker).to receive(:run_active_migration).and_raise(RuntimeError, 'I broke')
+ expect(lease).to receive(:cancel)
+
+ expect { worker.perform }.to raise_error(RuntimeError, 'I broke')
+ end
+
+ it 'receives the correct connection' do
+ base_model = Gitlab::Database.database_base_models[tracking_database]
+
+ expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(base_model.connection).and_yield
+
+ worker.perform
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb b/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb
index f2314793cb4..202606c6aa6 100644
--- a/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb
+++ b/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb
@@ -19,14 +19,28 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
end
shared_examples 'it calls Gitaly' do
- specify do
- repository_service = instance_double(Gitlab::GitalyClient::RepositoryService)
+ let(:repository_service) { instance_double(Gitlab::GitalyClient::RepositoryService) }
- expect(subject).to receive(:get_gitaly_client).with(task, repository.raw_repository).and_return(repository_service)
- expect(repository_service).to receive(gitaly_task)
+ specify do
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService, repository.raw_repository) do |instance|
+ expect(instance).to receive(:optimize_repository).and_call_original
+ end
subject.perform(*params)
end
+
+ context 'when optimized_housekeeping feature is disabled' do
+ before do
+ stub_feature_flags(optimized_housekeeping: false)
+ end
+
+ specify do
+ expect(subject).to receive(:get_gitaly_client).with(task, repository.raw_repository).and_return(repository_service)
+ expect(repository_service).to receive(gitaly_task)
+
+ subject.perform(*params)
+ end
+ end
end
shared_examples 'it updates the resource statistics' do
@@ -70,12 +84,31 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
end
it 'handles gRPC errors' do
- allow_next_instance_of(Gitlab::GitalyClient::RepositoryService, repository.raw_repository) do |instance|
- allow(instance).to receive(:garbage_collect).and_raise(GRPC::NotFound)
+ repository_service = instance_double(Gitlab::GitalyClient::RepositoryService)
+
+ allow_next_instance_of(Projects::GitDeduplicationService) do |instance|
+ allow(instance).to receive(:execute)
end
+ allow(repository.raw_repository).to receive(:gitaly_repository_client).and_return(repository_service)
+ allow(repository_service).to receive(:optimize_repository).and_raise(GRPC::NotFound)
+
expect { subject.perform(*params) }.to raise_exception(Gitlab::Git::Repository::NoRepository)
end
+
+ context 'when optimized_housekeeping feature flag is disabled' do
+ before do
+ stub_feature_flags(optimized_housekeeping: false)
+ end
+
+ it 'handles gRPC errors' do
+ allow_next_instance_of(Gitlab::GitalyClient::RepositoryService, repository.raw_repository) do |instance|
+ allow(instance).to receive(:garbage_collect).and_raise(GRPC::NotFound)
+ end
+
+ expect { subject.perform(*params) }.to raise_exception(Gitlab::Git::Repository::NoRepository)
+ end
+ end
end
context 'with different lease than the active one' do
@@ -152,13 +185,8 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid)
end
- it 'calls Gitaly' do
- repository_service = instance_double(Gitlab::GitalyClient::RefService)
-
- expect(subject).to receive(:get_gitaly_client).with(task, repository.raw_repository).and_return(repository_service)
- expect(repository_service).to receive(gitaly_task)
-
- subject.perform(*params)
+ it_behaves_like 'it calls Gitaly' do
+ let(:repository_service) { instance_double(Gitlab::GitalyClient::RefService) }
end
it 'does not update the resource statistics' do
@@ -180,10 +208,26 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
it_behaves_like 'it updates the resource statistics' if update_statistics
end
+ context 'prune' do
+ before do
+ expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid)
+ end
+
+ specify do
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService, repository.raw_repository) do |instance|
+ expect(instance).to receive(:prune_unreachable_objects).and_call_original
+ end
+
+ subject.perform(resource.id, 'prune', lease_key, lease_uuid)
+ end
+ end
+
shared_examples 'gc tasks' do
before do
allow(subject).to receive(:get_lease_uuid).and_return(lease_uuid)
allow(subject).to receive(:bitmaps_enabled?).and_return(bitmaps_enabled)
+
+ stub_feature_flags(optimized_housekeeping: false)
end
it 'incremental repack adds a new packfile' do
diff --git a/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb b/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
index d6e96ef37d6..d9105981b4b 100644
--- a/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
+++ b/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb
@@ -30,18 +30,11 @@ end
# `job_args` to be arguments to #perform if it takes arguments
RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_duration|
before do
- # Allow Timecop freeze and travel without the block form
- Timecop.safe_mode = false
- Timecop.freeze
+ freeze_time
time_travel_during_perform(actual_duration)
end
- after do
- Timecop.return
- Timecop.safe_mode = true
- end
-
let(:subject_perform) { defined?(job_args) ? subject.perform(job_args) : subject.perform }
context 'when the work finishes in 0 seconds' do
@@ -58,7 +51,7 @@ RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_durat
let(:actual_duration) { 0.1 * minimum_duration }
it 'sleeps 90% of minimum duration' do
- expect(subject).to receive(:sleep).with(a_value_within(0.01).of(0.9 * minimum_duration))
+ expect(subject).to receive(:sleep).with(a_value_within(1).of(0.9 * minimum_duration))
subject_perform
end
@@ -68,7 +61,7 @@ RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_durat
let(:actual_duration) { 0.9 * minimum_duration }
it 'sleeps 10% of minimum duration' do
- expect(subject).to receive(:sleep).with(a_value_within(0.01).of(0.1 * minimum_duration))
+ expect(subject).to receive(:sleep).with(a_value_within(1).of(0.1 * minimum_duration))
subject_perform
end
@@ -111,7 +104,7 @@ RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_durat
allow(subject).to receive(:ensure_minimum_duration) do |minimum_duration, &block|
original_ensure_minimum_duration.call(minimum_duration) do
# Time travel inside the block inside ensure_minimum_duration
- Timecop.travel(actual_duration) if actual_duration && actual_duration > 0
+ travel_to(actual_duration.from_now) if actual_duration && actual_duration > 0
end
end
end
diff --git a/spec/support/silence_stdout.rb b/spec/support/silence_stdout.rb
new file mode 100644
index 00000000000..b2bc65c5cda
--- /dev/null
+++ b/spec/support/silence_stdout.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ # Allows stdout to be redirected to reduce noise
+ config.before(:each, :silence_stdout) do
+ $stdout = StringIO.new
+ end
+
+ config.after(:each, :silence_stdout) do
+ $stdout = STDOUT
+ end
+end
diff --git a/spec/support/view_component.rb b/spec/support/view_component.rb
new file mode 100644
index 00000000000..9166a06fc8c
--- /dev/null
+++ b/spec/support/view_component.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+require 'view_component/test_helpers'
+
+RSpec.configure do |config|
+ config.include ViewComponent::TestHelpers, type: :component
+ config.include Capybara::RSpecMatchers, type: :component
+end