Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/before_all_adapter.rb11
-rw-r--r--spec/support/capybara.rb6
-rw-r--r--spec/support/capybara_wait_for_all_requests.rb20
-rw-r--r--spec/support/database/auto_explain.rb15
-rw-r--r--spec/support/database/click_house/hooks.rb8
-rw-r--r--spec/support/database/prevent_cross_database_modification.rb48
-rw-r--r--spec/support/database_cleaner.rb10
-rw-r--r--spec/support/db_cleaner.rb3
-rw-r--r--spec/support/factory_bot.rb14
-rw-r--r--spec/support/finder_collection_allowlist.yml6
-rw-r--r--spec/support/gitlab_stubs/gitlab_ci_dast_includes.yml7
-rw-r--r--spec/support/helpers/database/duplicate_indexes.rb77
-rw-r--r--spec/support/helpers/database/duplicate_indexes.yml265
-rw-r--r--spec/support/helpers/features/admin_users_helpers.rb2
-rw-r--r--spec/support/helpers/features/highlight_content_helper.rb19
-rw-r--r--spec/support/helpers/features/runners_helpers.rb10
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb2
-rw-r--r--spec/support/helpers/loose_foreign_keys_helper.rb11
-rw-r--r--spec/support/helpers/sign_up_helpers.rb27
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb14
-rw-r--r--spec/support/helpers/x509_helpers.rb181
-rw-r--r--spec/support/matchers/pagination_matcher.rb10
-rw-r--r--spec/support/migration.rb14
-rw-r--r--spec/support/multiple_databases.rb2
-rw-r--r--spec/support/protected_branch_helpers.rb2
-rw-r--r--spec/support/rspec.rb10
-rw-r--r--spec/support/rspec_order_todo.yml17
-rw-r--r--spec/support/shared_contexts/dependency_proxy_shared_context.rb14
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/features/integrations/group_integrations_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/features/integrations/instance_integrations_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb4
-rw-r--r--spec/support/shared_contexts/finders/users_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/lib/gitlab/database/load_balancing/wal_tracking_shared_context.rb8
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb20
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb2
-rw-r--r--spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/ci/create_pipeline_service_environment_shared_examples.rb166
-rw-r--r--spec/support/shared_examples/ci/deployable_shared_examples.rb56
-rw-r--r--spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/controllers/labels_controller_shared_examples.rb38
-rw-r--r--spec/support/shared_examples/controllers/search_rate_limit_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb2
-rw-r--r--spec/support/shared_examples/features/2fa_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb4
-rw-r--r--spec/support/shared_examples/features/runners_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/features/snippets_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/variable_list_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/features/work_items_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/finders/issues_finder_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/graphql/mutations/update_time_estimate_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/harbor/tags_controller_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb45
-rw-r--r--spec/support/shared_examples/lib/gitlab/bitbucket_import/object_import_shared_examples.rb100
-rw-r--r--spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/lib/gitlab/bitbucket_server_import/object_import_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb109
-rw-r--r--spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb33
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb87
-rw-r--r--spec/support/shared_examples/lib/menus_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb6
-rw-r--r--spec/support/shared_examples/mailers/notify_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb107
-rw-r--r--spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/models/group_shared_examples.rb45
-rw-r--r--spec/support/shared_examples/models/members_notifications_shared_example.rb8
-rw-r--r--spec/support/shared_examples/models/users/pages_visits_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/redis/redis_shared_examples.rb84
-rw-r--r--spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb33
-rw-r--r--spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb98
-rw-r--r--spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb69
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/requests/api_keyset_pagination_shared_examples.rb50
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb38
-rw-r--r--spec/support/shared_examples/services/incident_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb71
-rw-r--r--spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb193
-rw-r--r--spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/pages_size_limit_shared_examples.rb32
-rw-r--r--spec/support/shared_examples/services/protected_branches_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/services/users/build_service_shared_examples.rb51
-rw-r--r--spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/users/pages_visits_shared_examples.rb63
-rw-r--r--spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb36
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb21
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb22
-rw-r--r--spec/support/sidekiq.rb2
105 files changed, 2318 insertions, 753 deletions
diff --git a/spec/support/before_all_adapter.rb b/spec/support/before_all_adapter.rb
index f4946ff271f..35846fcecb8 100644
--- a/spec/support/before_all_adapter.rb
+++ b/spec/support/before_all_adapter.rb
@@ -25,20 +25,9 @@ module TestProfBeforeAllAdapter
end
end
- # This class is required so we can disable transactions on migration specs
- module NoTransactionAdapter
- def self.begin_transaction; end
-
- def self.rollback_transaction; end
- end
-
def self.default_adapter
MultipleDatabaseAdapter
end
-
- def self.no_transaction_adapter
- NoTransactionAdapter
- end
end
TestProf::BeforeAll.adapter = ::TestProfBeforeAllAdapter.default_adapter
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 392743fda4a..65abbe12621 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -54,11 +54,7 @@ Capybara.register_server :puma_via_workhorse do |app, port, host, **options|
# In cases of multiple installations of chromedriver, prioritize the version installed by SeleniumManager
# selenium-manager doesn't work with Linux arm64 yet:
# https://github.com/SeleniumHQ/selenium/issues/11357
- if RUBY_PLATFORM.include?('x86_64-linux') ||
- # Rosetta is required on macOS because the selenium-manager
- # binaries (https://github.com/SeleniumHQ/selenium/tree/trunk/common/manager/macos)
- # are only compiled for macOS x86.
- (RUBY_PLATFORM.include?('darwin') && system('/usr/bin/pgrep -q oahd'))
+ if RUBY_PLATFORM.include?('x86_64-linux') || RUBY_PLATFORM.include?('darwin')
chrome_options = Selenium::WebDriver::Chrome::Options.chrome
chromedriver_path = File.dirname(Selenium::WebDriver::SeleniumManager.driver_path(chrome_options))
ENV['PATH'] = "#{chromedriver_path}:#{ENV['PATH']}" # rubocop:disable RSpec/EnvAssignment
diff --git a/spec/support/capybara_wait_for_all_requests.rb b/spec/support/capybara_wait_for_all_requests.rb
index 36b63619b08..6f272474cf6 100644
--- a/spec/support/capybara_wait_for_all_requests.rb
+++ b/spec/support/capybara_wait_for_all_requests.rb
@@ -9,9 +9,11 @@ module Capybara
include CapybaraHelpers
include WaitForRequests
- def visit(visit_uri)
+ def visit(visit_uri, &block)
super
+ yield if block
+
wait_for_all_requests
end
end
@@ -24,24 +26,26 @@ module Capybara
include CapybaraHelpers
include WaitForRequests
- module WaitForAllRequestsAfterClickButton
+ module WaitForRequestsAfterClickButton
def click_button(locator = nil, **options)
super
- wait_for_all_requests
+ wait_for_requests
end
end
- module WaitForAllRequestsAfterClickLink
- def click_link(locator = nil, **options)
+ module WaitForRequestsAfterClickLink
+ def click_link(locator = nil, **options, &block)
super
- wait_for_all_requests
+ yield if block
+
+ wait_for_requests
end
end
- prepend WaitForAllRequestsAfterClickButton
- prepend WaitForAllRequestsAfterClickLink
+ prepend WaitForRequestsAfterClickButton
+ prepend WaitForRequestsAfterClickLink
end
end
end
diff --git a/spec/support/database/auto_explain.rb b/spec/support/database/auto_explain.rb
index 108d88e37b9..799457034a1 100644
--- a/spec/support/database/auto_explain.rb
+++ b/spec/support/database/auto_explain.rb
@@ -115,11 +115,16 @@ module AutoExplain
private
def record_auto_explain?(connection)
- ENV['CI'] \
- && ENV['CI_MERGE_REQUEST_LABELS']&.include?('pipeline:record-queries') \
- && ENV['CI_JOB_NAME_SLUG'] != 'db-migrate-non-superuser' \
- && connection.database_version.to_s[0..1].to_i >= 14 \
- && connection.select_one('SHOW is_superuser')['is_superuser'] == 'on'
+ return false unless ENV['CI']
+ return false if ENV['CI_JOB_NAME_SLUG'] == 'db-migrate-non-superuser'
+ return false if connection.database_version.to_s[0..1].to_i < 14
+ return false if connection.select_one('SHOW is_superuser')['is_superuser'] != 'on'
+
+ # This condition matches the pipeline rules for if-merge-request-labels-record-queries
+ return true if ENV['CI_MERGE_REQUEST_LABELS']&.include?('pipeline:record-queries')
+
+ # This condition matches the pipeline rules for if-default-branch-refs
+ ENV['CI_COMMIT_REF_NAME'] == ENV['CI_DEFAULT_BRANCH'] && !ENV['CI_MERGE_REQUEST_IID']
end
end
end
diff --git a/spec/support/database/click_house/hooks.rb b/spec/support/database/click_house/hooks.rb
index 27abd19dc3f..b970d3daf84 100644
--- a/spec/support/database/click_house/hooks.rb
+++ b/spec/support/database/click_house/hooks.rb
@@ -4,7 +4,13 @@
class ClickHouseTestRunner
def truncate_tables
ClickHouse::Client.configuration.databases.each_key do |db|
- tables_for(db).each do |table|
+ # Select tables with at least one row
+ query = tables_for(db).map do |table|
+ "(SELECT '#{table}' AS table FROM #{table} LIMIT 1)"
+ end.join(' UNION ALL ')
+
+ tables_with_data = ClickHouse::Client.select(query, db).pluck('table')
+ tables_with_data.each do |table|
ClickHouse::Client.execute("TRUNCATE TABLE #{table}", db)
end
end
diff --git a/spec/support/database/prevent_cross_database_modification.rb b/spec/support/database/prevent_cross_database_modification.rb
index 77fa7feacd4..02572d011f7 100644
--- a/spec/support/database/prevent_cross_database_modification.rb
+++ b/spec/support/database/prevent_cross_database_modification.rb
@@ -1,15 +1,53 @@
# frozen_string_literal: true
-module PreventCrossDatabaseModificationSpecHelpers
- delegate :with_cross_database_modification_prevented,
- :allow_cross_database_modification_within_transaction,
- to: :'::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification'
+module Database
+ module PreventCrossDatabaseModificationSpecHelpers
+ delegate :with_cross_database_modification_prevented,
+ :allow_cross_database_modification_within_transaction,
+ to: :'::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification'
+ end
+
+ module AllowCrossDatabaseFactoryBotBuilt
+ extend ActiveSupport::Concern
+
+ attr_accessor :factory_bot_built
+
+ prepended do
+ around_create :_test_ignore_table_in_transaction, prepend: true, if: :factory_bot_built?
+
+ def _test_ignore_table_in_transaction(&blk)
+ Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.temporary_ignore_tables_in_transaction(
+ [self.class.table_name], url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130277', &blk
+ )
+ end
+ end
+
+ def factory_bot_built?
+ return false unless Rails.env.test?
+
+ !!factory_bot_built
+ end
+
+ private
+
+ def ignore_cross_database_tables_if_factory_bot(tables, &blk)
+ return super unless factory_bot_built?
+
+ Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.temporary_ignore_tables_in_transaction(
+ tables,
+ url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130277',
+ &blk
+ )
+ end
+ end
end
+ActiveRecord::Base.prepend(Database::AllowCrossDatabaseFactoryBotBuilt)
+
CROSS_DB_MODIFICATION_ALLOW_LIST = Set.new(YAML.load_file(File.join(__dir__, 'cross-database-modification-allowlist.yml'))).freeze
RSpec.configure do |config|
- config.include(PreventCrossDatabaseModificationSpecHelpers)
+ config.include(Database::PreventCrossDatabaseModificationSpecHelpers)
# By default allow cross-modifications as we want to observe only transactions
# within a specific block of execution which is defined be `before(:each)` and `after(:each)`
diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb
index 7bd1f0c5dfa..1ffc1cc25fd 100644
--- a/spec/support/database_cleaner.rb
+++ b/spec/support/database_cleaner.rb
@@ -12,14 +12,4 @@ RSpec.configure do |config|
setup_database_cleaner
DatabaseCleaner.clean_with(:deletion)
end
-
- config.around(:each, :delete) do |example|
- self.class.use_transactional_tests = false
-
- example.run
-
- delete_from_all_tables!(except: deletion_except_tables)
-
- self.class.use_transactional_tests = true
- end
end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 17b4270fa20..3131a22a20b 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -73,7 +73,10 @@ module DbCleaner
end
end
+ disable_ddl_was = Feature.enabled?(:disallow_database_ddl_feature_flags, type: :ops)
+ stub_feature_flags(disallow_database_ddl_feature_flags: false)
Gitlab::Database::Partitioning.sync_partitions_ignore_db_error
+ stub_feature_flags(disallow_database_ddl_feature_flags: disable_ddl_was)
puts "Databases re-creation done in #{Gitlab::Metrics::System.monotonic_time - start}"
end
diff --git a/spec/support/factory_bot.rb b/spec/support/factory_bot.rb
index 6faa2db3330..d30098a5cc0 100644
--- a/spec/support/factory_bot.rb
+++ b/spec/support/factory_bot.rb
@@ -1,5 +1,19 @@
# frozen_string_literal: true
+FactoryBot.define do
+ after(:build) do |object, _|
+ next unless object.respond_to?(:factory_bot_built=)
+
+ object.factory_bot_built = true
+ end
+
+ before(:create) do |object, _|
+ next unless object.respond_to?(:factory_bot_built=)
+
+ object.factory_bot_built = false
+ end
+end
+
FactoryBot::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
include StubMethodCalls
diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml
index 5de8e8cdca2..860045c6ce6 100644
--- a/spec/support/finder_collection_allowlist.yml
+++ b/spec/support/finder_collection_allowlist.yml
@@ -7,6 +7,7 @@
- Namespaces::FreeUserCap::UsersFinder # Reason: There is no need to have anything else besides the count
- Groups::EnvironmentScopesFinder # Reason: There is no need to have anything else besides the simple strucutre with the scope name
- Security::RelatedPipelinesFinder # Reason: There is no need to have anything else besides the IDs of pipelines
+- Llm::ExtraResourceFinder # Reason: The finder does not deal with DB-backend resource for now.
# Temporary excludes (aka TODOs)
# For example:
@@ -61,11 +62,6 @@
- Security::PipelineVulnerabilitiesFinder
- Security::ScanExecutionPoliciesFinder
- Security::ScanResultPoliciesFinder
-- Security::TrainingProviders::BaseUrlFinder
-- Security::TrainingUrlsFinder
-- Security::TrainingProviders::KontraUrlFinder
-- Security::TrainingProviders::SecureCodeWarriorUrlFinder
-- Security::TrainingProviders::SecureFlagUrlFinder
- SentryIssueFinder
- ServerlessDomainFinder
- TagsFinder
diff --git a/spec/support/gitlab_stubs/gitlab_ci_dast_includes.yml b/spec/support/gitlab_stubs/gitlab_ci_dast_includes.yml
index 583d44c452e..097cbf5b0c8 100644
--- a/spec/support/gitlab_stubs/gitlab_ci_dast_includes.yml
+++ b/spec/support/gitlab_stubs/gitlab_ci_dast_includes.yml
@@ -1,3 +1,6 @@
+stages:
+ - dast
+
dast:
stage: dast
image:
@@ -7,4 +10,6 @@ dast:
allow_failure: true
dast_configuration:
site_profile: "site_profile_name_included"
- scanner_profile: "scanner_profile_name_included" \ No newline at end of file
+ scanner_profile: "scanner_profile_name_included"
+ script:
+ - echo "Runs DAST!"
diff --git a/spec/support/helpers/database/duplicate_indexes.rb b/spec/support/helpers/database/duplicate_indexes.rb
new file mode 100644
index 00000000000..0ad8ee1e055
--- /dev/null
+++ b/spec/support/helpers/database/duplicate_indexes.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module Database
+ class DuplicateIndexes
+ attr_accessor :table_name, :indexes
+
+ BTREE_INDEX_STRUCT = Struct.new(:name, :columns, :unique)
+
+ def initialize(table_name, indexes)
+ @table_name = table_name
+ @indexes = indexes
+ end
+
+ def duplicate_indexes
+ ret = {}
+
+ btree_indexes.each do |btree_index|
+ matching_indexes = matching_indexes_for(btree_index)
+ next unless matching_indexes.any?
+
+ ret[btree_index] = matching_indexes
+ end
+
+ ret
+ end
+
+ def self.btree_index_struct(index)
+ BTREE_INDEX_STRUCT.new(
+ index.name,
+ Array.wrap(index.columns).map do |column|
+ # https://apidock.com/rails/ActiveRecord/ConnectionAdapters/PostgreSQL/SchemaStatements/indexes
+ # asc is the default order
+ column_order = index.orders.is_a?(Symbol) ? index.orders : (index.orders[column] || :asc)
+ { name: column, order: column_order }
+ end,
+ index.unique
+ )
+ end
+
+ private
+
+ def btree_indexes
+ return @btree_indexes if @btree_indexes
+
+ # We only scan non-conditional btree indexes
+ @btree_indexes = indexes.select do |index|
+ index.using == :btree && index.where.nil? && index.opclasses.blank?
+ end
+
+ @btree_indexes = @btree_indexes.map { |index| self.class.btree_index_struct(index) }
+ end
+
+ def matching_indexes_for(btree_index)
+ all_matching_indexes = []
+
+ # When comparing btree_index with other_index. btree_index is the index that can have more columns
+ # than the other_index.
+ (1..btree_index.columns.length).each do |subset_length|
+ columns = btree_index.columns.first(subset_length)
+ matching_indexes = btree_indexes.reject { |other_index| other_index == btree_index }.select do |other_index|
+ other_index.columns == columns
+ end
+
+ # For now we ignore other indexes that are UNIQUE and have a matching columns subset of
+ # the btree_index columns, as UNIQUE indexes are still needed to enforce uniqueness
+ # constraints on subset of the columns.
+ matching_indexes = matching_indexes.reject do |other_index|
+ (other_index.unique && (other_index.columns.length < btree_index.columns.length))
+ end
+
+ all_matching_indexes += matching_indexes
+ end
+
+ all_matching_indexes
+ end
+ end
+end
diff --git a/spec/support/helpers/database/duplicate_indexes.yml b/spec/support/helpers/database/duplicate_indexes.yml
new file mode 100644
index 00000000000..02efdabd70b
--- /dev/null
+++ b/spec/support/helpers/database/duplicate_indexes.yml
@@ -0,0 +1,265 @@
+---
+# It maps table_name to {index1: array_of_duplicate_indexes, index2: array_of_duplicate_indexes, ... }
+abuse_reports:
+ idx_abuse_reports_user_id_status_and_category:
+ - index_abuse_reports_on_user_id
+alert_management_http_integrations:
+ index_http_integrations_on_project_and_endpoint:
+ - index_alert_management_http_integrations_on_project_id
+analytics_cycle_analytics_group_stages:
+ index_group_stages_on_group_id_group_value_stream_id_and_name:
+ - index_analytics_ca_group_stages_on_group_id
+approval_project_rules_users:
+ index_approval_project_rules_users_1:
+ - index_approval_project_rules_users_on_approval_project_rule_id
+approvals:
+ index_approvals_on_merge_request_id_and_created_at:
+ - index_approvals_on_merge_request_id
+board_group_recent_visits:
+ index_board_group_recent_visits_on_user_group_and_board:
+ - index_board_group_recent_visits_on_user_id
+board_project_recent_visits:
+ index_board_project_recent_visits_on_user_project_and_board:
+ - index_board_project_recent_visits_on_user_id
+board_user_preferences:
+ index_board_user_preferences_on_user_id_and_board_id:
+ - index_board_user_preferences_on_user_id
+boards_epic_board_recent_visits:
+ index_epic_board_recent_visits_on_user_group_and_board:
+ - index_boards_epic_board_recent_visits_on_user_id
+boards_epic_user_preferences:
+ index_boards_epic_user_preferences_on_board_user_epic_unique:
+ - index_boards_epic_user_preferences_on_board_id
+bulk_import_batch_trackers:
+ i_bulk_import_trackers_id_batch_number:
+ - index_bulk_import_batch_trackers_on_tracker_id
+bulk_import_export_batches:
+ i_bulk_import_export_batches_id_batch_number:
+ - index_bulk_import_export_batches_on_export_id
+ci_job_artifacts:
+ index_ci_job_artifacts_on_id_project_id_and_created_at:
+ - index_ci_job_artifacts_on_project_id
+ index_ci_job_artifacts_on_id_project_id_and_file_type:
+ - index_ci_job_artifacts_on_project_id
+ index_ci_job_artifacts_on_project_id_and_id:
+ - index_ci_job_artifacts_on_project_id
+ci_pipeline_artifacts:
+ index_ci_pipeline_artifacts_on_pipeline_id_and_file_type:
+ - index_ci_pipeline_artifacts_on_pipeline_id
+ci_stages:
+ index_ci_stages_on_pipeline_id_and_name:
+ - index_ci_stages_on_pipeline_id
+ index_ci_stages_on_pipeline_id_and_position:
+ - index_ci_stages_on_pipeline_id
+ index_ci_stages_on_pipeline_id_convert_to_bigint_and_name:
+ - index_ci_stages_on_pipeline_id_convert_to_bigint
+ index_ci_stages_on_pipeline_id_convert_to_bigint_and_position:
+ - index_ci_stages_on_pipeline_id_convert_to_bigint
+dast_site_tokens:
+ index_dast_site_token_on_project_id_and_url:
+ - index_dast_site_tokens_on_project_id
+design_management_designs:
+ index_design_management_designs_on_iid_and_project_id:
+ - index_design_management_designs_on_project_id
+design_management_designs_versions:
+ design_management_designs_versions_uniqueness:
+ - index_design_management_designs_versions_on_design_id
+error_tracking_errors:
+ index_et_errors_on_project_id_and_status_and_id:
+ - index_error_tracking_errors_on_project_id
+ index_et_errors_on_project_id_and_status_events_count_id_desc:
+ - index_error_tracking_errors_on_project_id
+ index_et_errors_on_project_id_and_status_first_seen_at_id_desc:
+ - index_error_tracking_errors_on_project_id
+ index_et_errors_on_project_id_and_status_last_seen_at_id_desc:
+ - index_error_tracking_errors_on_project_id
+geo_node_namespace_links:
+ index_geo_node_namespace_links_on_geo_node_id_and_namespace_id:
+ - index_geo_node_namespace_links_on_geo_node_id
+in_product_marketing_emails:
+ index_in_product_marketing_emails_on_user_campaign:
+ - index_in_product_marketing_emails_on_user_id
+ index_in_product_marketing_emails_on_user_track_series:
+ - index_in_product_marketing_emails_on_user_id
+incident_management_oncall_participants:
+ index_inc_mgmnt_oncall_participants_on_user_id_and_rotation_id:
+ - index_inc_mgmnt_oncall_participants_on_oncall_user_id
+incident_management_oncall_schedules:
+ index_im_oncall_schedules_on_project_id_and_iid:
+ - index_incident_management_oncall_schedules_on_project_id
+instance_audit_events_streaming_headers:
+ idx_instance_external_audit_event_destination_id_key_uniq:
+ - idx_headers_instance_external_audit_event_destination_id
+issue_links:
+ index_issue_links_on_source_id_and_target_id:
+ - index_issue_links_on_source_id
+issues:
+ index_issues_on_author_id_and_id_and_created_at:
+ - index_issues_on_author_id
+jira_connect_subscriptions:
+ idx_jira_connect_subscriptions_on_installation_id_namespace_id:
+ - idx_jira_connect_subscriptions_on_installation_id
+list_user_preferences:
+ index_list_user_preferences_on_user_id_and_list_id:
+ - index_list_user_preferences_on_user_id
+member_tasks:
+ index_member_tasks_on_member_id_and_project_id:
+ - index_member_tasks_on_member_id
+members:
+ index_members_on_member_namespace_id_compound:
+ - index_members_on_member_namespace_id
+merge_request_assignees:
+ index_merge_request_assignees_on_merge_request_id_and_user_id:
+ - index_merge_request_assignees_on_merge_request_id
+merge_request_metrics:
+ index_mr_metrics_on_target_project_id_merged_at_nulls_last:
+ - index_merge_request_metrics_on_target_project_id
+merge_requests:
+ index_merge_requests_on_author_id_and_created_at:
+ - index_merge_requests_on_author_id
+ index_merge_requests_on_author_id_and_id:
+ - index_merge_requests_on_author_id
+ index_merge_requests_on_author_id_and_target_project_id:
+ - index_merge_requests_on_author_id
+ml_candidate_params:
+ index_ml_candidate_params_on_candidate_id_on_name:
+ - index_ml_candidate_params_on_candidate_id
+ml_candidates:
+ index_ml_candidates_on_project_id_on_internal_id:
+ - index_ml_candidates_on_project_id
+ml_model_versions:
+ index_ml_model_versions_on_project_id_and_model_id_and_version:
+ - index_ml_model_versions_on_project_id
+ml_models:
+ index_ml_models_on_project_id_and_name:
+ - index_ml_models_on_project_id
+p_ci_runner_machine_builds:
+ index_p_ci_runner_machine_builds_on_runner_machine_id:
+ - index_ci_runner_machine_builds_on_runner_machine_id
+packages_debian_group_distributions:
+ uniq_pkgs_debian_group_distributions_group_id_and_codename:
+ - index_packages_debian_group_distributions_on_group_id
+ uniq_pkgs_debian_group_distributions_group_id_and_suite:
+ - index_packages_debian_group_distributions_on_group_id
+packages_debian_project_distributions:
+ uniq_pkgs_debian_project_distributions_project_id_and_codename:
+ - index_packages_debian_project_distributions_on_project_id
+ uniq_pkgs_debian_project_distributions_project_id_and_suite:
+ - index_packages_debian_project_distributions_on_project_id
+packages_tags:
+ index_packages_tags_on_package_id_and_updated_at:
+ - index_packages_tags_on_package_id
+pages_domains:
+ index_pages_domains_on_project_id_and_enabled_until:
+ - index_pages_domains_on_project_id
+ index_pages_domains_on_verified_at_and_enabled_until:
+ - index_pages_domains_on_verified_at
+personal_access_tokens:
+ index_pat_on_user_id_and_expires_at:
+ - index_personal_access_tokens_on_user_id
+pm_affected_packages:
+ i_affected_packages_unique_for_upsert:
+ - index_pm_affected_packages_on_pm_advisory_id
+pm_package_version_licenses:
+ i_pm_package_version_licenses_join_ids:
+ - index_pm_package_version_licenses_on_pm_package_version_id
+pm_package_versions:
+ i_pm_package_versions_on_package_id_and_version:
+ - index_pm_package_versions_on_pm_package_id
+project_compliance_standards_adherence:
+ u_project_compliance_standards_adherence_for_reporting:
+ - index_project_compliance_standards_adherence_on_project_id
+project_relation_exports:
+ index_project_export_job_relation:
+ - index_project_relation_exports_on_project_export_job_id
+project_repositories:
+ index_project_repositories_on_shard_id_and_project_id:
+ - index_project_repositories_on_shard_id
+project_topics:
+ index_project_topics_on_project_id_and_topic_id:
+ - index_project_topics_on_project_id
+projects:
+ index_projects_api_path_id_desc:
+ - index_on_projects_path
+ index_projects_on_path_and_id:
+ - index_on_projects_path
+protected_environments:
+ index_protected_environments_on_project_id_and_name:
+ - index_protected_environments_on_project_id
+protected_tags:
+ index_protected_tags_on_project_id_and_name:
+ - index_protected_tags_on_project_id
+related_epic_links:
+ index_related_epic_links_on_source_id_and_target_id:
+ - index_related_epic_links_on_source_id
+requirements_management_test_reports:
+ idx_test_reports_on_issue_id_created_at_and_id:
+ - index_requirements_management_test_reports_on_issue_id
+sbom_component_versions:
+ index_sbom_component_versions_on_component_id_and_version:
+ - index_sbom_component_versions_on_component_id
+sbom_occurrences:
+ index_sbom_occurrences_for_input_file_path_search:
+ - index_sbom_occurrences_on_project_id_component_id
+ - index_sbom_occurrences_on_project_id
+ idx_sbom_occurrences_on_project_id_and_source_id:
+ - index_sbom_occurrences_on_project_id
+ index_sbom_occurrences_on_project_id_and_id:
+ - index_sbom_occurrences_on_project_id
+ index_sbom_occurrences_on_project_id_component_id:
+ - index_sbom_occurrences_on_project_id
+ index_sbom_occurrences_on_project_id_and_component_id_and_id:
+ - index_sbom_occurrences_on_project_id_component_id
+ - index_sbom_occurrences_on_project_id
+ index_sbom_occurrences_on_project_id_and_package_manager:
+ - index_sbom_occurrences_on_project_id
+scan_result_policies:
+ index_scan_result_policies_on_position_in_configuration:
+ - index_scan_result_policies_on_policy_configuration_id
+search_namespace_index_assignments:
+ index_search_namespace_index_assignments_uniqueness_index_type:
+ - index_search_namespace_index_assignments_on_namespace_id
+ index_search_namespace_index_assignments_uniqueness_on_index_id:
+ - index_search_namespace_index_assignments_on_namespace_id
+sprints:
+ sequence_is_unique_per_iterations_cadence_id:
+ - index_sprints_iterations_cadence_id
+taggings:
+ taggings_idx:
+ - index_taggings_on_tag_id
+term_agreements:
+ term_agreements_unique_index:
+ - index_term_agreements_on_user_id
+todos:
+ index_todos_on_author_id_and_created_at:
+ - index_todos_on_author_id
+user_callouts:
+ index_user_callouts_on_user_id_and_feature_name:
+ - index_user_callouts_on_user_id
+users:
+ index_users_on_state_and_user_type:
+ - index_users_on_state
+vulnerabilities:
+ index_vulnerabilities_project_id_state_severity_default_branch:
+ - index_vulnerabilities_on_project_id_and_state_and_severity
+vulnerability_external_issue_links:
+ idx_vulnerability_ext_issue_links_on_vulne_id_and_ext_issue:
+ - index_vulnerability_external_issue_links_on_vulnerability_id
+vulnerability_finding_links:
+ finding_link_name_url_idx:
+ - finding_links_on_vulnerability_occurrence_id
+vulnerability_finding_signatures:
+ idx_vuln_signatures_uniqueness_signature_sha:
+ - index_vulnerability_finding_signatures_on_finding_id
+vulnerability_flags:
+ index_vulnerability_flags_on_unique_columns:
+ - index_vulnerability_flags_on_vulnerability_occurrence_id
+web_hook_logs:
+ index_web_hook_logs_on_web_hook_id_and_created_at:
+ - index_web_hook_logs_part_on_web_hook_id
+web_hooks:
+ index_web_hooks_on_project_id_recent_failures:
+ - index_web_hooks_on_project_id
+work_item_hierarchy_restrictions:
+ index_work_item_hierarchy_restrictions_on_parent_and_child:
+ - index_work_item_hierarchy_restrictions_on_parent_type_id
diff --git a/spec/support/helpers/features/admin_users_helpers.rb b/spec/support/helpers/features/admin_users_helpers.rb
index 9a87ccf113a..b4477537a40 100644
--- a/spec/support/helpers/features/admin_users_helpers.rb
+++ b/spec/support/helpers/features/admin_users_helpers.rb
@@ -4,7 +4,7 @@ module Features
module AdminUsersHelpers
def click_user_dropdown_toggle(user_id)
page.within("[data-testid='user-actions-#{user_id}']") do
- find("[data-testid='dropdown-toggle']").click
+ find("[data-testid='user-actions-dropdown-toggle']").click
end
end
diff --git a/spec/support/helpers/features/highlight_content_helper.rb b/spec/support/helpers/features/highlight_content_helper.rb
new file mode 100644
index 00000000000..f55dd213061
--- /dev/null
+++ b/spec/support/helpers/features/highlight_content_helper.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# This helper allows you to reliably highlight text within a given Element by
+# simulating mouse actions.
+#
+module Features
+ module HighlightContentHelper
+ def highlight_content(node)
+ height = node.native.rect.height
+ width = node.native.rect.width
+ page.driver.browser.action
+ .move_to(node.native, -(width / 2), -(height / 2))
+ .click_and_hold
+ .move_by(width, height)
+ .release
+ .perform
+ end
+ end
+end
diff --git a/spec/support/helpers/features/runners_helpers.rb b/spec/support/helpers/features/runners_helpers.rb
index dbd1edade8c..7c3618ee799 100644
--- a/spec/support/helpers/features/runners_helpers.rb
+++ b/spec/support/helpers/features/runners_helpers.rb
@@ -23,11 +23,13 @@ module Features
def input_filtered_search_keys(search_term)
focus_filtered_search
- page.find(search_bar_selector).find('input').send_keys(search_term)
- # blur input
- find('body').click
+ page.within(search_bar_selector) do
+ send_keys(search_term)
+ send_keys(:enter)
+
+ click_on 'Search'
+ end
- page.click_on 'Search'
wait_for_requests
end
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index 60638eb06cd..abd5d78e836 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -155,7 +155,7 @@ module FilteredSearchHelpers
end
def default_placeholder
- 'Search or filter results...'
+ 'Search or filter results…'
end
def get_filtered_search_placeholder
diff --git a/spec/support/helpers/loose_foreign_keys_helper.rb b/spec/support/helpers/loose_foreign_keys_helper.rb
new file mode 100644
index 00000000000..c83c60d72ed
--- /dev/null
+++ b/spec/support/helpers/loose_foreign_keys_helper.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+# Helper to process deletions of associated records created via loose foreign keys
+
+module LooseForeignKeysHelper
+ def process_loose_foreign_key_deletions(record:)
+ LooseForeignKeys::DeletedRecord.using_connection(record.connection) do
+ LooseForeignKeys::ProcessDeletedRecordsService.new(connection: record.connection).execute
+ end
+ end
+end
diff --git a/spec/support/helpers/sign_up_helpers.rb b/spec/support/helpers/sign_up_helpers.rb
new file mode 100644
index 00000000000..6259467232c
--- /dev/null
+++ b/spec/support/helpers/sign_up_helpers.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+module SignUpHelpers
+ def fill_in_sign_up_form(new_user, submit_button_text = 'Register')
+ fill_in 'new_user_first_name', with: new_user.first_name
+ fill_in 'new_user_last_name', with: new_user.last_name
+ fill_in 'new_user_username', with: new_user.username
+ fill_in 'new_user_email', with: new_user.email
+ fill_in 'new_user_password', with: new_user.password
+
+ wait_for_all_requests
+
+ expect_username_to_be_validated
+
+ yield if block_given?
+
+ click_button submit_button_text
+ end
+
+ private
+
+ def expect_username_to_be_validated
+ expect(page).to have_selector('[data-testid="new-user-username-field"].gl-field-success-outline')
+ end
+end
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index 6d0e97b0a75..c02ffe07159 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -23,13 +23,17 @@ module StubGitlabCalls
end
def stub_ci_pipeline_yaml_file(ci_yaml_content)
- allow_any_instance_of(Gitlab::Ci::ProjectConfig::Repository)
- .to receive(:file_in_repository?)
- .and_return(ci_yaml_content.present?)
+ blob = instance_double(Blob, empty?: ci_yaml_content.blank?, data: ci_yaml_content)
+ allow(blob).to receive(:load_all_data!)
allow_any_instance_of(Repository)
- .to receive(:gitlab_ci_yml_for)
- .and_return(ci_yaml_content)
+ .to receive(:blob_at)
+ .and_call_original
+
+ allow_any_instance_of(Repository)
+ .to receive(:blob_at)
+ .with(String, '.gitlab-ci.yml')
+ .and_return(blob)
# Ensure we don't hit auto-devops when config not found in repository
unless ci_yaml_content
diff --git a/spec/support/helpers/x509_helpers.rb b/spec/support/helpers/x509_helpers.rb
index 1dc8b1d4845..aa5c360d953 100644
--- a/spec/support/helpers/x509_helpers.rb
+++ b/spec/support/helpers/x509_helpers.rb
@@ -173,6 +173,10 @@ module X509Helpers
Time.at(1561027326)
end
+ def signed_tag_time
+ Time.at(1574261780)
+ end
+
def signed_tag_signature
<<~SIGNATURE
-----BEGIN SIGNED MESSAGE-----
@@ -337,6 +341,10 @@ module X509Helpers
'r.meier@siemens.com'
end
+ def tag_email
+ 'dmitriy.zaporozhets@gmail.com'
+ end
+
def certificate_issuer
'CN=Siemens Issuing CA EE Auth 2016,OU=Siemens Trust Center,serialNumber=ZZZZZZA2,O=Siemens,L=Muenchen,ST=Bayern,C=DE'
end
@@ -357,4 +365,177 @@ module X509Helpers
['r.meier@siemens.com']
end
end
+
+ module User2
+ extend self
+
+ def commit
+ '440bf5b2b499a90d9adcbebe3752f8c6f245a1aa'
+ end
+
+ def path
+ 'gitlab-test'
+ end
+
+ def trust_cert
+ <<~TRUSTCERTIFICATE
+ -----BEGIN CERTIFICATE-----
+ MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw
+ KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
+ MjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl
+ LmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C
+ AQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7
+ 7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS
+ 0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB
+ BQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp
+ KFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI
+ zj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR
+ nZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP
+ mygUY7Ii2zbdCdliiow=
+ -----END CERTIFICATE-----
+ TRUSTCERTIFICATE
+ end
+
+ def signed_commit_signature
+ <<~SIGNATURE
+ -----BEGIN SIGNED MESSAGE-----
+ MIIEOQYJKoZIhvcNAQcCoIIEKjCCBCYCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
+ hvcNAQcBoIIC2jCCAtYwggJdoAMCAQICFC5R9EXk+ljFhyCs4urRxmCuvQNAMAoG
+ CCqGSM49BAMDMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2ln
+ c3RvcmUtaW50ZXJtZWRpYXRlMB4XDTIzMDgxOTE3NTgwNVoXDTIzMDgxOTE4MDgw
+ NVowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBGajWb10Rt36IMxtJmjRDa7
+ 5O6YCLhVq9+LNJSAx2M7p6netqW7W+lwym4z1Y1gXLdGHBshrbx/yr6Trhh2TCej
+ ggF8MIIBeDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYD
+ VR0OBBYEFBttEjGzNppCqA4tlZY4oaxkdmQbMB8GA1UdIwQYMBaAFN/T6c9WJBGW
+ +ajY6ShVosYuGGQ/MCUGA1UdEQEB/wQbMBmBF2dpdGxhYmdwZ3Rlc3RAZ21haWwu
+ Y29tMCwGCisGAQQBg78wAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0
+ aDAuBgorBgEEAYO/MAEIBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0
+ aDCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt
+ /4eKcoAvKe6OAAABig7ydOsAAAQDAEgwRgIhAMqJnFLAspeqfbK/gA/7zjceyExq
+ QN7qDXWKRLS01rTvAiEAp/uBShQb9tVa3P3fYVAMiXydvr5dqCpNiuudZiuYq0Yw
+ CgYIKoZIzj0EAwMDZwAwZAIwWKXYyP5FvbfhvfLkV0tN887ax1eg7TmF1Tzkugag
+ cLJ5MzK3xYNcUO/3AxO3H/b8AjBD9DF6R4kFO4cXoqnpsk2FTUeSPiUJ+0x2PDFG
+ gQZvoMWz7CnwjXml8XDEKNpYoPkxggElMIIBIQIBATBPMDcxFTATBgNVBAoTDHNp
+ Z3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlAhQuUfRF
+ 5PpYxYcgrOLq0cZgrr0DQDALBglghkgBZQMEAgGgaTAYBgkqhkiG9w0BCQMxCwYJ
+ KoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMzA4MTkxNzU4MDVaMC8GCSqGSIb3
+ DQEJBDEiBCB4B7DeGk22WmBseJzjjRJcQsyYxu0PNDAFXq55uJ7MSzAKBggqhkjO
+ PQQDAgRHMEUCIQCNegIrK6m1xyGuu4lw06l22VQsmO74/k3H236jCFF+bAIgAX1N
+ rxBFWnjWboZmAV1NuduTD/YToShK6iRmJ/NpILA=
+ -----END SIGNED MESSAGE-----
+ SIGNATURE
+ end
+
+ def signed_commit_base_data
+ <<~SIGNEDDATA
+ tree 7d5ee08cadaa161d731c56a9265feef130143b07
+ parent 4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6
+ author Mona Lisa <gitlabgpgtest@gmail.com> 1692467872 +0000
+ committer Mona Lisa <gitlabgpgtest@gmail.com> 1692467872 +0000
+
+ Sigstore Signed Commit
+ SIGNEDDATA
+ end
+
+ def signed_commit_time
+ Time.at(1692467872)
+ end
+
+ def signed_tag_time
+ Time.at(1692467872)
+ end
+
+ def signed_tag_signature
+ <<~SIGNATURE
+ -----BEGIN SIGNED MESSAGE-----
+ MIIEOgYJKoZIhvcNAQcCoIIEKzCCBCcCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
+ hvcNAQcBoIIC2zCCAtcwggJdoAMCAQICFB5qFHBSNfcJDZecnHK5/tleuX3yMAoG
+ CCqGSM49BAMDMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2ln
+ c3RvcmUtaW50ZXJtZWRpYXRlMB4XDTIzMDgxOTE3NTgzM1oXDTIzMDgxOTE4MDgz
+ M1owADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKJtbdL88PM8lE21CuyDYlZm
+ 0xZYCThoXZSGmULrgE5+hfroCIbLswOi5i6TyB8j4CCe0Jxeu94Jn+76SXF+lbej
+ ggF8MIIBeDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYD
+ VR0OBBYEFBkU3IBENVJYeyK9b56vbGGrjPwYMB8GA1UdIwQYMBaAFN/T6c9WJBGW
+ +ajY6ShVosYuGGQ/MCUGA1UdEQEB/wQbMBmBF2dpdGxhYmdwZ3Rlc3RAZ21haWwu
+ Y29tMCwGCisGAQQBg78wAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0
+ aDAuBgorBgEEAYO/MAEIBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0
+ aDCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt
+ /4eKcoAvKe6OAAABig7y4tYAAAQDAEgwRgIhAMUjWh8ayhjWDI3faFah3Du/7IuY
+ xzbUXaPQnCyUbvwwAiEAwHgWv8fmKMudbVu37Nbq/c1cdnQqDK9Y2UGtlmzaLrYw
+ CgYIKoZIzj0EAwMDaAAwZQIwZTKZlS4HNJH48km3pxG95JTbldSBhvFlrpIEVRUd
+ TEK6uGQJmpIm1WYQjbJbiVS8AjEA+2NoAdMuRpa2k13HUfWQEMtzQcxZMMNB7Yux
+ 9ZIADOlFp701ujtFSZAXgqGL3FYKMYIBJTCCASECAQEwTzA3MRUwEwYDVQQKEwxz
+ aWdzdG9yZS5kZXYxHjAcBgNVBAMTFXNpZ3N0b3JlLWludGVybWVkaWF0ZQIUHmoU
+ cFI19wkNl5yccrn+2V65ffIwCwYJYIZIAWUDBAIBoGkwGAYJKoZIhvcNAQkDMQsG
+ CSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjMwODE5MTc1ODMzWjAvBgkqhkiG
+ 9w0BCQQxIgQgwpYCAlbS6KnfgxQD3SATWUbdUssLaBWkHwTkmtCye4wwCgYIKoZI
+ zj0EAwIERzBFAiB8y5bGhWJvWCHQyma7oF038ZPLzXmsDJyJffJHoAb6XAIhAOW3
+ gxuYuJAKP86B1fY0vYCZHF8vU6SZAcE6teSDowwq
+ -----END SIGNED MESSAGE-----
+ SIGNATURE
+ end
+
+ def signed_tag_base_data
+ <<~SIGNEDDATA
+ object 440bf5b2b499a90d9adcbebe3752f8c6f245a1aa
+ type commit
+ tag v1.1.2
+ tagger Mona Lisa <gitlabgpgtest@gmail.com> 1692467901 +0000
+
+ Sigstore Signed Tag
+ SIGNEDDATA
+ end
+
+ def certificate_serial
+ 264441215000592123389532407734419590292801651520
+ end
+
+ def tag_certificate_serial
+ 173635382582380059990335547381753891120957980146
+ end
+
+ def certificate_subject_key_identifier
+ '1B:6D:12:31:B3:36:9A:42:A8:0E:2D:95:96:38:A1:AC:64:76:64:1B'
+ end
+
+ def tag_certificate_subject_key_identifier
+ '19:14:DC:80:44:35:52:58:7B:22:BD:6F:9E:AF:6C:61:AB:8C:FC:18'
+ end
+
+ def issuer_subject_key_identifier
+ 'DF:D3:E9:CF:56:24:11:96:F9:A8:D8:E9:28:55:A2:C6:2E:18:64:3F'
+ end
+
+ def tag_issuer_subject_key_identifier
+ 'DF:D3:E9:CF:56:24:11:96:F9:A8:D8:E9:28:55:A2:C6:2E:18:64:3F'
+ end
+
+ def certificate_email
+ 'gitlabgpgtest@gmail.com'
+ end
+
+ def tag_email
+ 'gitlabgpgtest@gmail.com'
+ end
+
+ def certificate_issuer
+ 'CN=sigstore-intermediate,O=sigstore.dev'
+ end
+
+ def tag_certificate_issuer
+ 'CN=sigstore-intermediate,O=sigstore.dev'
+ end
+
+ def certificate_subject
+ ''
+ end
+
+ def names
+ ['Mona Lisa']
+ end
+
+ def emails
+ ['gitlabgpgtest@gmail.com']
+ end
+ end
end
diff --git a/spec/support/matchers/pagination_matcher.rb b/spec/support/matchers/pagination_matcher.rb
index a3e9c3b8474..beaba84d78c 100644
--- a/spec/support/matchers/pagination_matcher.rb
+++ b/spec/support/matchers/pagination_matcher.rb
@@ -11,3 +11,13 @@ RSpec::Matchers.define :include_limited_pagination_headers do |expected|
expect(actual.headers).to include('X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page', 'Link')
end
end
+
+RSpec::Matchers.define :include_keyset_url_params do |expected|
+ include KeysetPaginationHelpers
+
+ match do |actual|
+ params_for_next_page = pagination_params_from_next_url(actual)
+
+ expect(params_for_next_page).to include('cursor')
+ end
+end
diff --git a/spec/support/migration.rb b/spec/support/migration.rb
index b1e75d9c9e2..fc8a4bb12fb 100644
--- a/spec/support/migration.rb
+++ b/spec/support/migration.rb
@@ -20,21 +20,11 @@ RSpec.configure do |config|
Gitlab::CurrentSettings.clear_in_memory_application_settings!
end
- config.prepend_before(:all, :migration) do
- TestProf::BeforeAll.adapter = ::TestProfBeforeAllAdapter.no_transaction_adapter
- end
-
- config.append_after(:all, :migration) do
- TestProf::BeforeAll.adapter = ::TestProfBeforeAllAdapter.default_adapter
- end
-
config.append_after(:context, :migration) do
recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables
end
config.around(:each, :migration) do |example|
- self.class.use_transactional_tests = false
-
migration_schema = example.metadata[:migration]
migration_schema = :gitlab_main if migration_schema == true
base_model = Gitlab::Database.schemas_to_base_models.fetch(migration_schema).first
@@ -52,10 +42,6 @@ RSpec.configure do |config|
else
example.run
end
-
- delete_from_all_tables!(except: deletion_except_tables)
-
- self.class.use_transactional_tests = true
end
# Each example may call `migrate!`, so we must ensure we are migrated down every time
diff --git a/spec/support/multiple_databases.rb b/spec/support/multiple_databases.rb
index 616cf00269c..1c556858018 100644
--- a/spec/support/multiple_databases.rb
+++ b/spec/support/multiple_databases.rb
@@ -7,8 +7,6 @@ RSpec.configure do |config|
# at startup, but that generates its own
# `Gitlab::Database::Reflection` so the result is not memoized by
# callers of `ApplicationRecord.database.version`, such as
- # `Gitlab::Database::AsWithMaterialized.materialized_supported?`.
- # TODO This can be removed once https://gitlab.com/gitlab-org/gitlab/-/issues/325639 is completed.
[ApplicationRecord, ::Ci::ApplicationRecord].each { |record| record.database.version }
config.around(:each, :reestablished_active_record_base) do |example|
diff --git a/spec/support/protected_branch_helpers.rb b/spec/support/protected_branch_helpers.rb
index 576275e9d1d..db5118d6f88 100644
--- a/spec/support/protected_branch_helpers.rb
+++ b/spec/support/protected_branch_helpers.rb
@@ -34,7 +34,7 @@ module ProtectedBranchHelpers
select_input.click
wait_for_requests
- within('.dropdown.show .dropdown-menu', &block)
+ within('.dropdown .dropdown-menu.show', &block)
# Enhanced select is used in EE, therefore an extra click is needed.
select_input.click if select_input['aria-expanded'] == 'true'
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index 4479e679d67..7f3aa55fb1d 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -20,10 +20,18 @@ RSpec.configure do |config|
config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', './spec/examples.txt')
# Makes diffs show entire non-truncated values.
- config.before(:each, :unlimited_max_formatted_output_length) do
+ config.around(:each, :unlimited_max_formatted_output_length) do |example|
+ old_max_formatted_output_length = RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length
+
config.expect_with :rspec do |c|
c.max_formatted_output_length = nil
end
+
+ example.run
+
+ config.expect_with :rspec do |c|
+ c.max_formatted_output_length = old_max_formatted_output_length
+ end
end
unless ENV['CI']
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index f52f843e56a..298f4006c3b 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -19,7 +19,6 @@
- './ee/spec/controllers/admin/elasticsearch_controller_spec.rb'
- './ee/spec/controllers/admin/emails_controller_spec.rb'
- './ee/spec/controllers/admin/geo/nodes_controller_spec.rb'
-- './ee/spec/controllers/admin/geo/projects_controller_spec.rb'
- './ee/spec/controllers/admin/geo/settings_controller_spec.rb'
- './ee/spec/controllers/admin/groups_controller_spec.rb'
- './ee/spec/controllers/admin/impersonations_controller_spec.rb'
@@ -166,7 +165,6 @@
- './ee/spec/controllers/security/vulnerabilities_controller_spec.rb'
- './ee/spec/controllers/sitemap_controller_spec.rb'
- './ee/spec/controllers/subscriptions_controller_spec.rb'
-- './ee/spec/controllers/trial_registrations_controller_spec.rb'
- './ee/spec/controllers/users_controller_spec.rb'
- './ee/spec/db/production/license_spec.rb'
- './ee/spec/elastic_integration/global_search_spec.rb'
@@ -461,7 +459,6 @@
- './ee/spec/features/read_only_spec.rb'
- './ee/spec/features/registrations/combined_registration_spec.rb'
- './ee/spec/features/registrations/one_trust_spec.rb'
-- './ee/spec/features/registrations/welcome_spec.rb'
- './ee/spec/features/search/elastic/global_search_spec.rb'
- './ee/spec/features/search/elastic/group_search_spec.rb'
- './ee/spec/features/search/elastic/project_search_spec.rb'
@@ -526,7 +523,6 @@
- './ee/spec/finders/epics_finder_spec.rb'
- './ee/spec/finders/geo/ci_secure_file_registry_finder_spec.rb'
- './ee/spec/finders/geo/container_repository_registry_finder_spec.rb'
-- './ee/spec/finders/geo/design_registry_finder_spec.rb'
- './ee/spec/finders/geo/group_wiki_repository_registry_finder_spec.rb'
- './ee/spec/finders/geo/lfs_object_registry_finder_spec.rb'
- './ee/spec/finders/geo/merge_request_diff_registry_finder_spec.rb'
@@ -594,7 +590,6 @@
- './ee/spec/graphql/api/vulnerabilities_spec.rb'
- './ee/spec/graphql/ee/mutations/boards/issues/issue_move_list_spec.rb'
- './ee/spec/graphql/ee/mutations/boards/lists/create_spec.rb'
-- './ee/spec/graphql/ee/mutations/ci/project_ci_cd_settings_update_spec.rb'
- './ee/spec/graphql/ee/mutations/ci/runner/update_spec.rb'
- './ee/spec/graphql/ee/mutations/concerns/mutations/resolves_issuable_spec.rb'
- './ee/spec/graphql/ee/resolvers/board_list_issues_resolver_spec.rb'
@@ -999,7 +994,6 @@
- './ee/spec/helpers/security_helper_spec.rb'
- './ee/spec/helpers/subscriptions_helper_spec.rb'
- './ee/spec/helpers/timeboxes_helper_spec.rb'
-- './ee/spec/helpers/trial_registrations/reassurances_helper_spec.rb'
- './ee/spec/helpers/trial_status_widget_helper_spec.rb'
- './ee/spec/helpers/users_helper_spec.rb'
- './ee/spec/helpers/vulnerabilities_helper_spec.rb'
@@ -1147,7 +1141,6 @@
- './ee/spec/lib/ee/gitlab/group_search_results_spec.rb'
- './ee/spec/lib/ee/gitlab/hook_data/group_member_builder_spec.rb'
- './ee/spec/lib/ee/gitlab/hook_data/issue_builder_spec.rb'
-- './ee/spec/lib/ee/gitlab/hook_data/user_builder_spec.rb'
- './ee/spec/lib/ee/gitlab/import_export/after_export_strategies/custom_template_export_import_strategy_spec.rb'
- './ee/spec/lib/ee/gitlab/import_export/group/tree_restorer_spec.rb'
- './ee/spec/lib/ee/gitlab/import_export/group/tree_saver_spec.rb'
@@ -1395,7 +1388,6 @@
- './ee/spec/lib/gitlab/geo/log_cursor/daemon_spec.rb'
- './ee/spec/lib/gitlab/geo/log_cursor/event_logs_spec.rb'
- './ee/spec/lib/gitlab/geo/log_cursor/events/cache_invalidation_event_spec.rb'
-- './ee/spec/lib/gitlab/geo/log_cursor/events/design_repository_updated_event_spec.rb'
- './ee/spec/lib/gitlab/geo/log_cursor/events/event_spec.rb'
- './ee/spec/lib/gitlab/geo/log_cursor/events/hashed_storage_attachments_event_spec.rb'
- './ee/spec/lib/gitlab/geo/log_cursor/events/hashed_storage_migrated_event_spec.rb'
@@ -1758,7 +1750,6 @@
- './ee/spec/models/geo/ci_secure_file_registry_spec.rb'
- './ee/spec/models/geo/container_repository_registry_spec.rb'
- './ee/spec/models/geo/deleted_project_spec.rb'
-- './ee/spec/models/geo/design_registry_spec.rb'
- './ee/spec/models/geo/event_log_spec.rb'
- './ee/spec/models/geo/event_log_state_spec.rb'
- './ee/spec/models/geo/every_geo_event_spec.rb'
@@ -2750,7 +2741,6 @@
- './ee/spec/services/geo/wiki_sync_service_spec.rb'
- './ee/spec/services/gitlab_subscriptions/activate_service_spec.rb'
- './ee/spec/services/gitlab_subscriptions/check_future_renewal_service_spec.rb'
-- './ee/spec/services/gitlab_subscriptions/create_hand_raise_lead_service_spec.rb'
- './ee/spec/services/gitlab_subscriptions/create_service_spec.rb'
- './ee/spec/services/gitlab_subscriptions/create_trial_or_lead_service_spec.rb'
- './ee/spec/services/gitlab_subscriptions/fetch_purchase_eligible_namespaces_service_spec.rb'
@@ -2828,7 +2818,6 @@
- './ee/spec/services/milestones/destroy_service_spec.rb'
- './ee/spec/services/milestones/promote_service_spec.rb'
- './ee/spec/services/milestones/update_service_spec.rb'
-- './ee/spec/services/namespaces/in_product_marketing_emails_service_spec.rb'
- './ee/spec/services/namespaces/storage/email_notification_service_spec.rb'
- './ee/spec/services/path_locks/lock_service_spec.rb'
- './ee/spec/services/path_locks/unlock_service_spec.rb'
@@ -3057,7 +3046,6 @@
- './ee/spec/views/profiles/preferences/show.html.haml_spec.rb'
- './ee/spec/views/projects/edit.html.haml_spec.rb'
- './ee/spec/views/projects/issues/show.html.haml_spec.rb'
-- './ee/spec/views/projects/_merge_request_status_checks_settings.html.haml_spec.rb'
- './ee/spec/views/projects/on_demand_scans/index.html.haml_spec.rb'
- './ee/spec/views/projects/security/corpus_management/show.html.haml_spec.rb'
- './ee/spec/views/projects/security/dast_profiles/show.html.haml_spec.rb'
@@ -3068,6 +3056,7 @@
- './ee/spec/views/projects/security/discover/show.html.haml_spec.rb'
- './ee/spec/views/projects/security/policies/index.html.haml_spec.rb'
- './ee/spec/views/projects/security/sast_configuration/show.html.haml_spec.rb'
+- './ee/spec/views/projects/settings/merge_requests/_merge_request_status_checks_settings.html.haml_spec.rb'
- './ee/spec/views/projects/settings/subscriptions/_index.html.haml_spec.rb'
- './ee/spec/views/registrations/groups/new.html.haml_spec.rb'
- './ee/spec/views/shared/billings/_billing_plan_actions.html.haml_spec.rb'
@@ -3133,7 +3122,6 @@
- './ee/spec/workers/ee/arkose/blocked_users_report_worker_spec.rb'
- './ee/spec/workers/ee/ci/build_finished_worker_spec.rb'
- './ee/spec/workers/ee/issuable_export_csv_worker_spec.rb'
-- './ee/spec/workers/ee/namespaces/in_product_marketing_emails_worker_spec.rb'
- './ee/spec/workers/ee/namespaces/root_statistics_worker_spec.rb'
- './ee/spec/workers/ee/projects/inactive_projects_deletion_cron_worker_spec.rb'
- './ee/spec/workers/ee/repository_check/batch_worker_spec.rb'
@@ -8579,7 +8567,6 @@
- './spec/requests/groups/crm/contacts_controller_spec.rb'
- './spec/requests/groups/crm/organizations_controller_spec.rb'
- './spec/requests/groups/deploy_tokens_controller_spec.rb'
-- './spec/requests/groups/email_campaigns_controller_spec.rb'
- './spec/requests/groups/harbor/artifacts_controller_spec.rb'
- './spec/requests/groups/harbor/repositories_controller_spec.rb'
- './spec/requests/groups/harbor/tags_controller_spec.rb'
@@ -9323,7 +9310,6 @@
- './spec/services/milestones/transfer_service_spec.rb'
- './spec/services/milestones/update_service_spec.rb'
- './spec/services/namespace_settings/update_service_spec.rb'
-- './spec/services/namespaces/in_product_marketing_emails_service_spec.rb'
- './spec/services/namespaces/package_settings/update_service_spec.rb'
- './spec/services/namespaces/statistics_refresher_service_spec.rb'
- './spec/services/notes/build_service_spec.rb'
@@ -10126,7 +10112,6 @@
- './spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb'
- './spec/workers/metrics/dashboard/sync_dashboards_worker_spec.rb'
- './spec/workers/migrate_external_diffs_worker_spec.rb'
-- './spec/workers/namespaces/in_product_marketing_emails_worker_spec.rb'
- './spec/workers/namespaces/process_sync_events_worker_spec.rb'
- './spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb'
- './spec/workers/namespaces/root_statistics_worker_spec.rb'
diff --git a/spec/support/shared_contexts/dependency_proxy_shared_context.rb b/spec/support/shared_contexts/dependency_proxy_shared_context.rb
new file mode 100644
index 00000000000..02625722a8c
--- /dev/null
+++ b/spec/support/shared_contexts/dependency_proxy_shared_context.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'with a server running the dependency proxy' do
+ def run_server(handler)
+ default_server = Capybara.server
+
+ Capybara.server = Capybara.servers[:puma]
+ server = Capybara::Server.new(handler)
+ server.boot
+ server
+ ensure
+ Capybara.server = default_server
+ end
+end
diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb
index 8b4c1c1e243..3098fc3dc5e 100644
--- a/spec/support/shared_contexts/email_shared_context.rb
+++ b/spec/support/shared_contexts/email_shared_context.rb
@@ -191,7 +191,7 @@ RSpec.shared_examples 'note handler shared examples' do |forwardable|
context 'when the service desk' do
let(:project) { create(:project, :public, service_desk_enabled: true) }
- let(:support_bot) { User.support_bot }
+ let(:support_bot) { Users::Internal.support_bot }
let(:noteable) { create(:issue, project: project, author: support_bot, title: 'service desk issue') }
let!(:note) { create(:note, project: project, noteable: noteable) }
let(:email_raw) { with_quick_actions }
diff --git a/spec/support/shared_contexts/features/integrations/group_integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/group_integrations_shared_context.rb
index 5996fcc6593..befc231f04f 100644
--- a/spec/support/shared_contexts/features/integrations/group_integrations_shared_context.rb
+++ b/spec/support/shared_contexts/features/integrations/group_integrations_shared_context.rb
@@ -22,7 +22,7 @@ RSpec.shared_context 'group integration activation' do
visit_group_integrations
within('#content-body') do
- click_link(name)
+ click_link(name, match: :first)
end
end
end
diff --git a/spec/support/shared_contexts/features/integrations/instance_integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/instance_integrations_shared_context.rb
index 3b02db994a3..c740917cec4 100644
--- a/spec/support/shared_contexts/features/integrations/instance_integrations_shared_context.rb
+++ b/spec/support/shared_contexts/features/integrations/instance_integrations_shared_context.rb
@@ -18,7 +18,7 @@ RSpec.shared_context 'instance integration activation' do
visit_instance_integrations
within('#content-body') do
- click_link(name)
+ click_link(name, match: :first)
end
end
end
diff --git a/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb
index a9b9a5246e6..c3da9435e05 100644
--- a/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb
+++ b/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb
@@ -4,7 +4,7 @@ RSpec.shared_context 'project integration activation' do
include_context 'with integration activation'
let_it_be(:project) { create(:project) }
- let_it_be(:user) { create(:user) }
+ let_it_be(:user) { create(:user, :no_super_sidebar) }
before do
project.add_maintainer(user)
@@ -19,7 +19,7 @@ RSpec.shared_context 'project integration activation' do
visit_project_integrations
within('#content-body') do
- click_link(name)
+ click_link(name, match: :first)
end
end
diff --git a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
index ef1c01f72f9..b89b1aabd87 100644
--- a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
@@ -8,5 +8,5 @@ RSpec.shared_context 'UsersFinder#execute filter by project context' do
let_it_be(:external_user) { create(:user, :external) }
let_it_be(:unconfirmed_user) { create(:user, confirmed_at: nil) }
let_it_be(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
- let_it_be(:internal_user) { User.alert_bot.tap { |u| u.confirm } }
+ let_it_be(:internal_user) { Users::Internal.alert_bot.tap { |u| u.confirm } }
end
diff --git a/spec/support/shared_contexts/lib/gitlab/database/load_balancing/wal_tracking_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/database/load_balancing/wal_tracking_shared_context.rb
index cbbd3754108..46d6a1fbac0 100644
--- a/spec/support/shared_contexts/lib/gitlab/database/load_balancing/wal_tracking_shared_context.rb
+++ b/spec/support/shared_contexts/lib/gitlab/database/load_balancing/wal_tracking_shared_context.rb
@@ -62,7 +62,13 @@ RSpec.shared_context 'when tracking WAL location reference' do
def stub_replica_available!(available)
::Gitlab::Database::LoadBalancing.each_load_balancer do |lb|
- allow(lb).to receive(:select_up_to_date_host).with(current_location).and_return(available)
+ result = if available
+ ::Gitlab::Database::LoadBalancing::LoadBalancer::ANY_CAUGHT_UP
+ else
+ ::Gitlab::Database::LoadBalancing::LoadBalancer::NONE_CAUGHT_UP
+ end
+
+ allow(lb).to receive(:select_up_to_date_host).with(current_location).and_return(result)
end
end
end
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 112b90029b8..a09319b4980 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -82,7 +82,6 @@ RSpec.shared_context 'project navbar structure' do
{
nav_item: _('Monitor'),
nav_sub_items: [
- _('Tracing'),
_('Error Tracking'),
_('Alerts'),
_('Incidents')
@@ -165,15 +164,6 @@ RSpec.shared_context 'group navbar structure' do
}
end
- let(:ci_cd_nav_item) do
- {
- nav_item: _('CI/CD'),
- nav_sub_items: [
- s_('Runners|Runners')
- ]
- }
- end
-
let(:issues_nav_items) do
[
_('List'),
@@ -207,6 +197,12 @@ RSpec.shared_context 'group navbar structure' do
},
(security_and_compliance_nav_item if Gitlab.ee?),
{
+ nav_item: _('CI/CD'),
+ nav_sub_items: [
+ s_('Runners|Runners')
+ ]
+ },
+ {
nav_item: _('Kubernetes'),
nav_sub_items: []
},
@@ -231,6 +227,10 @@ RSpec.shared_context 'dashboard navbar structure' do
nav_sub_items: []
},
{
+ nav_item: _('Organizations'),
+ nav_sub_items: []
+ },
+ {
nav_item: _("Issues"),
nav_sub_items: []
},
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index 07a4cbdb534..70b48322efd 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -58,6 +58,7 @@ RSpec.shared_context 'GroupPolicy context' do
destroy_upload
admin_achievement
award_achievement
+ read_group_runners
]
end
@@ -73,7 +74,6 @@ RSpec.shared_context 'GroupPolicy context' do
create_subgroup
read_statistics
update_default_branch_protection
- read_group_runners
register_group_runners
read_billing
edit_billing
diff --git a/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb
index 36103b94542..cf5ac849f63 100644
--- a/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb
+++ b/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb
@@ -5,7 +5,7 @@ RSpec.shared_context 'npm api setup' do
include HttpBasicAuthHelpers
let_it_be(:user, reload: true) { create(:user) }
- let_it_be(:group) { create(:group, name: 'test-group') }
+ let_it_be(:group, reload: true) { create(:group, name: 'test-group') }
let_it_be(:namespace) { group }
let_it_be(:project, reload: true) { create(:project, :public, namespace: namespace) }
let_it_be(:package, reload: true) { create(:npm_package, project: project, name: "@#{group.path}/scoped_package", version: '1.2.3') }
diff --git a/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb b/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb
index cb7001a9faf..21bba14f3e6 100644
--- a/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb
+++ b/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb
@@ -15,16 +15,4 @@ RSpec.shared_examples 'handle subscription based on user access' do
expect(subscription).to be_rejected
end
-
- context 'when action_cable_notes is disabled' do
- before do
- stub_feature_flags(action_cable_notes: false)
- end
-
- it 'rejects the subscription' do
- subscribe(subscribe_params)
-
- expect(subscription).to be_rejected
- end
- end
end
diff --git a/spec/support/shared_examples/ci/create_pipeline_service_environment_shared_examples.rb b/spec/support/shared_examples/ci/create_pipeline_service_environment_shared_examples.rb
new file mode 100644
index 00000000000..77a352a8326
--- /dev/null
+++ b/spec/support/shared_examples/ci/create_pipeline_service_environment_shared_examples.rb
@@ -0,0 +1,166 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'creating a pipeline with environment keyword' do
+ context 'with environment' do
+ let(:config) do
+ YAML.dump(
+ deploy: {
+ environment: { name: "review/$CI_COMMIT_REF_NAME" },
+ **base_config
+ })
+ end
+
+ it 'creates the environment', :sidekiq_inline do
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ expect(Environment.find_by(name: "review/master")).to be_present
+ expect(result.all_jobs.first.deployment).to be_persisted
+ expect(result.all_jobs.first.deployment.deployable).to be_a(expected_deployable_class)
+ end
+
+ it 'sets tags when build job' do
+ skip unless expected_deployable_class == Ci::Build
+
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ expect(result.all_jobs.first.tag_list).to match_array(expected_tag_names)
+ end
+ end
+
+ context 'with environment with auto_stop_in' do
+ let(:config) do
+ YAML.dump(
+ deploy: {
+ environment: { name: "review/$CI_COMMIT_REF_NAME", auto_stop_in: '1 day' },
+ **base_config
+ })
+ end
+
+ it 'creates the environment with auto stop in' do
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ expect(result.all_jobs.first.options[:environment][:auto_stop_in]).to eq('1 day')
+ end
+ end
+
+ context 'with environment name including persisted variables' do
+ let(:config) do
+ YAML.dump(
+ deploy: {
+ environment: { name: "review/id1$CI_PIPELINE_ID/id2$CI_JOB_ID" },
+ **base_config
+ }
+ )
+ end
+
+ it 'skips persisted variables in environment name' do
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ expect(Environment.find_by(name: "review/id1/id2")).to be_present
+ end
+ end
+
+ context 'when environment with Kubernetes configuration' do
+ let(:kubernetes_namespace) { 'custom-namespace' }
+ let(:config) do
+ YAML.dump(
+ deploy: {
+ environment: {
+ name: "environment-name",
+ kubernetes: { namespace: kubernetes_namespace }
+ },
+ **base_config
+ }
+ )
+ end
+
+ it 'stores the requested namespace' do
+ result = execute_service.payload
+ job = result.all_jobs.first
+
+ expect(result).to be_persisted
+ expect(job.options.dig(:environment, :kubernetes, :namespace)).to eq(kubernetes_namespace)
+ end
+ end
+
+ context 'when environment with invalid name' do
+ let(:config) do
+ YAML.dump(deploy: { environment: { name: 'name,with,commas' }, **base_config })
+ end
+
+ it 'does not create an environment' do
+ expect do
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ end.not_to change { Environment.count }
+ end
+ end
+
+ context 'when environment with duplicate names' do
+ let(:config) do
+ YAML.dump({
+ deploy: { environment: { name: 'production' }, **base_config },
+ deploy_2: { environment: { name: 'production' }, **base_config }
+ })
+ end
+
+ it 'creates a pipeline with the environment', :sidekiq_inline do
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ expect(Environment.find_by(name: 'production')).to be_present
+ expect(result.all_jobs.first.deployment).to be_persisted
+ expect(result.all_jobs.first.deployment.deployable).to be_a(expected_deployable_class)
+ end
+ end
+
+ context 'when pipeline has a job with environment' do
+ let(:pipeline) { execute_service.payload }
+
+ context 'when environment name is valid' do
+ let(:config) do
+ YAML.dump({
+ review_app: {
+ environment: {
+ name: 'review/${CI_COMMIT_REF_NAME}',
+ url: 'http://${CI_COMMIT_REF_SLUG}-staging.example.com'
+ },
+ **base_config
+ }
+ })
+ end
+
+ it 'has a job with environment', :sidekiq_inline do
+ expect(pipeline.all_jobs.count).to eq(1)
+ expect(pipeline.all_jobs.first.persisted_environment.name).to eq('review/master')
+ expect(pipeline.all_jobs.first.deployment.status).to eq(expected_deployment_status)
+ expect(pipeline.all_jobs.first.status).to eq(expected_job_status)
+ end
+ end
+
+ context 'when environment name is invalid' do
+ let(:config) do
+ YAML.dump({
+ 'job:deploy-to-test-site': {
+ environment: {
+ name: '${CI_JOB_NAME}',
+ url: 'https://$APP_URL'
+ },
+ **base_config
+ }
+ })
+ end
+
+ it 'has a job without environment' do
+ expect(pipeline.all_jobs.count).to eq(1)
+ expect(pipeline.all_jobs.first.persisted_environment).to be_nil
+ expect(pipeline.all_jobs.first.deployment).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/ci/deployable_shared_examples.rb b/spec/support/shared_examples/ci/deployable_shared_examples.rb
index b51a8fa20e2..4f43d38e604 100644
--- a/spec/support/shared_examples/ci/deployable_shared_examples.rb
+++ b/spec/support/shared_examples/ci/deployable_shared_examples.rb
@@ -96,7 +96,7 @@ RSpec.shared_examples 'a deployable job' do
ActiveRecord::QueryRecorder.new { subject }
end
- index_for_build = recorded.log.index { |l| l.include?("UPDATE #{Ci::Build.quoted_table_name}") }
+ index_for_build = recorded.log.index { |l| l.include?("UPDATE #{described_class.quoted_table_name}") }
index_for_deployment = recorded.log.index { |l| l.include?("UPDATE \"deployments\"") }
expect(index_for_build).to be < index_for_deployment
@@ -259,7 +259,7 @@ RSpec.shared_examples 'a deployable job' do
describe '#environment_tier_from_options' do
subject { job.environment_tier_from_options }
- let(:job) { Ci::Build.new(options: options) }
+ let(:job) { described_class.new(options: options) }
let(:options) { { environment: { deployment_tier: 'production' } } }
it { is_expected.to eq('production') }
@@ -276,7 +276,7 @@ RSpec.shared_examples 'a deployable job' do
let(:options) { { environment: { deployment_tier: 'production' } } }
let!(:environment) { create(:environment, name: 'production', tier: 'development', project: project) }
- let(:job) { Ci::Build.new(options: options, environment: 'production', project: project) }
+ let(:job) { described_class.new(options: options, environment: 'production', project: project) }
it { is_expected.to eq('production') }
@@ -295,6 +295,52 @@ RSpec.shared_examples 'a deployable job' do
end
end
+ describe '#environment_url' do
+ subject { job.environment_url }
+
+ let!(:job) { create(factory_type, :with_deployment, :deploy_to_production, pipeline: pipeline) }
+
+ it { is_expected.to eq('http://prd.example.com/$CI_JOB_NAME') }
+
+ context 'when options does not include url' do
+ before do
+ job.update!(options: { environment: { url: nil } })
+ job.persisted_environment.update!(external_url: 'http://prd.example.com/$CI_JOB_NAME')
+ end
+
+ it 'fetches from the persisted environment' do
+ expect_any_instance_of(::Environment) do |environment|
+ expect(environment).to receive(:external_url).once
+ end
+
+ is_expected.to eq('http://prd.example.com/$CI_JOB_NAME')
+ end
+
+ context 'when persisted environment is absent' do
+ before do
+ job.clear_memoization(:persisted_environment)
+ job.persisted_environment = nil
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+
+ describe '#environment_slug' do
+ subject { job.environment_slug }
+
+ let!(:job) { create(factory_type, :with_deployment, :start_review_app, pipeline: pipeline) }
+
+ it { is_expected.to eq('review-master-8dyme2') }
+
+ context 'when persisted environment is absent' do
+ let!(:job) { create(factory_type, :start_review_app, pipeline: pipeline) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
describe 'environment' do
describe '#has_environment_keyword?' do
subject { job.has_environment_keyword? }
@@ -536,10 +582,6 @@ RSpec.shared_examples 'a deployable job' do
end
describe '#deployment_status' do
- before do
- allow_any_instance_of(Ci::Build).to receive(:create_deployment) # rubocop:disable RSpec/AnyInstanceOf
- end
-
context 'when job is a last deployment' do
let(:job) { create(factory_type, :success, environment: 'production', pipeline: pipeline) }
let(:environment) { create(:environment, name: 'production', project: job.project) }
diff --git a/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb b/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
index e61c884cd2b..14d0ac81250 100644
--- a/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
+++ b/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
@@ -121,8 +121,7 @@ RSpec.shared_examples 'every metric definition' do
let(:ignored_classes) do
[
Gitlab::Usage::Metrics::Instrumentations::IssuesWithAlertManagementAlertsMetric,
- Gitlab::Usage::Metrics::Instrumentations::IssuesWithPrometheusAlertEvents,
- Gitlab::Usage::Metrics::Instrumentations::IssuesWithSelfManagedPrometheusAlertEvents
+ Gitlab::Usage::Metrics::Instrumentations::IssuesWithPrometheusAlertEvents
].freeze
end
diff --git a/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb b/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb
index 56a5dcb10b2..cb4e68122d9 100644
--- a/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb
@@ -43,5 +43,14 @@ RSpec.shared_examples WebHooks::HookLogActions do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ it 'redirects back with a warning if the hook log url is outdated' do
+ web_hook_log.update!(url_hash: 'some_other_value')
+
+ post retry_path, headers: { 'REFERER' => show_path }
+
+ expect(response).to redirect_to(show_path)
+ expect(flash[:warning]).to eq(_('The hook URL has changed, and this log entry cannot be retried'))
+ end
end
end
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index a4eb6a839c0..bd9c2582d2f 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -18,23 +18,6 @@ RSpec.shared_examples 'issuable notes filter' do
expect(UserPreference.count).to eq(1)
end
- it 'expires notes e-tag cache for issuable if filter changed' do
- notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
-
- expect_any_instance_of(issuable.class).to receive(:expire_note_etag_cache)
-
- get :discussions, params: params.merge(notes_filter: notes_filter)
- end
-
- it 'does not expires notes e-tag cache for issuable if filter did not change' do
- notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
- user.set_notes_filter(notes_filter, issuable)
-
- expect_any_instance_of(issuable.class).not_to receive(:expire_note_etag_cache)
-
- get :discussions, params: params.merge(notes_filter: notes_filter)
- end
-
it 'does not set notes filter when database is in read-only mode' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
diff --git a/spec/support/shared_examples/controllers/labels_controller_shared_examples.rb b/spec/support/shared_examples/controllers/labels_controller_shared_examples.rb
new file mode 100644
index 00000000000..aea552f6ac7
--- /dev/null
+++ b/spec/support/shared_examples/controllers/labels_controller_shared_examples.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'lock_on_merge when editing labels' do
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(enforce_locked_labels_on_merge: false)
+ visit edit_label_path_unlocked
+ end
+
+ it 'does not display the checkbox/help text' do
+ expect(page).not_to have_content(_('Lock label after a merge request is merged'))
+ expect(page).not_to have_content(label_lock_on_merge_help_text)
+ end
+ end
+
+ it 'updates lock_on_merge' do
+ expect(page).to have_content(_('Lock label after a merge request is merged'))
+ expect(page).to have_content(label_lock_on_merge_help_text)
+
+ check(_('Lock label after a merge request is merged'))
+ click_button 'Save changes'
+
+ expect(label_unlocked.reload.lock_on_merge).to be_truthy
+ end
+
+ it 'checkbox is disabled if lock_on_merge already set' do
+ visit edit_label_path_locked
+
+ expect(page.find('#label_lock_on_merge')).to be_disabled
+ end
+end
+
+RSpec.shared_examples 'lock_on_merge when creating labels' do
+ it 'is not supported when creating a label' do
+ expect(page).not_to have_content(_('Lock label after a merge request is merged'))
+ expect(page).not_to have_content(label_lock_on_merge_help_text)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/search_rate_limit_shared_examples.rb b/spec/support/shared_examples/controllers/search_rate_limit_shared_examples.rb
new file mode 100644
index 00000000000..aefcdc70082
--- /dev/null
+++ b/spec/support/shared_examples/controllers/search_rate_limit_shared_examples.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# Requires a context containing:
+# - user
+# - params
+
+RSpec.shared_examples 'search request exceeding rate limit' do
+ include_examples 'rate limited endpoint', rate_limit_key: :search_rate_limit
+
+ it 'allows user in allow-list to search without applying rate limit', :freeze_time,
+ :clean_gitlab_redis_rate_limiting do
+ allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).with(:search_rate_limit).and_return(1)
+
+ stub_application_setting(search_rate_limit_allowlist: [current_user.username])
+
+ request
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
index 3d3b619451d..29d6f202498 100644
--- a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
+++ b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
@@ -57,7 +57,7 @@ RSpec.shared_examples 'Snowplow event tracking with Redis context' do |overrides
it_behaves_like 'Snowplow event tracking', overrides: overrides do
let(:context) do
key_path = try(:label) || action
- [Gitlab::Tracking::ServicePingContext.new(data_source: :redis, key_path: key_path).to_context.to_json]
+ [Gitlab::Usage::MetricDefinition.context_for(key_path).to_context.to_json]
end
end
end
diff --git a/spec/support/shared_examples/features/2fa_shared_examples.rb b/spec/support/shared_examples/features/2fa_shared_examples.rb
index 6c4e98c9989..f50874b6b05 100644
--- a/spec/support/shared_examples/features/2fa_shared_examples.rb
+++ b/spec/support/shared_examples/features/2fa_shared_examples.rb
@@ -14,7 +14,7 @@ RSpec.shared_examples 'hardware device for 2fa' do |device_type|
end
describe "registration" do
- let(:user) { create(:user) }
+ let(:user) { create(:user, :no_super_sidebar) }
before do
gitlab_sign_in(user)
@@ -67,7 +67,7 @@ RSpec.shared_examples 'hardware device for 2fa' do |device_type|
end
describe 'fallback code authentication' do
- let(:user) { create(:user) }
+ let(:user) { create(:user, :no_super_sidebar) }
before do
# Register and logout
diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb
index fff8ef915eb..3e81f969462 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -244,7 +244,8 @@ RSpec.shared_examples 'edits content using the content editor' do |params = { wi
end
end
- it 'expands the link, updates the link attributes and text if text is updated' do
+ it 'expands the link, updates the link attributes and text if text is updated',
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/419684' do
page.within '[data-testid="link-bubble-menu"]' do
fill_in 'link-text', with: 'new text'
fill_in 'link-href', with: 'https://about.gitlab.com'
diff --git a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
index d410653ca43..58bf461c733 100644
--- a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
@@ -4,8 +4,8 @@ RSpec.shared_examples 'project features apply to issuables' do |klass|
let(:described_class) { klass }
let(:group) { create(:group) }
- let(:user_in_group) { create(:group_member, :developer, user: create(:user), group: group ).user }
- let(:user_outside_group) { create(:user) }
+ let(:user_in_group) { create(:group_member, :developer, user: create(:user, :no_super_sidebar), group: group ).user }
+ let(:user_outside_group) { create(:user, :no_super_sidebar) }
let(:project) { create(:project, :public, project_args) }
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
index fb882ef8a23..fc717fbac20 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
@@ -2,6 +2,7 @@
RSpec.shared_examples "protected branches > access control > CE" do
let(:no_one) { ProtectedRef::AccessLevel.humanize(::Gitlab::Access::NO_ACCESS) }
+ let_it_be(:edit_form) { '.js-protected-branch-edit-form' }
ProtectedRef::AccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
it "allows creating protected branches that #{access_type_name} can push to" do
@@ -30,7 +31,8 @@ RSpec.shared_examples "protected branches > access control > CE" do
expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id])
end
- it "allows updating protected branches so that #{access_type_name} can push to them" do
+ it "allows updating protected branches so that #{access_type_name} can push to them",
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/425080' do
visit project_protected_branches_path(project)
show_add_form
@@ -41,18 +43,14 @@ RSpec.shared_examples "protected branches > access control > CE" do
expect(ProtectedBranch.count).to eq(1)
- within(".protected-branches-list") do
- within_select(".js-allowed-to-push") do
- click_on(access_type_name)
- end
- end
-
+ set_allowed_to('push', access_type_name, form: edit_form)
wait_for_requests
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id)
end
- it "allows updating protected branches so that #{access_type_name} can merge to them" do
+ it "allows updating protected branches so that #{access_type_name} can merge to them",
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/425080' do
visit project_protected_branches_path(project)
show_add_form
@@ -63,12 +61,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
expect(ProtectedBranch.count).to eq(1)
- within(".protected-branches-list") do
- within_select(".js-allowed-to-merge") do
- click_on(access_type_name)
- end
- end
-
+ set_allowed_to('merge', access_type_name, form: edit_form)
wait_for_requests
expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to include(access_type_id)
diff --git a/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb
index 703ba5b018a..2b7147fa4b4 100644
--- a/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb
+++ b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb
@@ -19,7 +19,7 @@ RSpec.shared_examples 'Deploy keys with protected tags' do
find(".js-allowed-to-create").click
wait_for_requests
- within('[data-testid="allowed-to-create-dropdown"]') do
+ within('.dropdown-menu') do
dropdown_headers = page.all('.dropdown-header').map(&:text)
expect(dropdown_headers).to contain_exactly(*all_dropdown_sections)
@@ -53,7 +53,7 @@ RSpec.shared_examples 'Deploy keys with protected tags' do
find(".js-allowed-to-create").click
wait_for_requests
- within('[data-testid="allowed-to-create-dropdown"]') do
+ within('.dropdown-menu') do
dropdown_headers = page.all('.dropdown-header').map(&:text)
expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys)
diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb
index 0c043f48c5f..861c205337a 100644
--- a/spec/support/shared_examples/features/runners_shared_examples.rb
+++ b/spec/support/shared_examples/features/runners_shared_examples.rb
@@ -94,6 +94,17 @@ RSpec.shared_examples 'shows runner in list' do
end
end
+RSpec.shared_examples 'shows runner details from list' do
+ it 'shows runner details page' do
+ click_link("##{runner.id} (#{runner.short_sha})")
+
+ expect(current_url).to include(runner_page_path)
+
+ expect(page).to have_selector 'h1', text: "##{runner.id} (#{runner.short_sha})"
+ expect(page).to have_content "#{s_('Runners|Description')} #{runner.description}"
+ end
+end
+
RSpec.shared_examples 'pauses, resumes and deletes a runner' do
include Spec::Support::Helpers::ModalHelpers
@@ -191,6 +202,13 @@ RSpec.shared_examples 'shows runner jobs tab' do
end
end
+RSpec.shared_examples 'shows locked field' do
+ it 'shows locked checkbox with description', :js do
+ expect(page).to have_selector('input[type="checkbox"][name="locked"]')
+ expect(page).to have_content(_('Lock to current projects'))
+ end
+end
+
RSpec.shared_examples 'submits edit runner form' do
it 'breadcrumb contains runner id and token' do
page.within '[data-testid="breadcrumb-links"]' do
diff --git a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb
index 8ebec19a884..c2d144bef3b 100644
--- a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb
@@ -86,7 +86,7 @@ RSpec.shared_examples 'labels sidebar widget' do
context 'creating a label', :js do
before do
page.within(labels_widget) do
- page.find('[data-testid="create-label-button"]').click
+ click_button 'Create project label'
end
end
@@ -96,12 +96,11 @@ RSpec.shared_examples 'labels sidebar widget' do
end
end
- it 'creates new label', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391240' do
+ it 'creates new label' do
page.within(labels_widget) do
fill_in 'Name new label', with: 'wontfix'
- page.find('.suggest-colors a', match: :first).click
- page.find('button', text: 'Create').click
- wait_for_requests
+ click_link 'Magenta-pink'
+ click_button 'Create'
expect(page).to have_content 'wontfix'
end
diff --git a/spec/support/shared_examples/features/snippets_shared_examples.rb b/spec/support/shared_examples/features/snippets_shared_examples.rb
index bf870b3ce66..383f81d048f 100644
--- a/spec/support/shared_examples/features/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/features/snippets_shared_examples.rb
@@ -52,7 +52,7 @@ RSpec.shared_examples 'tabs with counts' do
end
RSpec.shared_examples 'does not show New Snippet button' do
- let(:user) { create(:user, :external) }
+ let(:user) { create(:user, :external, :no_super_sidebar) }
specify do
sign_in(user)
diff --git a/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb b/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb
index 0b0c9edcb42..c1057671699 100644
--- a/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb
@@ -41,15 +41,15 @@ RSpec.shared_examples 'variable list pagination' do |variable_type|
it 'sorts variables alphabetically in ASC and DESC order' do
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key)
- expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]').text).to eq('test_key_8')
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]')).to have_content(variable.key)
+ expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]')).to have_content('test_key_8')
end
click_button 'Next'
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('test_key_9')
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]')).to have_content('test_key_9')
end
page.within('[data-testid="ci-variable-table"]') do
@@ -59,8 +59,8 @@ RSpec.shared_examples 'variable list pagination' do |variable_type|
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('test_key_9')
- expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]').text).to eq('test_key_0')
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]')).to have_content('test_key_9')
+ expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]')).to have_content('test_key_0')
end
end
end
diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb
index 3a91b798bbd..5951d3e781b 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -3,7 +3,7 @@
RSpec.shared_examples 'variable list' do
it 'shows a list of variables' do
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key)
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]')).to have_content(variable.key)
end
end
@@ -17,7 +17,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]')).to have_content('key')
end
end
@@ -31,8 +31,8 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']").text).to eq('key')
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Attributes')}']")).to have_content(s_('CiVariables|Protected'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).to have_content('key')
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).to have_content(s_('CiVariables|Protected'))
end
end
@@ -46,25 +46,25 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']").text).to eq('key')
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Attributes')}']")).not_to have_content(s_('CiVariables|Masked'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).to have_content('key')
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).not_to have_content(s_('CiVariables|Masked'))
end
end
it 'reveals and hides variables' do
page.within('[data-testid="ci-variable-table"]') do
- expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
+ expect(first('.js-ci-variable-row td[data-label="Key"]')).to have_content(variable.key)
expect(page).to have_content('*' * 5)
click_button('Reveal value')
- expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
- expect(first('.js-ci-variable-row td[data-label="Value"]').text).to eq(variable.value)
+ expect(first('.js-ci-variable-row td[data-label="Key"]')).to have_content(variable.key)
+ expect(first('.js-ci-variable-row td[data-label="Value"]')).to have_content(variable.value)
expect(page).not_to have_content('*' * 5)
click_button('Hide value')
- expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
+ expect(first('.js-ci-variable-row td[data-label="Key"]')).to have_content(variable.key)
expect(page).to have_content('*' * 5)
end
end
@@ -98,7 +98,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
- expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq('new_key')
+ expect(first('.js-ci-variable-row td[data-label="Key"]')).to have_content('new_key')
end
it 'edits a variable to be unmasked' do
@@ -116,8 +116,8 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Attributes')}']")).to have_content(s_('CiVariables|Protected'))
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Attributes')}']")).not_to have_content(s_('CiVariables|Masked'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).to have_content(s_('CiVariables|Protected'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).not_to have_content(s_('CiVariables|Masked'))
end
end
@@ -145,7 +145,7 @@ RSpec.shared_examples 'variable list' do
end
page.within('[data-testid="ci-variable-table"]') do
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Attributes')}']")).to have_content(s_('CiVariables|Masked'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).to have_content(s_('CiVariables|Masked'))
end
end
@@ -234,9 +234,9 @@ RSpec.shared_examples 'variable list' do
# expect to find 3 rows of variables in alphabetical order
expect(page).to have_selector('.js-ci-variable-row', count: 3)
rows = all('.js-ci-variable-row')
- expect(rows[0].find('td[data-label="Key"]').text).to eq('ckey')
- expect(rows[1].find('td[data-label="Key"]').text).to eq('test_key')
- expect(rows[2].find('td[data-label="Key"]').text).to eq('zkey')
+ expect(rows[0].find('td[data-label="Key"]')).to have_content('ckey')
+ expect(rows[1].find('td[data-label="Key"]')).to have_content('test_key')
+ expect(rows[2].find('td[data-label="Key"]')).to have_content('zkey')
end
context 'defaults to the application setting' do
diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb
index d3863c9a675..18e0cfdad00 100644
--- a/spec/support/shared_examples/features/work_items_shared_examples.rb
+++ b/spec/support/shared_examples/features/work_items_shared_examples.rb
@@ -179,6 +179,45 @@ RSpec.shared_examples 'work items assignees' do
expect(work_item.reload.assignees).to include(user)
end
+
+ it 'successfully assigns the current user by clicking `Assign myself` button' do
+ find('[data-testid="work-item-assignees-input"]').hover
+ find('[data-testid="assign-self"]').click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).to include(user)
+ end
+
+ it 'successfully removes all users on clear all button click' do
+ find('[data-testid="work-item-assignees-input"]').hover
+ find('[data-testid="assign-self"]').click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).to include(user)
+
+ find('[data-testid="work-item-assignees-input"]').click
+ find('[data-testid="clear-all-button"]').click
+ find("body").click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).not_to include(user)
+ end
+
+ it 'successfully removes user on clicking badge cross button' do
+ find('[data-testid="work-item-assignees-input"]').hover
+ find('[data-testid="assign-self"]').click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).to include(user)
+
+ within('[data-testid="work-item-assignees-input"]') do
+ find('[data-testid="close-icon"]').click
+ end
+ find("body").click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).not_to include(user)
+ end
end
RSpec.shared_examples 'work items labels' do
diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
index 19001abcbe2..ed8feebf1f6 100644
--- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
+++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
@@ -1406,7 +1406,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
expect(finder.execute.to_sql)
- .to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
+ .to match(/^WITH "issues" AS MATERIALIZED/)
end
end
@@ -1416,7 +1416,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
expect(finder.execute.to_sql)
- .to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
+ .to match(/^WITH "issues" AS MATERIALIZED/)
end
end
@@ -1426,7 +1426,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
expect(finder.execute.to_sql)
- .to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
+ .to match(/^WITH "issues" AS MATERIALIZED/)
end
end
@@ -1436,7 +1436,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
expect(finder.execute.to_sql)
- .to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
+ .to match(/^WITH "issues" AS MATERIALIZED/)
end
end
end
diff --git a/spec/support/shared_examples/graphql/mutations/update_time_estimate_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/update_time_estimate_shared_examples.rb
index d6d360bb413..a69b56c3d58 100644
--- a/spec/support/shared_examples/graphql/mutations/update_time_estimate_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/update_time_estimate_shared_examples.rb
@@ -6,11 +6,25 @@ RSpec.shared_examples 'updating time estimate' do
let(:input_params) { input.merge(extra_params).merge({ timeEstimate: time_estimate }) }
+ before do
+ resource.update!(time_estimate: 1800)
+ end
+
+ context 'when time estimate is not provided' do
+ let(:input_params) { input.merge(extra_params).except(:timeEstimate) }
+
+ it 'does not update' do
+ expect { post_graphql_mutation(mutation, current_user: current_user) }
+ .not_to change { resource.reload.time_estimate }
+ end
+ end
+
context 'when time estimate is not a valid numerical value' do
let(:time_estimate) { '-3.5d' }
it 'does not update' do
- expect { post_graphql_mutation(mutation, current_user: current_user) }.not_to change { resource.time_estimate }
+ expect { post_graphql_mutation(mutation, current_user: current_user) }
+ .not_to change { resource.reload.time_estimate }
end
it 'returns error' do
@@ -24,7 +38,8 @@ RSpec.shared_examples 'updating time estimate' do
let(:time_estimate) { 'nonsense' }
it 'does not update' do
- expect { post_graphql_mutation(mutation, current_user: current_user) }.not_to change { resource.time_estimate }
+ expect { post_graphql_mutation(mutation, current_user: current_user) }
+ .not_to change { resource.reload.time_estimate }
end
it 'returns error' do
@@ -47,6 +62,7 @@ RSpec.shared_examples 'updating time estimate' do
'1h' | 3600
'0h' | 0
'-0h' | 0
+ nil | 0
end
with_them do
diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
index b346f35bdc9..2c94f21e144 100644
--- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
@@ -61,14 +61,6 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
expect(deprecable.deprecation_reason).to eq('This was renamed. Deprecated in 1.10.')
end
- it 'supports named reasons: alpha' do
- deprecable = subject(deprecated: { milestone: '1.10', reason: :alpha })
-
- expect(deprecable.deprecation_reason).to eq(
- 'This feature is an Experiment. It can be changed or removed at any time. Introduced in 1.10.'
- )
- end
-
it 'supports :alpha' do
deprecable = subject(alpha: { milestone: '1.10' })
@@ -82,7 +74,7 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
subject(alpha: { milestone: '1.10' }, deprecated: { milestone: '1.10', reason: 'my reason' } )
end.to raise_error(
ArgumentError,
- eq("`experiment` and `deprecated` arguments cannot be passed at the same time")
+ eq("`alpha` and `deprecated` arguments cannot be passed at the same time")
)
end
diff --git a/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb b/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb
index 85fcd426e3d..16e25bf96dd 100644
--- a/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb
+++ b/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb
@@ -87,17 +87,7 @@ RSpec.shared_examples 'a harbor artifacts controller' do |args|
get harbor_artifact_url(container, repository_id), headers: json_header
end
- context 'with harbor registry feature flag enabled' do
- it_behaves_like 'responds with 200 status with json'
- end
-
- context 'with harbor registry feature flag disabled' do
- before do
- stub_feature_flags(harbor_registry_integration: false)
- end
-
- it_behaves_like 'responds with 404 status'
- end
+ it_behaves_like 'responds with 200 status with json'
context 'with anonymous user' do
before do
diff --git a/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb b/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb
index b35595a10b2..a0d47d1a2d1 100644
--- a/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb
+++ b/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb
@@ -87,17 +87,7 @@ RSpec.shared_examples 'a harbor repositories controller' do |args|
get harbor_repository_url(container)
end
- context 'with harbor registry feature flag enabled' do
- it_behaves_like 'responds with 200 status with html'
- end
-
- context 'with harbor registry feature flag disabled' do
- before do
- stub_feature_flags(harbor_registry_integration: false)
- end
-
- it_behaves_like 'responds with 404 status'
- end
+ it_behaves_like 'responds with 200 status with html'
context 'with anonymous user' do
before do
@@ -121,17 +111,7 @@ RSpec.shared_examples 'a harbor repositories controller' do |args|
get harbor_repository_url(container), headers: json_header
end
- context 'with harbor registry feature flag enabled' do
- it_behaves_like 'responds with 200 status with json'
- end
-
- context 'with harbor registry feature flag disabled' do
- before do
- stub_feature_flags(harbor_registry_integration: false)
- end
-
- it_behaves_like 'responds with 404 status'
- end
+ it_behaves_like 'responds with 200 status with json'
context 'with valid params' do
context 'with valid page params' do
diff --git a/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb b/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb
index 46fea7fdff6..aee728295de 100644
--- a/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb
+++ b/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb
@@ -76,17 +76,7 @@ RSpec.shared_examples 'a harbor tags controller' do |args|
headers: json_header)
end
- context 'with harbor registry feature flag enabled' do
- it_behaves_like 'responds with 200 status with json'
- end
-
- context 'with harbor registry feature flag disabled' do
- before do
- stub_feature_flags(harbor_registry_integration: false)
- end
-
- it_behaves_like 'responds with 404 status'
- end
+ it_behaves_like 'responds with 200 status with json'
context 'with anonymous user' do
before do
diff --git a/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb b/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb
deleted file mode 100644
index d4fe45a91a0..00000000000
--- a/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'behind AI related feature flags' do |provider_flag|
- context "when #{provider_flag} is disabled" do
- before do
- stub_feature_flags(provider_flag => false)
- end
-
- it 'responds as not found' do
- post api(url, current_user), params: input_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'when ai_experimentation_api is disabled' do
- before do
- stub_feature_flags(ai_experimentation_api: false)
- end
-
- it 'responds as not found' do
- post api(url, current_user), params: input_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-end
-
-RSpec.shared_examples 'delegates AI request to Workhorse' do
- it 'responds with Workhorse send-url headers' do
- post api(url, current_user), params: input_params
-
- expect(response.body).to eq('""')
- expect(response).to have_gitlab_http_status(:ok)
-
- send_url_prefix, encoded_data = response.headers['Gitlab-Workhorse-Send-Data'].split(':')
- data = Gitlab::Json.parse(Base64.urlsafe_decode64(encoded_data))
-
- expect(send_url_prefix).to eq('send-url')
- expect(data).to eq({
- 'AllowRedirects' => false,
- 'Method' => 'POST'
- }.merge(expected_params))
- end
-end
diff --git a/spec/support/shared_examples/lib/gitlab/bitbucket_import/object_import_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/bitbucket_import/object_import_shared_examples.rb
new file mode 100644
index 00000000000..3dbe43d822f
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/bitbucket_import/object_import_shared_examples.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Gitlab::BitbucketImport::ObjectImporter do
+ include AfterNextHelpers
+
+ describe '.sidekiq_retries_exhausted' do
+ let(:job) { { 'args' => [1, {}, 'key'], 'jid' => 'jid' } }
+
+ it 'notifies the waiter' do
+ expect(Gitlab::JobWaiter).to receive(:notify).with('key', 'jid')
+
+ described_class.sidekiq_retries_exhausted_block.call(job, StandardError.new)
+ end
+ end
+
+ describe '#perform' do
+ let_it_be(:import_started_project) { create(:project, :import_started) }
+
+ let(:project_id) { project_id }
+ let(:waiter_key) { 'key' }
+
+ shared_examples 'notifies the waiter' do
+ specify do
+ allow_next(worker.importer_class).to receive(:execute)
+
+ expect(Gitlab::JobWaiter).to receive(:notify).with(waiter_key, anything)
+
+ worker.perform(project_id, {}, waiter_key)
+ end
+ end
+
+ context 'when project does not exist' do
+ let(:project_id) { non_existing_record_id }
+
+ it_behaves_like 'notifies the waiter'
+ end
+
+ context 'when project has import started' do
+ let_it_be(:project) do
+ create(:project, :import_started, import_data_attributes: {
+ data: { 'project_key' => 'key', 'repo_slug' => 'slug' },
+ credentials: { 'token' => 'token' }
+ })
+ end
+
+ let(:project_id) { project.id }
+
+ it 'calls the importer' do
+ expect(Gitlab::BitbucketImport::Logger).to receive(:info).twice
+ expect_next(worker.importer_class, project, kind_of(Hash)).to receive(:execute)
+
+ worker.perform(project_id, {}, waiter_key)
+ end
+
+ it_behaves_like 'notifies the waiter'
+
+ context 'when the importer raises an ActiveRecord::RecordInvalid error' do
+ before do
+ allow_next(worker.importer_class).to receive(:execute).and_raise(ActiveRecord::RecordInvalid)
+ end
+
+ it 'tracks the error' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ worker.perform(project_id, {}, waiter_key)
+ end
+ end
+
+ context 'when the importer raises a StandardError' do
+ before do
+ allow_next(worker.importer_class).to receive(:execute).and_raise(StandardError)
+ end
+
+ it 'tracks the error and raises the error' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ expect { worker.perform(project_id, {}, waiter_key) }.to raise_error(StandardError)
+ end
+ end
+ end
+
+ context 'when project import has been cancelled' do
+ let_it_be(:project_id) { create(:project, :import_canceled).id }
+
+ it 'does not call the importer' do
+ expect_next(worker.importer_class).not_to receive(:execute)
+
+ worker.perform(project_id, {}, waiter_key)
+ end
+
+ it_behaves_like 'notifies the waiter'
+ end
+ end
+
+ describe '#importer_class' do
+ it 'does not raise a NotImplementedError' do
+ expect(worker.importer_class).not_to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb
new file mode 100644
index 00000000000..f128aa92a53
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Gitlab::BitbucketImport::StageMethods do
+ describe '.sidekiq_retries_exhausted' do
+ let(:job) { { 'args' => [project.id] } }
+
+ it 'tracks the import failure' do
+ expect(Gitlab::Import::ImportFailureService)
+ .to receive(:track).with(
+ project_id: project.id,
+ exception: StandardError.new,
+ fail_import: true
+ )
+
+ described_class.sidekiq_retries_exhausted_block.call(job, StandardError.new)
+ end
+ end
+
+ describe '.perform' do
+ let(:worker) { described_class.new }
+
+ it 'executes the import' do
+ expect(worker).to receive(:import).with(project).once
+ expect(Gitlab::BitbucketImport::Logger).to receive(:info).twice
+
+ worker.perform(project.id)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/object_import_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/object_import_shared_examples.rb
index ec2ae0b8a73..4eae8632467 100644
--- a/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/object_import_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/object_import_shared_examples.rb
@@ -7,7 +7,7 @@ RSpec.shared_examples Gitlab::BitbucketServerImport::ObjectImporter do
let(:job) { { 'args' => [1, {}, 'key'], 'jid' => 'jid' } }
it 'notifies the waiter' do
- expect(Gitlab::JobWaiter).to receive(:notify).with('key', 'jid')
+ expect(Gitlab::JobWaiter).to receive(:notify).with('key', 'jid', ttl: Gitlab::Import::JOB_WAITER_TTL)
described_class.sidekiq_retries_exhausted_block.call(job, StandardError.new)
end
@@ -23,7 +23,7 @@ RSpec.shared_examples Gitlab::BitbucketServerImport::ObjectImporter do
specify do
allow_next(worker.importer_class).to receive(:execute)
- expect(Gitlab::JobWaiter).to receive(:notify).with(waiter_key, anything)
+ expect(Gitlab::JobWaiter).to receive(:notify).with(waiter_key, anything, ttl: Gitlab::Import::JOB_WAITER_TTL)
worker.perform(project_id, {}, waiter_key)
end
diff --git a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
index 10f58748698..7cfab5c8295 100644
--- a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
@@ -35,8 +35,8 @@ RSpec.shared_examples 'common trace features' do
stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: trace.job.project)
end
- it 'calls ::Ci::Build.sticking.unstick_or_continue_sticking' do
- expect(::Ci::Build.sticking).to receive(:unstick_or_continue_sticking)
+ it 'calls ::Ci::Build.sticking.find_caught_up_replica' do
+ expect(::Ci::Build.sticking).to receive(:find_caught_up_replica)
.with(described_class::LOAD_BALANCING_STICKING_NAMESPACE, trace.job.id)
.and_call_original
@@ -49,8 +49,8 @@ RSpec.shared_examples 'common trace features' do
stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: false)
end
- it 'does not call ::Ci::Build.sticking.unstick_or_continue_sticking' do
- expect(::Ci::Build.sticking).not_to receive(:unstick_or_continue_sticking)
+ it 'does not call ::Ci::Build.sticking.find_caught_up_replica' do
+ expect(::Ci::Build.sticking).not_to receive(:find_caught_up_replica)
trace.read { |stream| stream }
end
diff --git a/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
index df795723874..b80a51a1fc6 100644
--- a/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
@@ -4,40 +4,17 @@ RSpec.shared_examples 'CTE with MATERIALIZED keyword examples' do
describe 'adding MATERIALIZE to the CTE' do
let(:options) { {} }
- before do
- # Clear the cached value before the test
- Gitlab::Database::AsWithMaterialized.clear_memoization(:materialized_supported)
- end
-
- context 'when PG version is <12' do
- it 'does not add MATERIALIZE keyword' do
- allow(ApplicationRecord.database).to receive(:version).and_return('11.1')
+ it 'adds MATERIALIZE keyword' do
+ allow(ApplicationRecord.database).to receive(:version).and_return('12.1')
- expect(query).to include(expected_query_block_without_materialized)
- end
+ expect(query).to include(expected_query_block_with_materialized)
end
- context 'when PG version is >=12' do
- it 'adds MATERIALIZE keyword' do
- allow(ApplicationRecord.database).to receive(:version).and_return('12.1')
-
- expect(query).to include(expected_query_block_with_materialized)
- end
+ context 'when materialized is disabled' do
+ let(:options) { { materialized: false } }
- context 'when version is higher than 12' do
- it 'adds MATERIALIZE keyword' do
- allow(ApplicationRecord.database).to receive(:version).and_return('15.1')
-
- expect(query).to include(expected_query_block_with_materialized)
- end
- end
-
- context 'when materialized is disabled' do
- let(:options) { { materialized: false } }
-
- it 'does not add MATERIALIZE keyword' do
- expect(query).to include(expected_query_block_without_materialized)
- end
+ it 'does not add MATERIALIZE keyword' do
+ expect(query).to include(expected_query_block_without_materialized)
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb
new file mode 100644
index 00000000000..0fef5269ab6
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Gitlab::Import::AdvanceStage do |factory:|
+ let_it_be(:project) { create(:project) }
+ let_it_be_with_reload(:import_state) { create(factory, :started, project: project, jid: '123') }
+ let(:worker) { described_class.new }
+ let(:next_stage) { :finish }
+
+ describe '#perform', :clean_gitlab_redis_shared_state do
+ context 'when the project no longer exists' do
+ it 'does not perform any work' do
+ expect(worker).not_to receive(:wait_for_jobs)
+
+ worker.perform(non_existing_record_id, { '123' => 2 }, next_stage)
+ end
+ end
+
+ context 'when there are remaining jobs' do
+ it 'reschedules itself' do
+ expect(worker)
+ .to receive(:wait_for_jobs)
+ .with({ '123' => 2 })
+ .and_return({ '123' => 1 })
+
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(described_class::INTERVAL, project.id, { '123' => 1 }, next_stage)
+
+ worker.perform(project.id, { '123' => 2 }, next_stage)
+ end
+
+ context 'when the project import is not running' do
+ before do
+ import_state.update_column(:status, :failed)
+ end
+
+ it 'does not perform any work' do
+ expect(worker).not_to receive(:wait_for_jobs)
+ expect(described_class).not_to receive(:perform_in)
+
+ worker.perform(project.id, { '123' => 2 }, next_stage)
+ end
+
+ it 'clears the JobWaiter cache' do
+ expect(Gitlab::JobWaiter).to receive(:delete_key).with('123')
+
+ worker.perform(project.id, { '123' => 2 }, next_stage)
+ end
+ end
+ end
+
+ context 'when there are no remaining jobs' do
+ before do
+ allow(worker)
+ .to receive(:wait_for_jobs)
+ .with({ '123' => 2 })
+ .and_return({})
+ end
+
+ it 'schedules the next stage' do
+ next_worker = described_class::STAGES[next_stage]
+
+ expect_next_found_instance_of(import_state.class) do |state|
+ expect(state).to receive(:refresh_jid_expiration)
+ end
+
+ expect(next_worker).to receive(:perform_async).with(project.id)
+
+ worker.perform(project.id, { '123' => 2 }, next_stage)
+ end
+
+ it 'raises KeyError when the stage name is invalid' do
+ expect { worker.perform(project.id, { '123' => 2 }, :kittens) }
+ .to raise_error(KeyError)
+ end
+ end
+ end
+
+ describe '#wait_for_jobs' do
+ it 'waits for jobs to complete and returns a new pair of keys to wait for' do
+ waiter1 = instance_double("Gitlab::JobWaiter", jobs_remaining: 1, key: '123')
+ waiter2 = instance_double("Gitlab::JobWaiter", jobs_remaining: 0, key: '456')
+
+ expect(Gitlab::JobWaiter)
+ .to receive(:new)
+ .ordered
+ .with(2, '123')
+ .and_return(waiter1)
+
+ expect(Gitlab::JobWaiter)
+ .to receive(:new)
+ .ordered
+ .with(1, '456')
+ .and_return(waiter2)
+
+ expect(waiter1)
+ .to receive(:wait)
+ .with(described_class::BLOCKING_WAIT_TIME)
+
+ expect(waiter2)
+ .to receive(:wait)
+ .with(described_class::BLOCKING_WAIT_TIME)
+
+ new_waiters = worker.wait_for_jobs({ '123' => 2, '456' => 1 })
+
+ expect(new_waiters).to eq({ '123' => 1 })
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb
index c2898513424..025f0d5c7ea 100644
--- a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb
@@ -15,7 +15,7 @@ RSpec.shared_examples 'a repo type' do
describe '#repository_for' do
it 'finds the repository for the repo type' do
- expect(described_class.repository_for(expected_repository_resolver)).to eq(expected_repository)
+ expect(described_class.repository_for(expected_container)).to eq(expected_repository)
end
it 'returns nil when container is nil' do
diff --git a/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb
index 6b296d0e78a..ac72b31d5a4 100644
--- a/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'search results filtered by archived' do |feature_flag_name|
+RSpec.shared_examples 'search results filtered by archived' do |feature_flag_name, migration_name|
context 'when filter not provided (all behavior)' do
let(:filters) { {} }
@@ -28,16 +28,33 @@ RSpec.shared_examples 'search results filtered by archived' do |feature_flag_nam
end
end
- context "when the #{feature_flag_name} feature flag is disabled" do
- let(:filters) { {} }
+ if feature_flag_name.present?
+ context "when the #{feature_flag_name} feature flag is disabled" do
+ let(:filters) { {} }
+
+ before do
+ stub_feature_flags("#{feature_flag_name}": false)
+ end
- before do
- stub_feature_flags("#{feature_flag_name}": false)
+ it 'returns archived and unarchived results' do
+ expect(results.objects(scope)).to include unarchived_result
+ expect(results.objects(scope)).to include archived_result
+ end
end
+ end
- it 'returns archived and unarchived results' do
- expect(results.objects(scope)).to include unarchived_result
- expect(results.objects(scope)).to include archived_result
+ if migration_name.present?
+ context "when the #{migration_name} is not completed" do
+ let(:filters) { {} }
+
+ before do
+ set_elasticsearch_migration_to(migration_name.to_s, including: false)
+ end
+
+ it 'returns archived and unarchived results' do
+ expect(results.objects(scope)).to include unarchived_result
+ expect(results.objects(scope)).to include archived_result
+ end
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
deleted file mode 100644
index 9dc18555340..00000000000
--- a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'tracked issuable events' do
- before do
- stub_application_setting(usage_ping_enabled: true)
- end
-
- def count_unique(date_from: Date.today.beginning_of_week, date_to: 1.week.from_now)
- Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to)
- end
-
- specify do
- aggregate_failures do
- expect(track_action({ author: user1 }.merge(track_params))).to be_truthy
- expect(track_action({ author: user1 }.merge(track_params))).to be_truthy
- expect(track_action({ author: user2 }.merge(track_params))).to be_truthy
- expect(count_unique).to eq(2)
- end
- end
-
- it 'does not track edit actions if author is not present' do
- expect(track_action({ author: nil }.merge(track_params))).to be_nil
- end
-end
-
-RSpec.shared_examples 'tracked issuable snowplow and service ping events for given event params' do
- it_behaves_like 'tracked issuable events'
-
- it 'emits snowplow event' do
- track_action({ author: user1 }.merge(track_params))
-
- expect_snowplow_event(**{ category: category, action: event_action, user: user1 }.merge(event_params))
- end
-end
-
-RSpec.shared_examples 'tracked issuable internal event for given event params' do
- it_behaves_like 'tracked issuable events'
-
- it_behaves_like 'internal event tracking' do
- subject(:track_event) { track_action({ author: user1 }.merge(track_params)) }
-
- let(:user) { user1 }
- let(:namespace) { project&.namespace }
- end
-end
-
-RSpec.shared_examples 'tracked issuable internal event with project' do
- it_behaves_like 'tracked issuable internal event for given event params' do
- let(:track_params) { original_params || { project: project } }
- end
-end
-
-RSpec.shared_examples 'tracked issuable snowplow and service ping events with project' do
- it_behaves_like 'tracked issuable snowplow and service ping events for given event params' do
- let(:context) do
- Gitlab::Tracking::ServicePingContext
- .new(data_source: :redis_hll, event: event_property)
- .to_h
- end
-
- let(:track_params) { original_params || { project: project } }
- let(:event_params) { { project: project }.merge(label: event_label, property: event_property, namespace: project.namespace, context: [context]) }
- end
-end
-
-RSpec.shared_examples 'tracked issuable snowplow and service ping events with namespace' do
- it_behaves_like 'tracked issuable snowplow and service ping events for given event params' do
- let(:context) do
- Gitlab::Tracking::ServicePingContext
- .new(data_source: :redis_hll, event: event_property)
- .to_h
- end
-
- let(:track_params) { { namespace: namespace } }
- let(:event_params) { track_params.merge(label: event_label, property: event_property, context: [context]) }
- end
-end
-
-RSpec.shared_examples 'does not track with namespace when feature flag is disabled' do |feature_flag|
- context "when feature flag #{feature_flag} is disabled" do
- it 'does not track action' do
- stub_feature_flags(feature_flag => false)
-
- expect(track_action(author: user1, namespace: namespace)).to be_nil
- end
- end
-end
diff --git a/spec/support/shared_examples/lib/menus_shared_examples.rb b/spec/support/shared_examples/lib/menus_shared_examples.rb
index 0aa98517444..575f48c43e0 100644
--- a/spec/support/shared_examples/lib/menus_shared_examples.rb
+++ b/spec/support/shared_examples/lib/menus_shared_examples.rb
@@ -62,6 +62,13 @@ RSpec.shared_examples_for 'not serializable as super_sidebar_menu_args' do
end
end
+RSpec.shared_examples_for 'a panel instantiable by the anonymous user' do
+ it do
+ context.instance_variable_set(:@current_user, nil)
+ expect(described_class.new(context)).to be_a(described_class)
+ end
+end
+
RSpec.shared_examples_for 'a panel with uniquely identifiable menu items' do
let(:menu_items) do
subject.instance_variable_get(:@menus)
diff --git a/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb
index 5e8aebb4f29..6a43b9b4300 100644
--- a/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb
+++ b/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
-RSpec.shared_examples 'User profile menu' do |title:, icon:, active_route:|
+RSpec.shared_examples 'User profile menu' do |
+ icon:, active_route:, avatar_shape: 'rect', expect_avatar: false, entity_id: nil,
+ # A nil title will fall back to user.name.
+ title: nil
+|
let_it_be(:current_user) { build(:user) }
let_it_be(:user) { build(:user) }
@@ -17,13 +21,19 @@ RSpec.shared_examples 'User profile menu' do |title:, icon:, active_route:|
end
it 'renders the correct title' do
- expect(subject.title).to eq title
+ expect(subject.title).to eq(title || user.name)
end
it 'renders the correct icon' do
expect(subject.sprite_icon).to eq icon
end
+ it 'renders the correct avatar' do
+ expect(subject.avatar).to eq(expect_avatar ? user.avatar_url : nil)
+ expect(subject.avatar_shape).to eq(avatar_shape)
+ expect(subject.entity_id).to eq(entity_id)
+ end
+
it 'defines correct active route' do
expect(subject.active_routes[:path]).to be active_route
end
diff --git a/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb b/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
index 5f59d43ad19..179bbc8734d 100644
--- a/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
+++ b/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
@@ -55,6 +55,8 @@ RSpec.shared_examples 'it has loose foreign keys' do
end
RSpec.shared_examples 'cleanup by a loose foreign key' do
+ include LooseForeignKeysHelper
+
let(:foreign_key_definition) do
foreign_keys_for_parent = Gitlab::Database::LooseForeignKeys.definitions_by_table[parent.class.table_name]
foreign_keys_for_parent.find { |definition| definition.from_table == model.class.table_name }
@@ -75,9 +77,7 @@ RSpec.shared_examples 'cleanup by a loose foreign key' do
expect(find_model).to be_present
- LooseForeignKeys::DeletedRecord.using_connection(parent.connection) do
- LooseForeignKeys::ProcessDeletedRecordsService.new(connection: parent.connection).execute
- end
+ process_loose_foreign_key_deletions(record: parent)
if foreign_key_definition.on_delete.eql?(:async_delete)
expect(find_model).not_to be_present
diff --git a/spec/support/shared_examples/mailers/notify_shared_examples.rb b/spec/support/shared_examples/mailers/notify_shared_examples.rb
index cf1ab7697ab..987060d73b9 100644
--- a/spec/support/shared_examples/mailers/notify_shared_examples.rb
+++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb
@@ -54,6 +54,14 @@ RSpec.shared_examples 'an email with X-GitLab headers containing IDs' do
expect(subject.header["X-GitLab-#{model.class.name}-IID"]).to eq nil
end
end
+
+ it 'has X-GitLab-*-State header if model has state defined' do
+ if model.respond_to?(:state)
+ is_expected.to have_header "X-GitLab-#{model.class.name}-State", model.state.to_s
+ else
+ expect(subject.header["X-GitLab-#{model.class.name}-State"]).to eq nil
+ end
+ end
end
RSpec.shared_examples 'an email with X-GitLab headers containing project details' do
diff --git a/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb b/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb
index fdb31fa5d9d..37c338a7712 100644
--- a/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb
+++ b/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb
@@ -32,3 +32,110 @@ RSpec.shared_examples 'migration that adds widget to work items definitions' do
end
end
end
+
+# Shared examples for testing migration that adds a single widget to a work item type
+#
+# It expects the following variables
+# - `target_type_enum_value`: Int, enum value for the target work item type, typically defined in the migration
+# as a constant
+# - `target_type`: Symbol, the target type's name
+# - `additional_types`: Hash (optional), name of work item types and their corresponding enum value that are defined
+# at the time the migration was created but are missing from `base_types`.
+# - `widgets_for_type`: Hash, name of the widgets included in the target type with their corresponding enum value
+RSpec.shared_examples 'migration that adds a widget to a work item type' do
+ include MigrationHelpers::WorkItemTypesHelper
+
+ let(:work_item_types) { table(:work_item_types) }
+ let(:work_item_widget_definitions) { table(:work_item_widget_definitions) }
+ let(:additional_base_types) { try(:additional_types) || {} }
+ let(:base_types) do
+ {
+ issue: 0,
+ incident: 1,
+ test_case: 2,
+ requirement: 3,
+ task: 4,
+ objective: 5,
+ key_result: 6,
+ epic: 7
+ }.merge!(additional_base_types)
+ end
+
+ after(:all) do
+ # Make sure base types are recreated after running the migration
+ # because migration specs are not run in a transaction
+ reset_work_item_types
+ end
+
+ before do
+ # Database needs to be in a similar state as when the migration was created
+ reset_db_state_prior_to_migration
+ end
+
+ describe '#up' do
+ it "adds widget to work item type", :aggregate_failures do
+ expect do
+ migrate!
+ end.to change { work_item_widget_definitions.count }.by(1)
+
+ work_item_type = work_item_types.find_by(namespace_id: nil, base_type: target_type_enum_value)
+ created_widget = work_item_widget_definitions.last
+
+ expect(created_widget).to have_attributes(
+ widget_type: described_class::WIDGET_ENUM_VALUE,
+ name: described_class::WIDGET_NAME,
+ work_item_type_id: work_item_type.id
+ )
+ end
+
+ context 'when type does not exist' do
+ it 'skips creating the new widget definition' do
+ work_item_types.where(namespace_id: nil, base_type: base_types[target_type]).delete_all
+
+ expect do
+ migrate!
+ end.to not_change(work_item_widget_definitions, :count)
+ end
+ end
+ end
+
+ describe '#down' do
+ it "removes widget from work item type" do
+ migrate!
+
+ expect { schema_migrate_down! }.to change { work_item_widget_definitions.count }.by(-1)
+ end
+ end
+
+ def reset_db_state_prior_to_migration
+ work_item_types.delete_all
+
+ base_types.each do |type_sym, type_enum|
+ create_work_item_type!(type_sym.to_s.titleize, type_enum)
+ end
+
+ target_type_record = work_item_types.find_by_name(target_type.to_s.titleize)
+
+ widgets = widgets_for_type.map do |widget_name_value, widget_enum_value|
+ {
+ work_item_type_id: target_type_record.id,
+ name: widget_name_value,
+ widget_type: widget_enum_value
+ }
+ end
+
+ # Creating all widgets for the type so the state in the DB is as close as possible to the actual state
+ work_item_widget_definitions.upsert_all(
+ widgets,
+ unique_by: :index_work_item_widget_definitions_on_default_witype_and_name
+ )
+ end
+
+ def create_work_item_type!(type_name, type_enum_value)
+ work_item_types.create!(
+ name: type_name,
+ namespace_id: nil,
+ base_type: type_enum_value
+ )
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb b/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb
index efd27a051fe..eb37fe66c11 100644
--- a/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb
@@ -78,5 +78,16 @@ RSpec.shared_examples 'includes LinkableItem concern' do
expect(described_class.for_items(item, item2)).to contain_exactly(target_link)
end
end
+
+ describe '.for_source_and_target' do
+ let_it_be(:item3) { create(:work_item, project: project) }
+ let_it_be(:link1) { create(link_factory, source: item, target: item1) }
+ let_it_be(:link2) { create(link_factory, source: item, target: item2) }
+ let_it_be(:link3) { create(link_factory, source: item, target: item3) }
+
+ it 'includes links for provided source and target' do
+ expect(described_class.for_source_and_target(item, [item1, item2])).to contain_exactly(link1, link2)
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/models/group_shared_examples.rb b/spec/support/shared_examples/models/group_shared_examples.rb
index 9f3359ba4ab..6397e7a87d7 100644
--- a/spec/support/shared_examples/models/group_shared_examples.rb
+++ b/spec/support/shared_examples/models/group_shared_examples.rb
@@ -3,7 +3,6 @@
RSpec.shared_examples 'checks self and root ancestor feature flag' do
let_it_be(:root_group) { create(:group) }
let_it_be(:group) { create(:group, parent: root_group) }
- let_it_be(:project) { create(:project, group: group) }
subject { group.public_send(feature_flag_method) }
@@ -41,3 +40,47 @@ RSpec.shared_examples 'checks self and root ancestor feature flag' do
it { is_expected.to be_truthy }
end
end
+
+RSpec.shared_examples 'checks self (project) and root ancestor feature flag' do
+ let_it_be(:root_group) { create(:group) }
+ let_it_be(:group) { create(:group, parent: root_group) }
+ let_it_be(:project) { create(:project, group: group) }
+
+ subject { project.public_send(feature_flag_method) }
+
+ context 'when FF is enabled for the root group' do
+ before do
+ stub_feature_flags(feature_flag => root_group)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when FF is enabled for the group' do
+ before do
+ stub_feature_flags(feature_flag => group)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when FF is enabled for the project' do
+ before do
+ stub_feature_flags(feature_flag => project)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when FF is disabled globally' do
+ before do
+ stub_feature_flags(feature_flag => false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when FF is enabled globally' do
+ it { is_expected.to be_truthy }
+ end
+end
diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb
index 329cb812a08..5c783b5cfa7 100644
--- a/spec/support/shared_examples/models/members_notifications_shared_example.rb
+++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.shared_examples 'members notifications' do |entity_type|
+ let_it_be(:user) { create(:user) }
+
let(:notification_service) { double('NotificationService').as_null_object }
before do
@@ -8,7 +10,7 @@ RSpec.shared_examples 'members notifications' do |entity_type|
end
describe "#after_create" do
- let(:member) { build(:"#{entity_type}_member", "#{entity_type}": create(entity_type.to_s)) }
+ let(:member) { build(:"#{entity_type}_member", "#{entity_type}": create(entity_type.to_s), user: user) }
it "sends email to user" do
expect(notification_service).to receive(:"new_#{entity_type}_member").with(member)
@@ -35,7 +37,9 @@ RSpec.shared_examples 'members notifications' do |entity_type|
describe '#after_commit' do
context 'on creation of a member requesting access' do
- let(:member) { build(:"#{entity_type}_member", :access_request, "#{entity_type}": create(entity_type.to_s)) }
+ let(:member) do
+ build(:"#{entity_type}_member", :access_request, "#{entity_type}": create(entity_type.to_s), user: user)
+ end
it "calls NotificationService.new_access_request" do
expect(notification_service).to receive(:new_access_request).with(member)
diff --git a/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb b/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb
new file mode 100644
index 00000000000..0b3e8516d25
--- /dev/null
+++ b/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'namespace visits model' do
+ it { is_expected.to validate_presence_of(:entity_id) }
+ it { is_expected.to validate_presence_of(:user_id) }
+ it { is_expected.to validate_presence_of(:visited_at) }
+
+ describe '#visited_around?' do
+ context 'when the checked time matches a recent visit' do
+ [-15.minutes, 15.minutes].each do |time_diff|
+ it 'returns true' do
+ expect(described_class.visited_around?(entity_id: entity.id, user_id: user.id,
+ time: base_time + time_diff)).to be(true)
+ end
+ end
+ end
+
+ context 'when the checked time does not match a recent visit' do
+ [-16.minutes, 16.minutes].each do |time_diff|
+ it 'returns false' do
+ expect(described_class.visited_around?(entity_id: entity.id, user_id: user.id,
+ time: base_time + time_diff)).to be(false)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/redis/redis_shared_examples.rb b/spec/support/shared_examples/redis/redis_shared_examples.rb
index 9224e01b1fe..23ec4a632b7 100644
--- a/spec/support/shared_examples/redis/redis_shared_examples.rb
+++ b/spec/support/shared_examples/redis/redis_shared_examples.rb
@@ -365,6 +365,90 @@ RSpec.shared_examples "redis_shared_examples" do
end
end
+ describe "#parse_client_tls_options" do
+ let(:dummy_certificate) { OpenSSL::X509::Certificate.new }
+ let(:dummy_key) { OpenSSL::PKey::RSA.new }
+ let(:resque_yaml_config_without_tls) { { url: 'redis://localhost:6379' } }
+ let(:resque_yaml_config_with_tls) do
+ {
+ url: 'rediss://localhost:6380',
+ ssl_params: {
+ cert_file: '/tmp/client.crt',
+ key_file: '/tmp/client.key'
+ }
+ }
+ end
+
+ let(:parsed_config_with_tls) do
+ {
+ url: 'rediss://localhost:6380',
+ ssl_params: {
+ cert: dummy_certificate,
+ key: dummy_key
+ }
+ }
+ end
+
+ before do
+ allow(::File).to receive(:exist?).and_call_original
+ allow(::File).to receive(:read).and_call_original
+ end
+
+ context 'when configuration does not have TLS related options' do
+ it 'returns the coniguration as-is' do
+ expect(subject.send(:parse_client_tls_options,
+ resque_yaml_config_without_tls)).to eq(resque_yaml_config_without_tls)
+ end
+ end
+
+ context 'when specified certificate file does not exist' do
+ before do
+ allow(::File).to receive(:exist?).with("/tmp/client.crt").and_return(false)
+ allow(::File).to receive(:exist?).with("/tmp/client.key").and_return(true)
+ end
+
+ it 'raises error about missing certificate file' do
+ expect do
+ subject.send(:parse_client_tls_options,
+ resque_yaml_config_with_tls)
+ end.to raise_error(Gitlab::Redis::Wrapper::InvalidPathError,
+ "Certificate file /tmp/client.crt specified in in `resque.yml` does not exist.")
+ end
+ end
+
+ context 'when specified key file does not exist' do
+ before do
+ allow(::File).to receive(:exist?).with("/tmp/client.crt").and_return(true)
+ allow(::File).to receive(:read).with("/tmp/client.crt").and_return("DUMMY_CERTIFICATE")
+ allow(OpenSSL::X509::Certificate).to receive(:new).with("DUMMY_CERTIFICATE").and_return(dummy_certificate)
+ allow(::File).to receive(:exist?).with("/tmp/client.key").and_return(false)
+ end
+
+ it 'raises error about missing key file' do
+ expect do
+ subject.send(:parse_client_tls_options,
+ resque_yaml_config_with_tls)
+ end.to raise_error(Gitlab::Redis::Wrapper::InvalidPathError,
+ "Key file /tmp/client.key specified in in `resque.yml` does not exist.")
+ end
+ end
+
+ context 'when configuration valid TLS related options' do
+ before do
+ allow(::File).to receive(:exist?).with("/tmp/client.crt").and_return(true)
+ allow(::File).to receive(:exist?).with("/tmp/client.key").and_return(true)
+ allow(::File).to receive(:read).with("/tmp/client.crt").and_return("DUMMY_CERTIFICATE")
+ allow(::File).to receive(:read).with("/tmp/client.key").and_return("DUMMY_KEY")
+ allow(OpenSSL::X509::Certificate).to receive(:new).with("DUMMY_CERTIFICATE").and_return(dummy_certificate)
+ allow(OpenSSL::PKey).to receive(:read).with("DUMMY_KEY").and_return(dummy_key)
+ end
+
+ it "converts cert_file and key_file appropriately" do
+ expect(subject.send(:parse_client_tls_options, resque_yaml_config_with_tls)).to eq(parsed_config_with_tls)
+ end
+ end
+ end
+
describe '#fetch_config' do
before do
FileUtils.mkdir_p(File.join(rails_root, 'config'))
diff --git a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
index 74dbec063e0..625f16824b4 100644
--- a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
+++ b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
@@ -72,7 +72,7 @@ RSpec.shared_examples 'GET access tokens are paginated and ordered' do
first_token = assigns(:active_access_tokens).first.as_json
expect(first_token['name']).to eq("Token1")
- expect(first_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+ expect(first_token['expires_at']).to eq(expires_1_day_from_now.iso8601)
end
it "orders tokens on id in case token has same expires_at" do
@@ -82,11 +82,11 @@ RSpec.shared_examples 'GET access tokens are paginated and ordered' do
first_token = assigns(:active_access_tokens).first.as_json
expect(first_token['name']).to eq("Token3")
- expect(first_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+ expect(first_token['expires_at']).to eq(expires_1_day_from_now.iso8601)
second_token = assigns(:active_access_tokens).second.as_json
expect(second_token['name']).to eq("Token1")
- expect(second_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+ expect(second_token['expires_at']).to eq(expires_1_day_from_now.iso8601)
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
index 6eceb7c350d..04f340fef37 100644
--- a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
@@ -518,6 +518,39 @@ RSpec.shared_examples 'graphql issue list request spec' do
end
end
+ context 'when fetching external participants' do
+ before_all do
+ issue_a.update!(external_author: 'user@example.com')
+ end
+
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ id
+ externalAuthor
+ }
+ QUERY
+ end
+
+ it 'returns the email address' do
+ post_query
+
+ emails = issues_data.pluck('externalAuthor').compact
+ expect(emails).to contain_exactly('user@example.com')
+ end
+
+ context 'when user does not have access to view emails' do
+ let(:current_user) { external_user }
+
+ it 'obfuscates the email address' do
+ post_query
+
+ emails = issues_data.pluck('externalAuthor').compact
+ expect(emails).to contain_exactly("us*****@e*****.c**")
+ end
+ end
+ end
+
context 'when fetching escalation status' do
let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue_a) }
let_it_be(:incident_type) { WorkItems::Type.default_by_type(:incident) }
diff --git a/spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb
new file mode 100644
index 00000000000..a9c422c8f2d
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'graphql work item list request spec' do
+ let(:work_item_ids) { graphql_dig_at(work_item_data, :id) }
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_query
+ end
+ end
+
+ describe 'filters' do
+ before do
+ post_query
+ end
+
+ context 'when filtering by author username' do
+ let(:author) { create(:author) }
+ let(:authored_work_item) { create(:work_item, author: author, **container_build_params) }
+
+ let(:item_filter_params) { { author_username: authored_work_item.author.username } }
+
+ it 'returns correct results' do
+ expect(work_item_ids).to contain_exactly(authored_work_item.to_global_id.to_s)
+ end
+ end
+
+ context 'when filtering by state' do
+ let_it_be(:opened_work_item) { create(:work_item, :opened, **container_build_params) }
+ let_it_be(:closed_work_item) { create(:work_item, :closed, **container_build_params) }
+
+ context 'when filtering by state opened' do
+ let(:item_filter_params) { { state: :opened } }
+
+ it 'filters by state' do
+ expect(work_item_ids).to include(opened_work_item.to_global_id.to_s)
+ expect(work_item_ids).not_to include(closed_work_item.to_global_id.to_s)
+ end
+ end
+
+ context 'when filtering by state closed' do
+ let(:item_filter_params) { { state: :closed } }
+
+ it 'filters by state' do
+ expect(work_item_ids).not_to include(opened_work_item.to_global_id.to_s)
+ expect(work_item_ids).to include(closed_work_item.to_global_id.to_s)
+ end
+ end
+ end
+
+ context 'when filtering by type' do
+ let_it_be(:issue_work_item) { create(:work_item, :issue, **container_build_params) }
+ let_it_be(:task_work_item) { create(:work_item, :task, **container_build_params) }
+
+ context 'when filtering by issue type' do
+ let(:item_filter_params) { { types: [:ISSUE] } }
+
+ it 'filters by type' do
+ expect(work_item_ids).to include(issue_work_item.to_global_id.to_s)
+ expect(work_item_ids).not_to include(task_work_item.to_global_id.to_s)
+ end
+ end
+
+ context 'when filtering by task type' do
+ let(:item_filter_params) { { types: [:TASK] } }
+
+ it 'filters by type' do
+ expect(work_item_ids).not_to include(issue_work_item.to_global_id.to_s)
+ expect(work_item_ids).to include(task_work_item.to_global_id.to_s)
+ end
+ end
+ end
+
+ context 'when filtering by iid' do
+ let_it_be(:work_item_by_iid) { create(:work_item, **container_build_params) }
+
+ context 'when using the iid filter' do
+ let(:item_filter_params) { { iid: work_item_by_iid.iid.to_s } }
+
+ it 'returns only items by the given iid' do
+ expect(work_item_ids).to contain_exactly(work_item_by_iid.to_global_id.to_s)
+ end
+ end
+
+ context 'when using the iids filter' do
+ let(:item_filter_params) { { iids: [work_item_by_iid.iid.to_s] } }
+
+ it 'returns only items by the given iid' do
+ expect(work_item_ids).to contain_exactly(work_item_by_iid.to_global_id.to_s)
+ end
+ end
+ end
+ end
+
+ def work_item_data
+ graphql_data.dig(*work_item_node_path)
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb
index f2c38d70508..00e50b07909 100644
--- a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb
@@ -8,12 +8,25 @@ RSpec.shared_examples 'MLflow|Not Found - Resource Does Not Exist' do
end
end
-RSpec.shared_examples 'MLflow|Requires api scope' do
+RSpec.shared_examples 'MLflow|Requires api scope and write permission' do
context 'when user has access but token has wrong scope' do
let(:access_token) { tokens[:read] }
it { is_expected.to have_gitlab_http_status(:forbidden) }
end
+
+ context 'when user has access but is not allowed to write' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(current_user, :write_model_experiments, project)
+ .and_return(false)
+ end
+
+ it "is Unauthorized" do
+ is_expected.to have_gitlab_http_status(:unauthorized)
+ end
+ end
end
RSpec.shared_examples 'MLflow|Requires read_api scope' do
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index 6b6bf375827..5f043cdd996 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -280,7 +280,10 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
end
end
- status = :not_found if scope == :group && params[:package_name_type] == :non_existing && !params[:request_forward]
+ if (scope == :group && params[:package_name_type] == :non_existing) &&
+ (!params[:request_forward] || (!params[:auth] && params[:request_forward] && params[:visibility] != :public))
+ status = :not_found
+ end
# Check the error message for :not_found
example_name = 'returning response status with error' if status == :not_found
@@ -873,3 +876,67 @@ RSpec.shared_examples 'rejects invalid package names' do
expect(Gitlab::Json.parse(response.body)).to eq({ 'error' => 'package_name should be a valid file path' })
end
end
+
+RSpec.shared_examples 'handling get metadata requests for packages in multiple projects' do
+ let_it_be(:project2) { create(:project, namespace: namespace) }
+ let_it_be(:package2) do
+ create(:npm_package,
+ project: project2,
+ name: "@#{group.path}/scoped_package",
+ version: '1.2.0')
+ end
+
+ let(:headers) { build_token_auth_header(personal_access_token.token) }
+
+ subject { get(url, headers: headers) }
+
+ before_all do
+ project.update!(visibility: 'private')
+
+ group.add_guest(user)
+ project.add_reporter(user)
+ project2.add_reporter(user)
+ end
+
+ it 'includes all matching package versions in the response' do
+ subject
+
+ expect(json_response['versions'].keys).to match_array([package.version, package2.version])
+ end
+
+ context 'with the feature flag disabled' do
+ before do
+ stub_feature_flags(npm_allow_packages_in_multiple_projects: false)
+ end
+
+ it 'returns matching package versions from only one project' do
+ subject
+
+ expect(json_response['versions'].keys).to match_array([package2.version])
+ end
+ end
+
+ context 'with limited access to the project with the last package version' do
+ before_all do
+ project2.add_guest(user)
+ end
+
+ it 'includes matching package versions from authorized projects in the response' do
+ subject
+
+ expect(json_response['versions'].keys).to contain_exactly(package.version)
+ end
+ end
+
+ context 'with limited access to the project with the first package version' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'includes matching package versions from authorized projects in the response' do
+ subject
+
+ expect(json_response['versions'].keys).to contain_exactly(package2.version)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
index 2e66bae26ba..1be99040ae5 100644
--- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -373,14 +373,6 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st
end
it_behaves_like 'bumping the package last downloaded at field'
-
- context 'when nuget_normalized_version feature flag is disabled' do
- before do
- stub_feature_flags(nuget_normalized_version: false)
- end
-
- it_behaves_like 'returning response status', :not_found
- end
end
end
end
@@ -710,4 +702,38 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
it_behaves_like 'returning response status', :forbidden
end
+
+ context 'when package duplicates are not allowed' do
+ let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_headers) }
+ let_it_be(:existing_package) { create(:nuget_package, project: project) }
+ let_it_be(:metadata) { { package_name: existing_package.name, package_version: existing_package.version } }
+ let_it_be(:package_settings) do
+ create(:namespace_package_setting, :group, namespace: project.namespace, nuget_duplicates_allowed: false)
+ end
+
+ before do
+ allow_next_instance_of(::Packages::Nuget::MetadataExtractionService) do |instance|
+ allow(instance).to receive(:execute).and_return(ServiceResponse.success(payload: metadata))
+ end
+ end
+
+ it_behaves_like 'returning response status', :conflict unless symbol_package
+ it_behaves_like 'returning response status', :created if symbol_package
+
+ context 'when exception_regex is set' do
+ before do
+ package_settings.update_column(:nuget_duplicate_exception_regex, ".*#{existing_package.name.last(3)}.*")
+ end
+
+ it_behaves_like 'returning response status', :created
+ end
+
+ context 'when nuget_duplicates_option feature flag is disabled' do
+ before do
+ stub_feature_flags(nuget_duplicates_option: false)
+ end
+
+ it_behaves_like 'returning response status', :created
+ end
+ end
end
diff --git a/spec/support/shared_examples/requests/api_keyset_pagination_shared_examples.rb b/spec/support/shared_examples/requests/api_keyset_pagination_shared_examples.rb
new file mode 100644
index 00000000000..85db36013dd
--- /dev/null
+++ b/spec/support/shared_examples/requests/api_keyset_pagination_shared_examples.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'an endpoint with keyset pagination' do |invalid_order: 'name', invalid_sort: 'asc'|
+ include KeysetPaginationHelpers
+
+ let(:keyset_params) { { pagination: 'keyset', per_page: 1 } }
+ let(:additional_params) { {} }
+
+ subject do
+ get api_call, params: keyset_params.merge(additional_params)
+ response
+ end
+
+ context 'on making requests with supported ordering structure' do
+ it 'includes keyset url params in the url response' do
+ is_expected.to have_gitlab_http_status(:ok)
+ is_expected.to include_keyset_url_params
+ end
+
+ it 'does not include pagination headers' do
+ is_expected.to have_gitlab_http_status(:ok)
+ is_expected.not_to include_pagination_headers
+ end
+
+ it 'paginates the records correctly', :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:ok)
+ records = json_response
+ expect(records.size).to eq(1)
+ expect(records.first['id']).to eq(first_record.id)
+
+ get api_call, params: pagination_params_from_next_url(response)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ records = Gitlab::Json.parse(response.body)
+ expect(records.size).to eq(1)
+ expect(records.first['id']).to eq(second_record.id)
+ end
+ end
+
+ context 'on making requests with unsupported ordering structure' do
+ let(:additional_params) { { order_by: invalid_order, sort: invalid_sort } }
+
+ if invalid_order
+ it 'returns error', :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:method_not_allowed)
+ expect(json_response['error']).to eq('Keyset pagination is not yet available for this type of request')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
index dafa324b3c6..48d3e438322 100644
--- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -148,6 +148,12 @@ RSpec.shared_examples 'rate-limited token requests' do
expect(response).not_to have_gitlab_http_status(:too_many_requests)
end
+ matched = throttle_types[throttle_setting_prefix]
+
+ if request_method == 'GET' && throttle_setting_prefix == 'throttle_protected_paths'
+ matched = 'throttle_authenticated_get_protected_paths_api'
+ end
+
arguments = a_hash_including({
message: 'Rack_Attack',
status: 429,
@@ -155,7 +161,7 @@ RSpec.shared_examples 'rate-limited token requests' do
remote_ip: '127.0.0.1',
request_method: request_method,
path: request_args.first,
- matched: throttle_types[throttle_setting_prefix]
+ matched: matched
}.merge(log_data))
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
@@ -166,7 +172,14 @@ RSpec.shared_examples 'rate-limited token requests' do
end
it_behaves_like 'tracking when dry-run mode is set' do
- let(:throttle_name) { throttle_types[throttle_setting_prefix] }
+ let(:throttle_name) do
+ name = throttle_types[throttle_setting_prefix]
+ if request_method == 'GET' && throttle_setting_prefix == 'throttle_protected_paths'
+ name = 'throttle_authenticated_get_protected_paths_api'
+ end
+
+ name
+ end
def do_request
make_request(request_args)
@@ -315,7 +328,13 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
expect(response).not_to have_gitlab_http_status(:too_many_requests)
end
- arguments = a_hash_including({
+ matched = throttle_types[throttle_setting_prefix]
+
+ if request_method == 'GET' && throttle_setting_prefix == 'throttle_protected_paths'
+ matched = 'throttle_authenticated_get_protected_paths_web'
+ end
+
+ arguments = a_hash_including(
message: 'Rack_Attack',
status: 429,
env: :throttle,
@@ -324,15 +343,22 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
path: url_that_requires_authentication,
user_id: user.id,
'meta.user' => user.username,
- matched: throttle_types[throttle_setting_prefix]
- })
+ matched: matched
+ )
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
expect { request_authenticated_web_url }.not_to exceed_query_limit(control_count)
end
it_behaves_like 'tracking when dry-run mode is set' do
- let(:throttle_name) { throttle_types[throttle_setting_prefix] }
+ let(:throttle_name) do
+ name = throttle_types[throttle_setting_prefix]
+ if request_method == 'GET' && throttle_setting_prefix == 'throttle_protected_paths'
+ name = 'throttle_authenticated_get_protected_paths_web'
+ end
+
+ name
+ end
def do_request
request_authenticated_web_url
diff --git a/spec/support/shared_examples/services/incident_shared_examples.rb b/spec/support/shared_examples/services/incident_shared_examples.rb
index db2b448f567..94467ad53fa 100644
--- a/spec/support/shared_examples/services/incident_shared_examples.rb
+++ b/spec/support/shared_examples/services/incident_shared_examples.rb
@@ -40,7 +40,7 @@ end
RSpec.shared_examples 'incident management label service' do
let_it_be(:project) { create(:project, :private) }
- let_it_be(:user) { User.alert_bot }
+ let_it_be(:user) { Users::Internal.alert_bot }
let(:service) { described_class.new(project, user) }
subject(:execute) { service.execute }
diff --git a/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb
index 3f95d6060ea..9624f7a4450 100644
--- a/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb
@@ -145,6 +145,77 @@ RSpec.shared_examples 'updating issuable labels' do
end
end
+RSpec.shared_examples 'updating merged MR with locked labels' do
+ context 'when add_label_ids and label_ids are passed' do
+ let(:params) { { label_ids: [label_a.id], add_label_ids: [label_c.id] } }
+
+ it 'replaces unlocked labels with the ones in label_ids and adds those in add_label_ids' do
+ issuable.update!(labels: [label_b, label_unlocked])
+ update_issuable(params)
+
+ expect(issuable.label_ids).to contain_exactly(label_a.id, label_b.id, label_c.id)
+ end
+ end
+
+ context 'when remove_label_ids and label_ids are passed' do
+ let(:params) { { label_ids: [label_a.id, label_b.id, label_c.id], remove_label_ids: [label_a.id] } }
+
+ it 'replaces unlocked labels with the ones in label_ids and does not remove locked label in remove_label_ids' do
+ issuable.update!(labels: [label_a, label_c, label_unlocked])
+ update_issuable(params)
+
+ expect(issuable.label_ids).to contain_exactly(label_a.id, label_b.id, label_c.id)
+ end
+ end
+
+ context 'when add_label_ids and remove_label_ids are passed' do
+ let(:params) { { add_label_ids: [label_c.id], remove_label_ids: [label_a.id, label_unlocked.id] } }
+
+ before do
+ issuable.update!(labels: [label_a, label_unlocked])
+ update_issuable(params)
+ end
+
+ it 'adds the passed labels' do
+ expect(issuable.label_ids).to include(label_c.id)
+ end
+
+ it 'removes the passed unlocked labels' do
+ expect(issuable.label_ids).to include(label_a.id)
+ expect(issuable.label_ids).not_to include(label_unlocked.id)
+ end
+ end
+
+ context 'when same id is passed as add_label_ids and remove_label_ids' do
+ let(:params) { { add_label_ids: [label_a.id], remove_label_ids: [label_a.id] } }
+
+ context 'for a label assigned to an issue' do
+ it 'does not remove the label' do
+ issuable.update!(labels: [label_a])
+ update_issuable(params)
+
+ expect(issuable.label_ids).to contain_exactly(label_a.id)
+ end
+ end
+
+ context 'for a label not assigned to an issue' do
+ it 'does not add the label' do
+ expect(issuable.label_ids).to be_empty
+ end
+ end
+ end
+
+ context 'when duplicate label titles are given' do
+ let(:params) { { labels: [label_c.title, label_c.title] } }
+
+ it 'assigns the label once' do
+ update_issuable(params)
+
+ expect(issuable.labels).to contain_exactly(label_c)
+ end
+ end
+end
+
RSpec.shared_examples 'keeps issuable labels sorted after update' do
before do
update_issuable(label_ids: [label_b.id])
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
index 1532e870dcc..b955b71a6bb 100644
--- 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
@@ -1,13 +1,7 @@
# frozen_string_literal: true
-RSpec.shared_examples 'a destroyable issuable link' do |required_role: :reporter|
+RSpec.shared_examples 'a destroyable issuable link' do
context 'when successfully removes an issuable link' do
- before do
- [issuable_link.target, issuable_link.source].each do |issuable|
- issuable.resource_parent.try(:"add_#{required_role}", user)
- end
- end
-
it 'removes related issue' do
expect { subject }.to change { issuable_link.class.count }.by(-1)
end
@@ -28,6 +22,9 @@ RSpec.shared_examples 'a destroyable issuable link' do |required_role: :reporter
end
context 'when failing to remove an issuable link' do
+ let_it_be(:non_member) { create(:user) }
+ let(:user) { non_member }
+
it 'does not remove relation' do
expect { subject }.not_to change { issuable_link.class.count }.from(1)
end
diff --git a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
deleted file mode 100644
index 9b2e038a331..00000000000
--- a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'misconfigured dashboard service response' do |status_code, message = nil|
- it 'returns an appropriate message and status code', :aggregate_failures do
- result = service_call
-
- expect(result.keys).to contain_exactly(:message, :http_status, :status)
- expect(result[:status]).to eq(:error)
- expect(result[:http_status]).to eq(status_code)
- expect(result[:message]).to eq(message) if message
- end
-end
-
-RSpec.shared_examples 'valid dashboard service response for schema' do
- it 'returns a json representation of the dashboard' do
- result = service_call
-
- expect(result.keys).to contain_exactly(:dashboard, :status)
- expect(result[:status]).to eq(:success)
-
- schema_path = Rails.root.join('spec/fixtures', dashboard_schema)
- validator = JSONSchemer.schema(schema_path)
- expect(validator.valid?(result[:dashboard].with_indifferent_access)).to be true
- end
-end
-
-RSpec.shared_examples 'valid dashboard service response' do
- let(:dashboard_schema) { 'lib/gitlab/metrics/dashboard/schemas/dashboard.json' }
-
- it_behaves_like 'valid dashboard service response for schema'
-end
-
-RSpec.shared_examples 'caches the unprocessed dashboard for subsequent calls' do
- specify do
- expect_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
- expect(loader).to receive(:load_raw!).once.and_call_original
- end
-
- described_class.new(*service_params).get_dashboard
- described_class.new(*service_params).get_dashboard
- end
-end
-
-# This spec is applicable for predefined/out-of-the-box dashboard services.
-RSpec.shared_examples 'refreshes cache when dashboard_version is changed' do
- specify do
- allow_next_instance_of(described_class) do |service|
- allow(service).to receive(:dashboard_version).and_return('1', '2')
- end
-
- expect_file_read(Rails.root.join(described_class::DASHBOARD_PATH)).twice.and_call_original
-
- service = described_class.new(*service_params)
-
- service.get_dashboard
- service.get_dashboard
- end
-end
-
-# This spec is applicable for predefined/out-of-the-box dashboard services.
-# This shared_example requires the following variables to be defined:
-# dashboard_path: Relative path to the dashboard, ex: 'config/prometheus/common_metrics.yml'
-# dashboard_version: The version string used in the cache_key.
-RSpec.shared_examples 'dashboard_version contains SHA256 hash of dashboard file content' do
- specify do
- dashboard = File.read(Rails.root.join(dashboard_path))
- expect(dashboard_version).to eq(Digest::SHA256.hexdigest(dashboard))
- end
-end
-
-RSpec.shared_examples 'valid embedded dashboard service response' do
- let(:dashboard_schema) { 'lib/gitlab/metrics/dashboard/schemas/embedded_dashboard.json' }
-
- it_behaves_like 'valid dashboard service response for schema'
-end
-
-RSpec.shared_examples 'raises error for users with insufficient permissions' do
- context 'when the user does not have sufficient access' do
- let(:user) { build(:user) }
-
- it_behaves_like 'misconfigured dashboard service response', :unauthorized
- end
-
- context 'when the user is anonymous' do
- let(:user) { nil }
-
- it_behaves_like 'misconfigured dashboard service response', :unauthorized
- end
-end
-
-RSpec.shared_examples 'valid dashboard cloning process' do |dashboard_template, sequence|
- context "dashboard template: #{dashboard_template}" do
- let(:dashboard) { dashboard_template }
- let(:dashboard_attrs) do
- {
- commit_message: commit_message,
- branch_name: branch,
- start_branch: project.default_branch,
- encoding: 'text',
- file_path: ".gitlab/dashboards/#{file_name}",
- file_content: file_content_hash.to_yaml
- }
- end
-
- it 'delegates commit creation to Files::CreateService', :aggregate_failures do
- service_instance = instance_double(::Files::CreateService)
- allow(::Gitlab::Metrics::Dashboard::Processor).to receive(:new).and_return(double(process: file_content_hash))
- expect(::Files::CreateService).to receive(:new).with(project, user, dashboard_attrs).and_return(service_instance)
- expect(service_instance).to receive(:execute).and_return(status: :success)
-
- service_call
- end
-
- context 'user has defined custom metrics' do
- it 'uses external service to includes them into new file content', :aggregate_failures do
- service_instance = double(::Gitlab::Metrics::Dashboard::Processor)
- expect(::Gitlab::Metrics::Dashboard::Processor).to receive(:new).with(project, file_content_hash, sequence, {}).and_return(service_instance)
- expect(service_instance).to receive(:process).and_return(file_content_hash)
- expect(::Files::CreateService).to receive(:new).with(project, user, dashboard_attrs).and_return(double(execute: { status: :success }))
-
- service_call
- end
- end
- end
-end
-
-RSpec.shared_examples 'misconfigured dashboard service response with stepable' do |status_code, message = nil|
- it 'returns an appropriate message and status code', :aggregate_failures do
- result = service_call
-
- expect(result.keys).to contain_exactly(:message, :http_status, :status, :last_step)
- expect(result[:status]).to eq(:error)
- expect(result[:http_status]).to eq(status_code)
- expect(result[:message]).to eq(message) if message
- end
-end
-
-RSpec.shared_examples 'updates gitlab_metrics_dashboard_processing_time_ms metric' do
- specify :prometheus do
- service_call
- metric = subject.send(:processing_time_metric)
- labels = subject.send(:processing_time_metric_labels)
-
- expect(metric.get(labels)).to be > 0
- end
-end
-
-RSpec.shared_examples '#raw_dashboard raises error if dashboard loading fails' do
- context 'when yaml is too large' do
- before do
- allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
- allow(loader).to receive(:load_raw!)
- .and_raise(Gitlab::Config::Loader::Yaml::DataTooLargeError, 'The parsed YAML is too big')
- end
- end
-
- it 'raises error' do
- expect { subject.raw_dashboard }.to raise_error(
- Gitlab::Metrics::Dashboard::Errors::LayoutError,
- 'The parsed YAML is too big'
- )
- end
- end
-
- context 'when yaml loader returns error' do
- before do
- allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
- allow(loader).to receive(:load_raw!)
- .and_raise(Gitlab::Config::Loader::FormatError, 'Invalid configuration format')
- end
- end
-
- it 'raises error' do
- expect { subject.raw_dashboard }.to raise_error(
- Gitlab::Metrics::Dashboard::Errors::LayoutError,
- 'Invalid yaml'
- )
- end
- end
-
- context 'when yaml is not a hash' do
- before do
- allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
- allow(loader).to receive(:load_raw!)
- .and_raise(Gitlab::Config::Loader::Yaml::NotHashError, 'Invalid configuration format')
- end
- end
-
- it 'returns nil' do
- expect(subject.raw_dashboard).to eq({})
- end
- end
-end
diff --git a/spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb
index e77d73d1c72..621fb99afe5 100644
--- a/spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -38,7 +38,7 @@ RSpec.shared_examples "migrating a deleted user's associated records to the ghos
migrated_record = record_class.find_by_id(record.id)
migrated_fields.each do |field|
- expect(migrated_record.public_send(field)).to eq(User.ghost)
+ expect(migrated_record.public_send(field)).to eq(Users::Internal.ghost)
end
end
@@ -47,7 +47,7 @@ RSpec.shared_examples "migrating a deleted user's associated records to the ghos
migrated_record = record_class.find_by_id(record.id)
- check_user = always_ghost ? User.ghost : user
+ check_user = always_ghost ? Users::Internal.ghost : user
migrated_fields.each do |field|
expect(migrated_record.public_send(field)).to eq(check_user)
diff --git a/spec/support/shared_examples/services/pages_size_limit_shared_examples.rb b/spec/support/shared_examples/services/pages_size_limit_shared_examples.rb
deleted file mode 100644
index d9e906ebb75..00000000000
--- a/spec/support/shared_examples/services/pages_size_limit_shared_examples.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'pages size limit is' do |size_limit|
- context "when size is below the limit" do
- before do
- allow(metadata).to receive(:total_size).and_return(size_limit - 1.megabyte)
- allow(metadata).to receive(:entries).and_return([])
- end
-
- it 'updates pages correctly' do
- subject.execute
-
- expect(deploy_status.description).not_to be_present
- expect(project.pages_metadatum).to be_deployed
- end
- end
-
- context "when size is above the limit" do
- before do
- allow(metadata).to receive(:total_size).and_return(size_limit + 1.megabyte)
- allow(metadata).to receive(:entries).and_return([])
- end
-
- it 'limits the maximum size of gitlab pages' do
- subject.execute
-
- expect(deploy_status.description)
- .to match(/artifacts for pages are too large/)
- expect(deploy_status).to be_script_failure
- end
- end
-end
diff --git a/spec/support/shared_examples/services/protected_branches_shared_examples.rb b/spec/support/shared_examples/services/protected_branches_shared_examples.rb
new file mode 100644
index 00000000000..ce607a6b956
--- /dev/null
+++ b/spec/support/shared_examples/services/protected_branches_shared_examples.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'with scan result policy blocking protected branches' do
+ before do
+ create(
+ :scan_result_policy_read,
+ :blocking_protected_branches,
+ project: project)
+
+ stub_licensed_features(security_orchestration_policies: true)
+ end
+end
diff --git a/spec/support/shared_examples/services/users/build_service_shared_examples.rb b/spec/support/shared_examples/services/users/build_service_shared_examples.rb
index e448f2f874b..45ebc837e6d 100644
--- a/spec/support/shared_examples/services/users/build_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/users/build_service_shared_examples.rb
@@ -33,57 +33,6 @@ RSpec.shared_examples 'common user build items' do
end
RSpec.shared_examples_for 'current user not admin build items' do
- using RSpec::Parameterized::TableSyntax
-
- context 'with "user_default_external" application setting' do
- where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do
- true | nil | 'fl@example.com' | nil | true
- true | true | 'fl@example.com' | nil | true
- true | false | 'fl@example.com' | nil | true # admin difference
-
- true | nil | 'fl@example.com' | '' | true
- true | true | 'fl@example.com' | '' | true
- true | false | 'fl@example.com' | '' | true # admin difference
-
- true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
- true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference
- true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
-
- true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
- true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
- true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true # admin difference
-
- false | nil | 'fl@example.com' | nil | false
- false | true | 'fl@example.com' | nil | false # admin difference
- false | false | 'fl@example.com' | nil | false
-
- false | nil | 'fl@example.com' | '' | false
- false | true | 'fl@example.com' | '' | false # admin difference
- false | false | 'fl@example.com' | '' | false
-
- false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
- false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference
- false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
-
- false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
- false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference
- false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
- end
-
- with_them do
- before do
- stub_application_setting(user_default_external: user_default_external)
- stub_application_setting(user_default_internal_regex: user_default_internal_regex)
-
- params.merge!({ external: external, email: email }.compact)
- end
-
- it 'sets the value of Gitlab::CurrentSettings.user_default_external' do
- expect(user.external).to eq(result)
- end
- end
- end
-
context 'when "email_confirmation_setting" application setting is set to `hard`' do
before do
stub_application_setting_enum('email_confirmation_setting', 'hard')
diff --git a/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb b/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb
index eb03f0888b9..5d24aa10453 100644
--- a/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb
@@ -32,7 +32,7 @@ RSpec.shared_examples 'migrating records to the ghost user' do |record_class, fi
migrated_record = record_class.find_by_id(record.id)
migrated_fields.each do |field|
- expect(migrated_record.public_send(field)).to eq(User.ghost)
+ expect(migrated_record.public_send(field)).to eq(Users::Internal.ghost)
end
end
end
diff --git a/spec/support/shared_examples/users/pages_visits_shared_examples.rb b/spec/support/shared_examples/users/pages_visits_shared_examples.rb
new file mode 100644
index 00000000000..1a613fa0aed
--- /dev/null
+++ b/spec/support/shared_examples/users/pages_visits_shared_examples.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'namespace visits tracking worker' do
+ let_it_be(:base_time) { DateTime.now }
+
+ context 'when params are provided' do
+ before do
+ worker.perform(entity_type, entity.id, user.id, base_time)
+ end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [entity_type, entity.id, user.id, base_time] }
+
+ it 'tracks the entity visit' do
+ latest_visit = model.last
+
+ expect(model.count).to be(1)
+ expect(latest_visit[:entity_id]).to be(entity.id)
+ expect(latest_visit.user_id).to be(user.id)
+ end
+ end
+
+ context 'when a visit occurs within 15 minutes of a previously tracked one' do
+ [-15.minutes, 15.minutes].each do |time_diff|
+ it 'does not track the visit' do
+ worker.perform(entity_type, entity.id, user.id, base_time + time_diff)
+
+ expect(model.count).to be(1)
+ end
+ end
+ end
+
+ context 'when a visit occurs more than 15 minutes away from a previously tracked one' do
+ [-16.minutes, 16.minutes].each do |time_diff|
+ it 'tracks the visit' do
+ worker.perform(entity_type, entity.id, user.id, base_time + time_diff)
+
+ expect(model.count).to be > 1
+ end
+ end
+ end
+ end
+
+ context 'when user is missing' do
+ before do
+ worker.perform(entity_type, entity.id, nil, base_time)
+ end
+
+ it 'does not do anything' do
+ expect(model.count).to be(0)
+ end
+ end
+
+ context 'when entity is missing' do
+ before do
+ worker.perform(entity_type, nil, user.id, base_time)
+ end
+
+ it 'does not do anything' do
+ expect(model.count).to be(0)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb
index 8ecb04bfdd6..1ea5eb6fd2e 100644
--- a/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb
+++ b/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
RSpec.shared_examples 'it runs background migration jobs' do |tracking_database|
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: false)
+ end
+
describe 'defining the job attributes' do
it 'defines the data_consistency as always' do
expect(described_class.get_data_consistency).to eq(:always)
@@ -74,6 +78,38 @@ RSpec.shared_examples 'it runs background migration jobs' do |tracking_database|
end
end
+ context 'when disallow_database_ddl_feature_flags feature flag is disabled' do
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: true)
+ end
+
+ it 'does not perform the job, reschedules it in the future, and logs a message' do
+ expect(worker).not_to receive(:perform_with_connection)
+
+ 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 execution, migration rescheduled/)
+ end
+
+ lease_attempts = 3
+ delay = described_class::BACKGROUND_MIGRATIONS_DELAY
+ job_args = [10, 20]
+
+ freeze_time do
+ worker.perform('Foo', job_args, lease_attempts)
+
+ job = described_class.jobs.find { |job| job['args'] == ['Foo', job_args, lease_attempts] }
+ expect(job).to be, "Expected the job to be rescheduled with (#{job_args}, #{lease_attempts}), but it was not."
+
+ expected_time = delay.to_i + Time.now.to_i
+ expect(job['at']).to eq(expected_time),
+ "Expected the job to be rescheduled in #{expected_time} seconds, " \
+ "but it was rescheduled in #{job['at']} seconds."
+ end
+ end
+ end
+
context 'when execute_background_migrations feature flag is enabled' do
before do
stub_feature_flags(execute_background_migrations: true)
diff --git a/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb
index 8fdd59d1d8c..cf488a4d753 100644
--- a/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb
+++ b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb
@@ -3,6 +3,10 @@
RSpec.shared_examples 'batched background migrations execution worker' do
include ExclusiveLeaseHelpers
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: false)
+ end
+
it 'is a limited capacity worker' do
expect(described_class.new).to be_a(LimitedCapacity::Worker)
end
@@ -100,6 +104,23 @@ RSpec.shared_examples 'batched background migrations execution worker' do
end
end
+ context 'when disable ddl flag is enabled' do
+ let(:migration) do
+ create(:batched_background_migration, :active, interval: job_interval, table_name: table_name)
+ end
+
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: true)
+ end
+
+ it 'does nothing' do
+ expect(Gitlab::Database::BackgroundMigration::BatchedMigration).not_to receive(:find_executable)
+ expect(worker).not_to receive(:run_migration_job)
+
+ worker.perform_work(database_name, migration.id)
+ end
+ end
+
context 'when the feature flag is enabled' do
before do
stub_feature_flags(execute_batched_migrations_on_schedule: true)
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
index e7385f9abb6..003b8d07819 100644
--- 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
@@ -3,6 +3,10 @@
RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_database, table_name|
include ExclusiveLeaseHelpers
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: false)
+ end
+
describe 'defining the job attributes' do
it 'defines the data_consistency as always' do
expect(described_class.get_data_consistency).to eq(:always)
@@ -51,6 +55,12 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
expect(described_class.enabled?).to be_falsey
end
+
+ it 'returns false when disallow_database_ddl_feature_flags feature flag is enabled' do
+ stub_feature_flags(disallow_database_ddl_feature_flags: true)
+
+ expect(described_class.enabled?).to be_falsey
+ end
end
describe '#perform' do
@@ -116,6 +126,18 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
end
end
+ context 'when the disallow_database_ddl_feature_flags feature flag is enabled' do
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: true)
+ end
+
+ it 'does nothing' do
+ expect(worker).not_to receive(:queue_migrations_for_execution)
+
+ worker.perform
+ end
+ end
+
context 'when the execute_batched_migrations_on_schedule feature flag is enabled' do
let(:base_model) { Gitlab::Database.database_base_models[tracking_database] }
let(:connection) { base_model.connection }
diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb
index dc475b92c0b..b25f39c5e74 100644
--- a/spec/support/sidekiq.rb
+++ b/spec/support/sidekiq.rb
@@ -5,9 +5,11 @@ RSpec.configure do |config|
# We need to cleanup the queues before running jobs in specs because the
# middleware might have written to redis
redis_queues_cleanup!
+ redis_queues_metadata_cleanup!
Sidekiq::Testing.inline!(&block)
ensure
redis_queues_cleanup!
+ redis_queues_metadata_cleanup!
end
# As we'll review the examples with this tag, we should either: