summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
commitaee0a117a889461ce8ced6fcf73207fe017f1d99 (patch)
tree891d9ef189227a8445d83f35c1b0fc99573f4380 /spec/support
parent8d46af3258650d305f53b819eabf7ab18d22f59e (diff)
downloadgitlab-ce-aee0a117a889461ce8ced6fcf73207fe017f1d99.tar.gz
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/database/cross-database-modification-allowlist.yml59
-rw-r--r--spec/support/database/multiple_databases.rb37
-rw-r--r--spec/support/database/prevent_cross_joins.rb10
-rw-r--r--spec/support/database/query_analyzer.rb12
-rw-r--r--spec/support/flaky_tests.rb2
-rw-r--r--spec/support/frontend_fixtures.rb16
-rw-r--r--spec/support/graphql/fake_query_type.rb9
-rw-r--r--spec/support/graphql/field_inspection.rb2
-rw-r--r--spec/support/helpers/api_helpers.rb4
-rw-r--r--spec/support/helpers/features/invite_members_modal_helper.rb10
-rw-r--r--spec/support/helpers/gitaly_setup.rb16
-rw-r--r--spec/support/helpers/gpg_helpers.rb8
-rw-r--r--spec/support/helpers/graphql_helpers.rb9
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb6
-rw-r--r--spec/support/helpers/memory_usage_helper.rb2
-rw-r--r--spec/support/helpers/migrations_helpers/work_item_types_helper.rb27
-rw-r--r--spec/support/helpers/modal_helpers.rb27
-rw-r--r--spec/support/helpers/navbar_structure_helper.rb11
-rw-r--r--spec/support/helpers/session_helpers.rb4
-rw-r--r--spec/support/helpers/snowplow_helpers.rb14
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb2
-rw-r--r--spec/support/helpers/test_env.rb4
-rw-r--r--spec/support/matchers/background_migrations_matchers.rb6
-rw-r--r--spec/support/redis/redis_helpers.rb7
-rw-r--r--spec/support/redis/redis_new_instance_shared_examples.rb8
-rw-r--r--spec/support/redis/redis_shared_examples.rb21
-rw-r--r--spec/support/rspec.rb5
-rw-r--r--spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/markdown_golden_master_shared_examples.rb127
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb11
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb1
-rw-r--r--spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb25
-rw-r--r--spec/support/shared_examples/ci/create_pipeline_service_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb30
-rw-r--r--spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/controllers/unique_visits_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/csp.rb6
-rw-r--r--spec/support/shared_examples/features/page_description_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb30
-rw-r--r--spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb60
-rw-r--r--spec/support/shared_examples/features/sidebar_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/features/snippets_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb108
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/lib/gitlab/redis/multi_store_feature_flags_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb50
-rw-r--r--spec/support/shared_examples/mailers/notify_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb18
-rw-r--r--spec/support/shared_examples/namespaces/traversal_examples.rb52
-rw-r--r--spec/support/shared_examples/namespaces/traversal_scope_examples.rb19
-rw-r--r--spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb62
-rw-r--r--spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb60
-rw-r--r--spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/issuable_participants_examples.rb30
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb212
69 files changed, 1168 insertions, 377 deletions
diff --git a/spec/support/database/cross-database-modification-allowlist.yml b/spec/support/database/cross-database-modification-allowlist.yml
index d05812a64eb..d6e74349069 100644
--- a/spec/support/database/cross-database-modification-allowlist.yml
+++ b/spec/support/database/cross-database-modification-allowlist.yml
@@ -1,90 +1,31 @@
-- "./ee/spec/controllers/projects/settings/access_tokens_controller_spec.rb"
-- "./ee/spec/lib/gitlab/ci/templates/Jobs/dast_default_branch_gitlab_ci_yaml_spec.rb"
- "./ee/spec/mailers/notify_spec.rb"
-- "./ee/spec/models/ci/bridge_spec.rb"
-- "./ee/spec/models/ci/build_spec.rb"
-- "./ee/spec/models/ci/minutes/additional_pack_spec.rb"
-- "./ee/spec/models/ee/ci/job_artifact_spec.rb"
- "./ee/spec/models/group_member_spec.rb"
-- "./ee/spec/replicators/geo/pipeline_artifact_replicator_spec.rb"
- "./ee/spec/replicators/geo/terraform_state_version_replicator_spec.rb"
-- "./ee/spec/services/ci/destroy_pipeline_service_spec.rb"
- "./ee/spec/services/ci/retry_build_service_spec.rb"
-- "./ee/spec/services/ci/subscribe_bridge_service_spec.rb"
-- "./ee/spec/services/deployments/auto_rollback_service_spec.rb"
-- "./ee/spec/services/ee/ci/job_artifacts/destroy_all_expired_service_spec.rb"
-- "./ee/spec/services/ee/users/destroy_service_spec.rb"
-- "./ee/spec/services/projects/transfer_service_spec.rb"
-- "./ee/spec/services/security/security_orchestration_policies/rule_schedule_service_spec.rb"
- "./spec/controllers/abuse_reports_controller_spec.rb"
-- "./spec/controllers/admin/spam_logs_controller_spec.rb"
-- "./spec/controllers/admin/users_controller_spec.rb"
- "./spec/controllers/omniauth_callbacks_controller_spec.rb"
- "./spec/controllers/projects/issues_controller_spec.rb"
-- "./spec/controllers/projects/pipelines_controller_spec.rb"
-- "./spec/controllers/projects/settings/access_tokens_controller_spec.rb"
- "./spec/features/issues/issue_detail_spec.rb"
- "./spec/features/projects/pipelines/pipeline_spec.rb"
- "./spec/features/signed_commits_spec.rb"
- "./spec/helpers/issuables_helper_spec.rb"
- "./spec/lib/gitlab/auth_spec.rb"
- "./spec/lib/gitlab/ci/pipeline/chain/create_spec.rb"
-- "./spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb"
-- "./spec/lib/gitlab/ci/pipeline/seed/build_spec.rb"
-- "./spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb"
-- "./spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb"
-- "./spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb"
-- "./spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb"
-- "./spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb"
- "./spec/lib/gitlab/email/handler/create_issue_handler_spec.rb"
- "./spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb"
- "./spec/lib/gitlab/email/handler/create_note_handler_spec.rb"
- "./spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb"
-- "./spec/lib/peek/views/active_record_spec.rb"
-- "./spec/models/ci/build_need_spec.rb"
- "./spec/models/ci/build_trace_chunk_spec.rb"
-- "./spec/models/ci/group_variable_spec.rb"
- "./spec/models/ci/job_artifact_spec.rb"
-- "./spec/models/ci/job_variable_spec.rb"
-- "./spec/models/ci/pipeline_spec.rb"
- "./spec/models/ci/runner_spec.rb"
-- "./spec/models/ci/variable_spec.rb"
- "./spec/models/clusters/applications/runner_spec.rb"
-- "./spec/models/commit_status_spec.rb"
-- "./spec/models/concerns/batch_destroy_dependent_associations_spec.rb"
-- "./spec/models/concerns/bulk_insertable_associations_spec.rb"
-- "./spec/models/concerns/has_environment_scope_spec.rb"
-- "./spec/models/concerns/token_authenticatable_spec.rb"
- "./spec/models/design_management/version_spec.rb"
- "./spec/models/hooks/system_hook_spec.rb"
- "./spec/models/members/project_member_spec.rb"
-- "./spec/models/spam_log_spec.rb"
- "./spec/models/user_spec.rb"
- "./spec/models/user_status_spec.rb"
-- "./spec/requests/api/ci/pipeline_schedules_spec.rb"
-- "./spec/requests/api/ci/pipelines_spec.rb"
-- "./spec/requests/api/commit_statuses_spec.rb"
- "./spec/requests/api/commits_spec.rb"
-- "./spec/requests/api/graphql/mutations/ci/pipeline_destroy_spec.rb"
-- "./spec/requests/api/resource_access_tokens_spec.rb"
-- "./spec/requests/api/users_spec.rb"
-- "./spec/services/ci/create_pipeline_service/environment_spec.rb"
-- "./spec/services/ci/create_pipeline_service_spec.rb"
-- "./spec/services/ci/destroy_pipeline_service_spec.rb"
-- "./spec/services/ci/ensure_stage_service_spec.rb"
-- "./spec/services/ci/expire_pipeline_cache_service_spec.rb"
-- "./spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb"
-- "./spec/services/ci/job_artifacts/destroy_associations_service_spec.rb"
-- "./spec/services/ci/pipeline_bridge_status_service_spec.rb"
-- "./spec/services/ci/pipelines/add_job_service_spec.rb"
- "./spec/services/ci/retry_build_service_spec.rb"
-- "./spec/services/groups/transfer_service_spec.rb"
-- "./spec/services/projects/destroy_service_spec.rb"
- "./spec/services/projects/overwrite_project_service_spec.rb"
-- "./spec/services/projects/transfer_service_spec.rb"
-- "./spec/services/resource_access_tokens/revoke_service_spec.rb"
-- "./spec/services/users/destroy_service_spec.rb"
-- "./spec/services/users/reject_service_spec.rb"
- "./spec/workers/merge_requests/create_pipeline_worker_spec.rb"
-- "./spec/workers/remove_expired_members_worker_spec.rb"
- "./spec/workers/repository_cleanup_worker_spec.rb"
diff --git a/spec/support/database/multiple_databases.rb b/spec/support/database/multiple_databases.rb
index 9e72ea589e3..94857b47127 100644
--- a/spec/support/database/multiple_databases.rb
+++ b/spec/support/database/multiple_databases.rb
@@ -6,6 +6,10 @@ module Database
skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
end
+ def skip_if_multiple_databases_are_setup
+ skip 'Skipping because multiple databases are set up' if Gitlab::Database.has_config?(:ci)
+ end
+
def reconfigure_db_connection(name: nil, config_hash: {}, model: ActiveRecord::Base, config_model: nil)
db_config = (config_model || model).connection_db_config
@@ -46,6 +50,26 @@ module Database
new_handler&.clear_all_connections!
end
# rubocop:enable Database/MultipleDatabases
+
+ def with_added_ci_connection
+ if Gitlab::Database.has_config?(:ci)
+ # No need to add a ci: connection if we already have one
+ yield
+ else
+ with_reestablished_active_record_base(reconnect: true) do
+ reconfigure_db_connection(
+ name: :ci,
+ model: Ci::ApplicationRecord,
+ config_model: ActiveRecord::Base
+ )
+
+ yield
+
+ # Cleanup connection_specification_name for Ci::ApplicationRecord
+ Ci::ApplicationRecord.remove_connection
+ end
+ end
+ end
end
module ActiveRecordBaseEstablishConnection
@@ -69,18 +93,9 @@ RSpec.configure do |config|
end
end
- config.around(:each, :mocked_ci_connection) do |example|
- with_reestablished_active_record_base(reconnect: true) do
- reconfigure_db_connection(
- name: :ci,
- model: Ci::ApplicationRecord,
- config_model: ActiveRecord::Base
- )
-
+ config.around(:each, :add_ci_connection) do |example|
+ with_added_ci_connection do
example.run
-
- # Cleanup connection_specification_name for Ci::ApplicationRecord
- Ci::ApplicationRecord.remove_connection
end
end
end
diff --git a/spec/support/database/prevent_cross_joins.rb b/spec/support/database/prevent_cross_joins.rb
index e69374fbc70..42c69a26788 100644
--- a/spec/support/database/prevent_cross_joins.rb
+++ b/spec/support/database/prevent_cross_joins.rb
@@ -31,9 +31,13 @@ module Database
# See https://gitlab.com/gitlab-org/gitlab/-/issues/339396
return if sql.include?("DISABLE TRIGGER") || sql.include?("ENABLE TRIGGER")
- # PgQuery might fail in some cases due to limited nesting:
- # https://github.com/pganalyze/pg_query/issues/209
- tables = PgQuery.parse(sql).tables
+ tables = begin
+ PgQuery.parse(sql).tables
+ rescue PgQuery::ParseError
+ # PgQuery might fail in some cases due to limited nesting:
+ # https://github.com/pganalyze/pg_query/issues/209
+ return
+ end
schemas = ::Gitlab::Database::GitlabSchema.table_schemas(tables)
diff --git a/spec/support/database/query_analyzer.rb b/spec/support/database/query_analyzer.rb
index 85fa55f81ef..6d6627d54b9 100644
--- a/spec/support/database/query_analyzer.rb
+++ b/spec/support/database/query_analyzer.rb
@@ -4,11 +4,15 @@
# can be disabled selectively
RSpec.configure do |config|
- config.around do |example|
+ config.before do |example|
if example.metadata.fetch(:query_analyzers, true)
- ::Gitlab::Database::QueryAnalyzer.instance.within { example.run }
- else
- example.run
+ ::Gitlab::Database::QueryAnalyzer.instance.begin!
+ end
+ end
+
+ config.after do |example|
+ if example.metadata.fetch(:query_analyzers, true)
+ ::Gitlab::Database::QueryAnalyzer.instance.end!
end
end
end
diff --git a/spec/support/flaky_tests.rb b/spec/support/flaky_tests.rb
index 30a064d8705..0c211af695d 100644
--- a/spec/support/flaky_tests.rb
+++ b/spec/support/flaky_tests.rb
@@ -11,7 +11,7 @@ RSpec.configure do |config|
raise "$SUITE_FLAKY_RSPEC_REPORT_PATH is empty." if ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'].to_s.empty?
raise "#{ENV['SUITE_FLAKY_RSPEC_REPORT_PATH']} doesn't exist" unless File.exist?(ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'])
- RspecFlaky::Report.load(ENV['SUITE_FLAKY_RSPEC_REPORT_PATH']).map { |_, flaky_test_data| flaky_test_data["example_id"] }
+ RspecFlaky::Report.load(ENV['SUITE_FLAKY_RSPEC_REPORT_PATH']).map { |_, flaky_test_data| flaky_test_data.to_h[:example_id] }
rescue => e # rubocop:disable Style/RescueStandardError
puts e
[]
diff --git a/spec/support/frontend_fixtures.rb b/spec/support/frontend_fixtures.rb
new file mode 100644
index 00000000000..5587d9059dd
--- /dev/null
+++ b/spec/support/frontend_fixtures.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+return unless ENV['CI']
+return unless ENV['GENERATE_FRONTEND_FIXTURES_MAPPING'] == 'true'
+
+RSpec.configure do |config|
+ config.before(:suite) do
+ $fixtures_mapping = Hash.new { |h, k| h[k] = [] } # rubocop:disable Style/GlobalVars
+ end
+
+ config.after(:suite) do
+ next unless ENV['FRONTEND_FIXTURES_MAPPING_PATH']
+
+ File.write(ENV['FRONTEND_FIXTURES_MAPPING_PATH'], $fixtures_mapping.to_json) # rubocop:disable Style/GlobalVars
+ end
+end
diff --git a/spec/support/graphql/fake_query_type.rb b/spec/support/graphql/fake_query_type.rb
index ffd851a6e6a..18cf2cf3e82 100644
--- a/spec/support/graphql/fake_query_type.rb
+++ b/spec/support/graphql/fake_query_type.rb
@@ -1,15 +1,22 @@
# frozen_string_literal: true
+require 'graphql'
module Graphql
- class FakeQueryType < Types::BaseObject
+ class FakeQueryType < ::GraphQL::Schema::Object
graphql_name 'FakeQuery'
field :hello_world, String, null: true do
argument :message, String, required: false
end
+ field :breaking_field, String, null: true
+
def hello_world(message: "world")
"Hello #{message}!"
end
+
+ def breaking_field
+ raise "This field is supposed to break"
+ end
end
end
diff --git a/spec/support/graphql/field_inspection.rb b/spec/support/graphql/field_inspection.rb
index f39ba751141..e5fe37ec555 100644
--- a/spec/support/graphql/field_inspection.rb
+++ b/spec/support/graphql/field_inspection.rb
@@ -22,7 +22,7 @@ module Graphql
@type ||= begin
field_type = @field.type.respond_to?(:to_graphql) ? @field.type.to_graphql : @field.type
- # The type could be nested. For example `[GraphQL::STRING_TYPE]`:
+ # The type could be nested. For example `[GraphQL::Types::String]`:
# - List
# - String!
# - String
diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb
index d3cc7367b6e..fd85071cca3 100644
--- a/spec/support/helpers/api_helpers.rb
+++ b/spec/support/helpers/api_helpers.rb
@@ -19,13 +19,15 @@ module ApiHelpers
# => "/api/v2/issues?foo=bar&private_token=..."
#
# Returns the relative path to the requested API resource
- def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil)
+ def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil, job_token: nil)
full_path = "/api/#{version}#{path}"
if oauth_access_token
query_string = "access_token=#{oauth_access_token.token}"
elsif personal_access_token
query_string = "private_token=#{personal_access_token.token}"
+ elsif job_token
+ query_string = "job_token=#{job_token}"
elsif user
personal_access_token = create(:personal_access_token, user: user)
query_string = "private_token=#{personal_access_token.token}"
diff --git a/spec/support/helpers/features/invite_members_modal_helper.rb b/spec/support/helpers/features/invite_members_modal_helper.rb
index 3502558b2c2..11040562b49 100644
--- a/spec/support/helpers/features/invite_members_modal_helper.rb
+++ b/spec/support/helpers/features/invite_members_modal_helper.rb
@@ -5,7 +5,7 @@ module Spec
module Helpers
module Features
module InviteMembersModalHelper
- def invite_member(name, role: 'Guest', expires_at: nil, area_of_focus: false)
+ def invite_member(name, role: 'Guest', expires_at: nil)
click_on 'Invite members'
page.within '[data-testid="invite-members-modal"]' do
@@ -14,7 +14,6 @@ module Spec
wait_for_requests
click_button name
choose_options(role, expires_at)
- choose_area_of_focus if area_of_focus
click_button 'Invite'
@@ -44,13 +43,6 @@ module Spec
fill_in 'YYYY-MM-DD', with: expires_at.strftime('%Y-%m-%d') if expires_at
end
-
- def choose_area_of_focus
- page.within '[data-testid="area-of-focus-checks"]' do
- check 'Contribute to the codebase'
- check 'Collaborate on open issues and merge requests'
- end
- end
end
end
end
diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb
index 8a329c2f9dd..923051a2e04 100644
--- a/spec/support/helpers/gitaly_setup.rb
+++ b/spec/support/helpers/gitaly_setup.rb
@@ -18,8 +18,12 @@ module GitalySetup
Logger.new($stdout, level: level, formatter: ->(_, _, _, msg) { msg })
end
+ def expand_path(path)
+ File.expand_path(path, File.join(__dir__, '../../..'))
+ end
+
def tmp_tests_gitaly_dir
- File.expand_path('../../../tmp/tests/gitaly', __dir__)
+ expand_path('tmp/tests/gitaly')
end
def tmp_tests_gitaly_bin_dir
@@ -27,11 +31,11 @@ module GitalySetup
end
def tmp_tests_gitlab_shell_dir
- File.expand_path('../../../tmp/tests/gitlab-shell', __dir__)
+ expand_path('tmp/tests/gitlab-shell')
end
def rails_gitlab_shell_secret
- File.expand_path('../../../.gitlab_shell_secret', __dir__)
+ expand_path('.gitlab_shell_secret')
end
def gemfile
@@ -48,7 +52,7 @@ module GitalySetup
def env
{
- 'HOME' => File.expand_path('tmp/tests'),
+ 'HOME' => expand_path('tmp/tests'),
'GEM_PATH' => Gem.path.join(':'),
'BUNDLE_APP_CONFIG' => File.join(gemfile_dir, '.bundle'),
'BUNDLE_INSTALL_FLAGS' => nil,
@@ -67,7 +71,7 @@ module GitalySetup
system('bundle config set --local retry 3', chdir: gemfile_dir)
if ENV['CI']
- bundle_path = File.expand_path('../../../vendor/gitaly-ruby', __dir__)
+ bundle_path = expand_path('vendor/gitaly-ruby')
system('bundle', 'config', 'set', '--local', 'path', bundle_path, chdir: gemfile_dir)
end
end
@@ -154,7 +158,7 @@ module GitalySetup
LOGGER.debug "Checking gitaly-ruby bundle...\n"
out = ENV['CI'] ? $stdout : '/dev/null'
- abort 'bundle check failed' unless system(env, 'bundle', 'check', out: out, chdir: File.dirname(gemfile))
+ abort 'bundle check failed' unless system(env, 'bundle', 'check', out: out, chdir: gemfile_dir)
end
def read_socket_path(service)
diff --git a/spec/support/helpers/gpg_helpers.rb b/spec/support/helpers/gpg_helpers.rb
index 81e669aab57..7e78fd86de3 100644
--- a/spec/support/helpers/gpg_helpers.rb
+++ b/spec/support/helpers/gpg_helpers.rb
@@ -138,7 +138,7 @@ module GpgHelpers
end
def primary_keyid
- fingerprint[-16..-1]
+ fingerprint[-16..]
end
def fingerprint
@@ -281,7 +281,7 @@ module GpgHelpers
end
def primary_keyid2
- fingerprint2[-16..-1]
+ fingerprint2[-16..]
end
def fingerprint2
@@ -374,7 +374,7 @@ module GpgHelpers
end
def primary_keyid
- fingerprint[-16..-1]
+ fingerprint[-16..]
end
def fingerprint
@@ -776,7 +776,7 @@ module GpgHelpers
end
def primary_keyid
- fingerprint[-16..-1]
+ fingerprint[-16..]
end
def fingerprint
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 1f0c9b658dc..8b7d1c753d5 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -515,8 +515,13 @@ module GraphqlHelpers
# Allows for array indexing, like this
# ['project', 'boards', 'edges', 0, 'node', 'lists']
keys.reduce(data) do |memo, key|
- if memo.is_a?(Array)
- key.is_a?(Integer) ? memo[key] : memo.flat_map { |e| Array.wrap(e[key]) }
+ if memo.is_a?(Array) && key.is_a?(Integer)
+ memo[key]
+ elsif memo.is_a?(Array)
+ memo.compact.flat_map do |e|
+ x = e[key]
+ x.nil? ? [x] : Array.wrap(x)
+ end
else
memo&.dig(key)
end
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index fb909008f12..84cd0181533 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -13,6 +13,12 @@ module JavaScriptFixturesHelpers
included do |base|
base.around do |example|
+ # Don't actually run the example when we're only interested in the `test file -> JSON frontend fixture` mapping
+ if ENV['GENERATE_FRONTEND_FIXTURES_MAPPING'] == 'true'
+ $fixtures_mapping[example.metadata[:file_path].delete_prefix('./')] << File.join(fixture_root_path, example.description) # rubocop:disable Style/GlobalVars
+ next
+ end
+
# pick an arbitrary date from the past, so tests are not time dependent
# Also see spec/frontend/__helpers__/fake_date/jest.js
Timecop.freeze(Time.utc(2015, 7, 3, 10)) { example.run }
diff --git a/spec/support/helpers/memory_usage_helper.rb b/spec/support/helpers/memory_usage_helper.rb
index aa7b3bae83a..02d1935921f 100644
--- a/spec/support/helpers/memory_usage_helper.rb
+++ b/spec/support/helpers/memory_usage_helper.rb
@@ -23,7 +23,7 @@ module MemoryUsageHelper
output, status = Gitlab::Popen.popen(%w(free -m))
abort "`free -m` return code is #{status}: #{output}" unless status == 0
- result = output.split("\n")[1].split(" ")[1..-1]
+ result = output.split("\n")[1].split(" ")[1..]
attrs = %i(m_total m_used m_free m_shared m_buffers_cache m_available).freeze
attrs.zip(result).to_h
diff --git a/spec/support/helpers/migrations_helpers/work_item_types_helper.rb b/spec/support/helpers/migrations_helpers/work_item_types_helper.rb
new file mode 100644
index 00000000000..59b1f1b1305
--- /dev/null
+++ b/spec/support/helpers/migrations_helpers/work_item_types_helper.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module MigrationHelpers
+ module WorkItemTypesHelper
+ DEFAULT_WORK_ITEM_TYPES = {
+ issue: { name: 'Issue', icon_name: 'issue-type-issue', enum_value: 0 },
+ incident: { name: 'Incident', icon_name: 'issue-type-incident', enum_value: 1 },
+ test_case: { name: 'Test Case', icon_name: 'issue-type-test-case', enum_value: 2 },
+ requirement: { name: 'Requirement', icon_name: 'issue-type-requirements', enum_value: 3 },
+ task: { name: 'Task', icon_name: 'issue-type-task', enum_value: 4 }
+ }.freeze
+
+ def reset_work_item_types
+ work_item_types_table.delete_all
+
+ DEFAULT_WORK_ITEM_TYPES.each do |type, attributes|
+ work_item_types_table.create!(base_type: attributes[:enum_value], **attributes.slice(:name, :icon_name))
+ end
+ end
+
+ private
+
+ def work_item_types_table
+ table(:work_item_types)
+ end
+ end
+end
diff --git a/spec/support/helpers/modal_helpers.rb b/spec/support/helpers/modal_helpers.rb
new file mode 100644
index 00000000000..a1f03cc0da5
--- /dev/null
+++ b/spec/support/helpers/modal_helpers.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Spec
+ module Support
+ module Helpers
+ module ModalHelpers
+ def within_modal
+ page.within('[role="dialog"]') do
+ yield
+ end
+ end
+
+ def accept_gl_confirm(text = nil, button_text: 'OK')
+ yield if block_given?
+
+ within_modal do
+ unless text.nil?
+ expect(page).to have_content(text)
+ end
+
+ click_button button_text
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb
index c2ec82155cd..6fa69cbd6ad 100644
--- a/spec/support/helpers/navbar_structure_helper.rb
+++ b/spec/support/helpers/navbar_structure_helper.rb
@@ -19,6 +19,17 @@ module NavbarStructureHelper
hash[:nav_sub_items].insert(index + 1, new_sub_nav_item_name)
end
+ def insert_before_sub_nav_item(after_sub_nav_item_name, within:, new_sub_nav_item_name:)
+ expect(structure).to include(a_hash_including(nav_item: within))
+ hash = structure.find { |h| h[:nav_item] == within if h }
+
+ expect(hash).to have_key(:nav_sub_items)
+ expect(hash[:nav_sub_items]).to include(after_sub_nav_item_name)
+
+ index = hash[:nav_sub_items].find_index(after_sub_nav_item_name)
+ hash[:nav_sub_items].insert(index, new_sub_nav_item_name)
+ end
+
def insert_package_nav(within)
insert_after_nav_item(
within,
diff --git a/spec/support/helpers/session_helpers.rb b/spec/support/helpers/session_helpers.rb
index 4ef099a393e..236585296e5 100644
--- a/spec/support/helpers/session_helpers.rb
+++ b/spec/support/helpers/session_helpers.rb
@@ -17,10 +17,10 @@ module SessionHelpers
end
def get_session_keys
- Gitlab::Redis::SharedState.with { |redis| redis.scan_each(match: 'session:gitlab:*').to_a }
+ Gitlab::Redis::Sessions.with { |redis| redis.scan_each(match: 'session:gitlab:*').to_a }
end
def get_ttl(key)
- Gitlab::Redis::SharedState.with { |redis| redis.ttl(key) }
+ Gitlab::Redis::Sessions.with { |redis| redis.ttl(key) }
end
end
diff --git a/spec/support/helpers/snowplow_helpers.rb b/spec/support/helpers/snowplow_helpers.rb
index 553739b5d30..c8b194919ed 100644
--- a/spec/support/helpers/snowplow_helpers.rb
+++ b/spec/support/helpers/snowplow_helpers.rb
@@ -48,11 +48,15 @@ module SnowplowHelpers
# )
def expect_snowplow_event(category:, action:, context: nil, **kwargs)
if context
- kwargs[:context] = []
- context.each do |c|
- expect(SnowplowTracker::SelfDescribingJson).to have_received(:new)
- .with(c[:schema], c[:data]).at_least(:once)
- kwargs[:context] << an_instance_of(SnowplowTracker::SelfDescribingJson)
+ if context.is_a?(Array)
+ kwargs[:context] = []
+ context.each do |c|
+ expect(SnowplowTracker::SelfDescribingJson).to have_received(:new)
+ .with(c[:schema], c[:data]).at_least(:once)
+ kwargs[:context] << an_instance_of(SnowplowTracker::SelfDescribingJson)
+ end
+ else
+ kwargs[:context] = context
end
end
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index ef3c39c83c2..ae031f58bd4 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -93,7 +93,7 @@ module StubGitlabCalls
def stub_commonmark_sourcepos_disabled
render_options =
- if Feature.enabled?(:use_cmark_renderer)
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS_C
else
Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS_RUBY
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index acbc15f7b62..d36bc4e3cb4 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -53,7 +53,7 @@ module TestEnv
'wip' => 'b9238ee',
'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3',
- 'add-ipython-files' => '2b5ef814',
+ 'add-ipython-files' => '532c837',
'add-pdf-file' => 'e774ebd',
'squash-large-files' => '54cec52',
'add-pdf-text-binary' => '79faa7b',
@@ -594,6 +594,8 @@ module TestEnv
# Not a git SHA, so return early
return false unless expected_version =~ ::Gitlab::Git::COMMIT_ID
+ return false unless Dir.exist?(component_folder)
+
sha, exit_status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} rev-parse HEAD), component_folder)
return false if exit_status != 0
diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb
index d3833a1e8e8..1057639beec 100644
--- a/spec/support/matchers/background_migrations_matchers.rb
+++ b/spec/support/matchers/background_migrations_matchers.rb
@@ -9,7 +9,7 @@ RSpec::Matchers.define :be_background_migration_with_arguments do |arguments|
end
RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected|
- define_method :matches? do |migration|
+ match(notify_expectation_failures: true) do |migration|
expect(migration).to be_background_migration_with_arguments(expected)
BackgroundMigrationWorker.jobs.any? do |job|
@@ -26,7 +26,7 @@ RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected|
end
RSpec::Matchers.define :be_scheduled_migration do |*expected|
- define_method :matches? do |migration|
+ match(notify_expectation_failures: true) do |migration|
expect(migration).to be_background_migration_with_arguments(expected)
BackgroundMigrationWorker.jobs.any? do |job|
@@ -41,7 +41,7 @@ RSpec::Matchers.define :be_scheduled_migration do |*expected|
end
RSpec::Matchers.define :be_scheduled_migration_with_multiple_args do |*expected|
- define_method :matches? do |migration|
+ match(notify_expectation_failures: true) do |migration|
expect(migration).to be_background_migration_with_arguments(expected)
BackgroundMigrationWorker.jobs.any? do |job|
diff --git a/spec/support/redis/redis_helpers.rb b/spec/support/redis/redis_helpers.rb
index f27d873eb31..90c15dea1f8 100644
--- a/spec/support/redis/redis_helpers.rb
+++ b/spec/support/redis/redis_helpers.rb
@@ -32,4 +32,11 @@ module RedisHelpers
def redis_sessions_cleanup!
Gitlab::Redis::Sessions.with(&:flushdb)
end
+
+ # Usage: reset cached instance config
+ def redis_clear_raw_config!(instance_class)
+ instance_class.remove_instance_variable(:@_raw_config)
+ rescue NameError
+ # raised if @_raw_config was not set; ignore
+ end
end
diff --git a/spec/support/redis/redis_new_instance_shared_examples.rb b/spec/support/redis/redis_new_instance_shared_examples.rb
index e9b1e3e4da1..943fe0f11ba 100644
--- a/spec/support/redis/redis_new_instance_shared_examples.rb
+++ b/spec/support/redis/redis_new_instance_shared_examples.rb
@@ -8,10 +8,16 @@ RSpec.shared_examples "redis_new_instance_shared_examples" do |name, fallback_cl
let(:fallback_config_file) { nil }
before do
+ redis_clear_raw_config!(fallback_class)
+
allow(fallback_class).to receive(:config_file_name).and_return(fallback_config_file)
end
- include_examples "redis_shared_examples"
+ after do
+ redis_clear_raw_config!(fallback_class)
+ end
+
+ it_behaves_like "redis_shared_examples"
describe '.config_file_name' do
subject { described_class.config_file_name }
diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb
index 72b3a72f9d4..d4c8682ec71 100644
--- a/spec/support/redis/redis_shared_examples.rb
+++ b/spec/support/redis/redis_shared_examples.rb
@@ -20,11 +20,11 @@ RSpec.shared_examples "redis_shared_examples" do
before do
allow(described_class).to receive(:config_file_name).and_return(Rails.root.join(config_file_name).to_s)
- clear_raw_config
+ redis_clear_raw_config!(described_class)
end
after do
- clear_raw_config
+ redis_clear_raw_config!(described_class)
end
describe '.config_file_name' do
@@ -93,18 +93,23 @@ RSpec.shared_examples "redis_shared_examples" do
subject { described_class.new(rails_env).store }
shared_examples 'redis store' do
+ let(:redis_store) { ::Redis::Store }
+ let(:redis_store_to_s) { "Redis Client connected to #{host} against DB #{redis_database}" }
+
it 'instantiates Redis::Store' do
- is_expected.to be_a(::Redis::Store)
- expect(subject.to_s).to eq("Redis Client connected to #{host} against DB #{redis_database}")
+ is_expected.to be_a(redis_store)
+
+ expect(subject.to_s).to eq(redis_store_to_s)
end
context 'with the namespace' do
let(:namespace) { 'namespace_name' }
+ let(:redis_store_to_s) { "Redis Client connected to #{host} against DB #{redis_database} with namespace #{namespace}" }
subject { described_class.new(rails_env).store(namespace: namespace) }
it "uses specified namespace" do
- expect(subject.to_s).to eq("Redis Client connected to #{host} against DB #{redis_database} with namespace #{namespace}")
+ expect(subject.to_s).to eq(redis_store_to_s)
end
end
end
@@ -394,12 +399,6 @@ RSpec.shared_examples "redis_shared_examples" do
end
end
- def clear_raw_config
- described_class.remove_instance_variable(:@_raw_config)
- rescue NameError
- # raised if @_raw_config was not set; ignore
- end
-
def clear_pool
described_class.remove_instance_variable(:@pool)
rescue NameError
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index 00b9aac7bf4..b4a25fd121d 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -15,7 +15,10 @@ require 'rubocop'
require 'rubocop/rspec/support'
RSpec.configure do |config|
- config.mock_with :rspec
+ config.mock_with :rspec do |mocks|
+ mocks.verify_doubled_constant_names = true
+ end
+
config.raise_errors_for_deprecations!
config.include StubConfiguration
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 07012914a4d..6414a4d1eb3 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
@@ -28,7 +28,7 @@ RSpec.shared_context 'project service activation' do
end
def click_test_integration
- click_link('Test settings')
+ click_button('Test settings')
end
def click_test_then_save_integration(expect_test_to_fail: true)
diff --git a/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb b/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb
new file mode 100644
index 00000000000..d0915bbf158
--- /dev/null
+++ b/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# See spec/fixtures/markdown/markdown_golden_master_examples.yml for documentation on how this spec works.
+RSpec.shared_context 'API::Markdown Golden Master shared context' do |markdown_yml_file_path|
+ include ApiHelpers
+ include WikiHelpers
+
+ let_it_be(:user) { create(:user, username: 'gfm_user') }
+
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:project) { create(:project, :public, :repository, group: group) }
+
+ let_it_be(:label) { create(:label, project: project, title: 'bug') }
+ let_it_be(:milestone) { create(:milestone, project: project, title: '1.1') }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:merge_request) { create(:merge_request, source_project: project) }
+
+ let_it_be(:project_wiki) { create(:project_wiki, project: project, user: user) }
+
+ let_it_be(:project_wiki_page) { create(:wiki_page, wiki: project_wiki) }
+
+ before(:all) do
+ group.add_owner(user)
+ project.add_maintainer(user)
+ end
+
+ before do
+ sign_in(user)
+ end
+
+ markdown_examples = begin
+ yaml = File.read(markdown_yml_file_path)
+ YAML.safe_load(yaml, symbolize_names: true, aliases: true)
+ end
+
+ it "examples must be unique and alphabetized by name", :unlimited_max_formatted_output_length do
+ names = markdown_examples.map { |example| example[:name] }
+ expect(names).to eq(names.sort.uniq)
+ end
+
+ if focused_markdown_examples_string = ENV['FOCUSED_MARKDOWN_EXAMPLES']
+ focused_markdown_examples = focused_markdown_examples_string.split(',').map(&:strip) || []
+ markdown_examples.reject! {|markdown_example| !focused_markdown_examples.include?(markdown_example.fetch(:name)) }
+ end
+
+ markdown_examples.each do |markdown_example|
+ name = markdown_example.fetch(:name)
+ api_context = markdown_example[:api_context]
+
+ if api_context && !name.end_with?("_for_#{api_context}")
+ raise "Name must have suffix of '_for_#{api_context}' to the api_context"
+ end
+
+ context "for #{name}#{api_context ? " (api_context: #{api_context})" : ''}" do
+ let(:pending_reason) do
+ pending_value = markdown_example.fetch(:pending, nil)
+ get_pending_reason(pending_value)
+ end
+
+ let(:example_markdown) { markdown_example.fetch(:markdown) }
+ let(:example_html) { markdown_example.fetch(:html) }
+ let(:substitutions) { markdown_example.fetch(:substitutions, {}) }
+
+ it "verifies conversion of GFM to HTML", :unlimited_max_formatted_output_length do
+ pending pending_reason if pending_reason
+
+ normalized_example_html = normalize_html(example_html, substitutions)
+
+ api_url = get_url_for_api_context(api_context)
+
+ post api_url, params: { text: example_markdown, gfm: true }
+ expect(response).to be_successful
+ response_body = Gitlab::Json.parse(response.body)
+ # Some requests have the HTML in the `html` key, others in the `body` key.
+ response_html = response_body['body'] ? response_body.fetch('body') : response_body.fetch('html')
+ normalized_response_html = normalize_html(response_html, substitutions)
+
+ expect(normalized_response_html).to eq(normalized_example_html)
+ end
+
+ def get_pending_reason(pending_value)
+ return false unless pending_value
+
+ return pending_value if pending_value.is_a?(String)
+
+ pending_value[:backend] || false
+ end
+
+ def normalize_html(html, substitutions)
+ normalized_html = html.dup
+ # Note: having the top level `substitutions` data structure be a hash of arrays
+ # allows us to compose multiple substitutions via YAML anchors (YAML anchors
+ # pointing to arrays can't be combined)
+ substitutions.each_value do |substitution_entry|
+ substitution_entry.each do |substitution|
+ regex = substitution.fetch(:regex)
+ replacement = substitution.fetch(:replacement)
+ normalized_html.gsub!(%r{#{regex}}, replacement)
+ end
+ end
+
+ normalized_html
+ end
+ end
+ end
+
+ def supported_api_contexts
+ %w(project group project_wiki)
+ end
+
+ def get_url_for_api_context(api_context)
+ case api_context
+ when 'project'
+ "/#{project.full_path}/preview_markdown"
+ when 'group'
+ "/groups/#{group.full_path}/preview_markdown"
+ when 'project_wiki'
+ "/#{project.full_path}/-/wikis/#{project_wiki_page.slug}/preview_markdown"
+ when nil
+ api "/markdown"
+ else
+ raise "Error: 'context' extension was '#{api_context}'. It must be one of: #{supported_api_contexts.join(',')}"
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index bcc6abdc308..085f1f13c2c 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -5,7 +5,7 @@ RSpec.shared_context 'project navbar structure' do
{
nav_item: _('Security & Compliance'),
nav_sub_items: [
- (_('Audit Events') if Gitlab.ee?),
+ (_('Audit events') if Gitlab.ee?),
_('Configuration')
]
}
@@ -94,11 +94,11 @@ RSpec.shared_context 'project navbar structure' do
{
nav_item: _('Analytics'),
nav_sub_items: [
+ _('Value stream'),
_('CI/CD'),
(_('Code review') if Gitlab.ee?),
(_('Merge request') if Gitlab.ee?),
- _('Repository'),
- _('Value stream')
+ _('Repository')
]
},
{
@@ -165,7 +165,7 @@ RSpec.shared_context 'group navbar structure' do
{
nav_item: _('Security & Compliance'),
nav_sub_items: [
- _('Audit Events')
+ _('Audit events')
]
}
end
@@ -190,7 +190,8 @@ RSpec.shared_context 'group navbar structure' do
[
_('List'),
_('Board'),
- _('Milestones')
+ _('Milestones'),
+ (_('Iterations') if Gitlab.ee?)
]
end
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 b432aa24bb8..ad6462dc367 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -48,6 +48,7 @@ RSpec.shared_context 'GroupPolicy context' do
destroy_package
create_projects
read_cluster create_cluster update_cluster admin_cluster add_cluster
+ admin_group_runners
]
end
diff --git a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
index e8cc666605b..06800f7cded 100644
--- a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
+++ b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
@@ -9,16 +9,18 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
let(:extracted_data) { BulkImports::Pipeline::ExtractedData.new(data: {}) }
- context 'successfully imports wiki for an entity' do
- subject { described_class.new(context) }
+ subject { described_class.new(context) }
- before do
- allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
- allow(extractor).to receive(:extract).and_return(extracted_data)
- end
+ before do
+ allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
+ allow(extractor).to receive(:extract).and_return(extracted_data)
end
+ end
+ context 'when wiki exists' do
it 'imports new wiki into destination project' do
+ expect(subject).to receive(:source_wiki_exists?).and_return(true)
+
expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
url = "https://oauth2:token@gitlab.example/#{entity.source_full_path}.wiki.git"
expect(repository_service).to receive(:fetch_remote).with(url, any_args).and_return 0
@@ -27,5 +29,16 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
subject.run
end
end
+
+ context 'when wiki does not exist' do
+ it 'does not import wiki' do
+ expect(subject).to receive(:source_wiki_exists?).and_return(false)
+
+ expect(parent.wiki).not_to receive(:ensure_repository)
+ expect(parent.wiki.repository).not_to receive(:ensure_repository)
+
+ expect { subject.run }.not_to raise_error
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/ci/create_pipeline_service_shared_examples.rb b/spec/support/shared_examples/ci/create_pipeline_service_shared_examples.rb
new file mode 100644
index 00000000000..a72ce320e90
--- /dev/null
+++ b/spec/support/shared_examples/ci/create_pipeline_service_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'pipelines are created without N+1 SQL queries' do
+ before do
+ # warm up
+ stub_ci_pipeline_yaml_file(config1)
+ execute_service
+ end
+
+ it 'avoids N+1 queries', :aggregate_failures, :request_store, :use_sql_query_cache do
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ stub_ci_pipeline_yaml_file(config1)
+
+ pipeline = execute_service.payload
+
+ expect(pipeline).to be_created_successfully
+ end
+
+ expect do
+ stub_ci_pipeline_yaml_file(config2)
+
+ pipeline = execute_service.payload
+
+ expect(pipeline).to be_created_successfully
+ end.not_to exceed_all_query_limit(control).with_threshold(accepted_n_plus_ones)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
index 0ffa32dec9e..46fc2cbdc9b 100644
--- a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
@@ -58,11 +58,12 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET new' do
end
RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
+ let(:repo_fake) { Struct.new(:id, :login, :full_name, :name, :owner, keyword_init: true) }
let(:new_import_url) { public_send("new_import_#{provider}_url") }
let(:user) { create(:user) }
- let(:repo) { OpenStruct.new(login: 'vim', full_name: 'asd/vim', name: 'vim', owner: { login: 'owner' }) }
- let(:org) { OpenStruct.new(login: 'company') }
- let(:org_repo) { OpenStruct.new(login: 'company', full_name: 'company/repo', name: 'repo', owner: { login: 'owner' }) }
+ let(:repo) { repo_fake.new(login: 'vim', full_name: 'asd/vim', name: 'vim', owner: { login: 'owner' }) }
+ let(:org) { double('org', login: 'company') }
+ let(:org_repo) { repo_fake.new(login: 'company', full_name: 'company/repo', name: 'repo', owner: { login: 'owner' }) }
before do
assign_session_token(provider)
@@ -72,7 +73,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
project = create(:project, import_type: provider, namespace: user.namespace, import_status: :finished, import_source: 'example/repo')
group = create(:group)
group.add_owner(user)
- stub_client(repos: [repo, org_repo], orgs: [org], org_repos: [org_repo], each_page: [OpenStruct.new(objects: [repo, org_repo])].to_enum)
+ stub_client(repos: [repo, org_repo], orgs: [org], org_repos: [org_repo], each_page: [double('client', objects: [repo, org_repo])].to_enum)
get :status, format: :json
@@ -125,7 +126,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
end
context 'when filtering' do
- let(:repo_2) { OpenStruct.new(login: 'emacs', full_name: 'asd/emacs', name: 'emacs', owner: { login: 'owner' }) }
+ let(:repo_2) { repo_fake.new(login: 'emacs', full_name: 'asd/emacs', name: 'emacs', owner: { login: 'owner' }) }
let(:project) { create(:project, import_type: provider, namespace: user.namespace, import_status: :finished, import_source: 'example/repo') }
let(:group) { create(:group) }
let(:repos) { [repo, repo_2, org_repo] }
@@ -133,7 +134,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
before do
group.add_owner(user)
client = stub_client(repos: repos, orgs: [org], org_repos: [org_repo])
- allow(client).to receive(:each_page).and_return([OpenStruct.new(objects: repos)].to_enum)
+ allow(client).to receive(:each_page).and_return([double('client', objects: repos)].to_enum)
# GitHub controller has filtering done using GitHub Search API
stub_feature_flags(remove_legacy_github_client: false)
end
@@ -172,7 +173,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
repos = [build(:project, name: 2, path: 'test')]
client = stub_client(repos: repos)
- allow(client).to receive(:each_page).and_return([OpenStruct.new(objects: repos)].to_enum)
+ allow(client).to receive(:each_page).and_return([double('client', objects: repos)].to_enum)
end
it 'does not raise an error' do
@@ -189,13 +190,14 @@ end
RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
let(:user) { create(:user) }
let(:provider_username) { user.username }
- let(:provider_user) { OpenStruct.new(login: provider_username) }
+ let(:provider_user) { double('user', login: provider_username) }
let(:project) { create(:project, import_type: provider, import_status: :finished, import_source: "#{provider_username}/vim") }
let(:provider_repo) do
- OpenStruct.new(
+ double(
+ 'provider',
name: 'vim',
full_name: "#{provider_username}/vim",
- owner: OpenStruct.new(login: provider_username)
+ owner: double('owner', login: provider_username)
)
end
@@ -265,10 +267,9 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
end
context "when the repository owner is not the provider user" do
- let(:other_username) { "someone_else" }
+ let(:provider_username) { "someone_else" }
before do
- provider_repo.owner = OpenStruct.new(login: other_username)
assign_session_token(provider)
end
@@ -277,8 +278,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
context "when the namespace is owned by the GitLab user" do
before do
- user.username = other_username
- user.save!
+ user.update!(username: provider_username)
end
it "takes the existing namespace" do
@@ -292,7 +292,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do
context "when the namespace is not owned by the GitLab user" do
it "creates a project using user's namespace" do
- create(:user, username: other_username)
+ create(:user, username: provider_username)
expect(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params)
diff --git a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
index 00a0fb7e4c5..3a7588a5cc9 100644
--- a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
@@ -50,7 +50,8 @@ RSpec.shared_examples Repositories::GitHttpController do
context 'with authorized user' do
before do
- request.headers.merge! auth_env(user.username, user.password, nil)
+ password = user.try(:password) || user.try(:token)
+ request.headers.merge! auth_env(user.username, password, nil)
end
it 'returns 200' do
@@ -71,9 +72,10 @@ RSpec.shared_examples Repositories::GitHttpController do
it 'adds user info to the logs' do
get :info_refs, params: params
- expect(log_data).to include('username' => user.username,
- 'user_id' => user.id,
- 'meta.user' => user.username)
+ user_log_data = { 'username' => user.username, 'user_id' => user.id }
+ user_log_data['meta.user'] = user.username if user.is_a?(User)
+
+ expect(log_data).to include(user_log_data)
end
end
end
diff --git a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
index 30914e61df0..ac7680f7ddb 100644
--- a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
@@ -6,15 +6,23 @@ RSpec.shared_examples 'tracking unique visits' do |method|
let(:request_params) { {} }
it 'tracks unique visit if the format is HTML' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter)
- .to receive(:track_event).with(target_id, values: kind_of(String))
+ ids = target_id.instance_of?(String) ? [target_id] : target_id
+
+ ids.each do |id|
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:track_event).with(id, values: kind_of(String))
+ end
get method, params: request_params, format: :html
end
it 'tracks unique visit if DNT is not enabled' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter)
- .to receive(:track_event).with(target_id, values: kind_of(String))
+ ids = target_id.instance_of?(String) ? [target_id] : target_id
+
+ ids.each do |id|
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:track_event).with(id, values: kind_of(String))
+ end
stub_do_not_track('0')
diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
index 30710e43357..1cb52c07069 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -299,7 +299,7 @@ RSpec.shared_examples 'wiki controller actions' do
expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true')
expect(response.cache_control[:public]).to be(false)
- expect(response.headers['Cache-Control']).to eq('private, no-store')
+ expect(response.headers['Cache-Control']).to eq('max-age=60, private')
end
end
end
diff --git a/spec/support/shared_examples/csp.rb b/spec/support/shared_examples/csp.rb
index c4a8c7df898..9143d0f4720 100644
--- a/spec/support/shared_examples/csp.rb
+++ b/spec/support/shared_examples/csp.rb
@@ -28,7 +28,7 @@ RSpec.shared_examples 'setting CSP' do |rule_name|
context 'when feature is enabled' do
it "appends to #{rule_name}" do
- is_expected.to eql("#{rule_name} #{default_csp_values} #{whitelisted_url}")
+ is_expected.to eql("#{rule_name} #{default_csp_values} #{allowlisted_url}")
end
end
@@ -46,7 +46,7 @@ RSpec.shared_examples 'setting CSP' do |rule_name|
context 'when feature is enabled' do
it "uses default-src values in #{rule_name}" do
- is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{whitelisted_url}")
+ is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{allowlisted_url}")
end
end
@@ -64,7 +64,7 @@ RSpec.shared_examples 'setting CSP' do |rule_name|
context 'when feature is enabled' do
it "uses default-src values in #{rule_name}" do
- is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{whitelisted_url}")
+ is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{allowlisted_url}")
end
end
diff --git a/spec/support/shared_examples/features/page_description_shared_examples.rb b/spec/support/shared_examples/features/page_description_shared_examples.rb
index 81653220b4c..e3ea36633d1 100644
--- a/spec/support/shared_examples/features/page_description_shared_examples.rb
+++ b/spec/support/shared_examples/features/page_description_shared_examples.rb
@@ -7,3 +7,13 @@ RSpec.shared_examples 'page meta description' do |expected_description|
end
end
end
+
+RSpec.shared_examples 'default brand title page meta description' do
+ include AppearancesHelper
+
+ it 'renders the page with description, og:description, and twitter:description meta tags with the default brand title', :aggregate_failures do
+ %w(name='description' property='og:description' property='twitter:description').each do |selector|
+ expect(page).to have_selector("meta[#{selector}][content='#{default_brand_title}']", visible: false)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
new file mode 100644
index 00000000000..345dfbce423
--- /dev/null
+++ b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'date sidebar widget' do
+ context 'editing due date' do
+ let(:due_date_value) { find('[data-testid="sidebar-due-date"] [data-testid="sidebar-date-value"]') }
+
+ around do |example|
+ freeze_time { example.run }
+ end
+
+ it 'displays "None" when there is no due date' do
+ expect(due_date_value.text).to have_content 'None'
+ end
+
+ it 'updates due date' do
+ page.within('[data-testid="sidebar-due-date"]') do
+ today = Date.today.day
+
+ click_button 'Edit'
+
+ click_button today.to_s
+
+ wait_for_requests
+
+ expect(page).to have_content(today.to_s(:medium))
+ expect(due_date_value.text).to have_content Time.current.strftime('%b %-d, %Y')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb
new file mode 100644
index 00000000000..da730240e8e
--- /dev/null
+++ b/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'milestone sidebar widget' do
+ context 'editing milestone' do
+ let_it_be(:milestone_expired) { create(:milestone, project: project, title: 'Foo - expired', due_date: 5.days.ago) }
+ let_it_be(:milestone_no_duedate) { create(:milestone, project: project, title: 'Foo - No due date') }
+ let_it_be(:milestone1) { create(:milestone, project: project, title: 'Milestone-1', due_date: 20.days.from_now) }
+ let_it_be(:milestone2) { create(:milestone, project: project, title: 'Milestone-2', due_date: 15.days.from_now) }
+ let_it_be(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) }
+
+ let(:milestone_widget) { find('[data-testid="sidebar-milestones"]') }
+
+ before do
+ within(milestone_widget) do
+ click_button 'Edit'
+ end
+
+ wait_for_all_requests
+ end
+
+ it 'shows milestones list in the dropdown' do
+ # 5 milestones + "No milestone" = 6 items
+ expect(milestone_widget.find('.gl-new-dropdown-contents')).to have_selector('li.gl-new-dropdown-item', count: 6)
+ end
+
+ it 'shows expired milestone at the bottom of the list and milestone due earliest at the top of the list', :aggregate_failures do
+ within(milestone_widget, '.gl-new-dropdown-contents') do
+ expect(page.find('li:last-child')).to have_content milestone_expired.title
+
+ [milestone3, milestone2, milestone1, milestone_no_duedate].each_with_index do |m, i|
+ expect(page.all('li.gl-new-dropdown-item')[i + 1]).to have_content m.title
+ end
+ end
+ end
+
+ it 'adds a milestone' do
+ within(milestone_widget) do
+ click_button milestone1.title
+
+ wait_for_requests
+
+ page.within('[data-testid="select-milestone"]') do
+ expect(page).to have_content(milestone1.title)
+ end
+ end
+ end
+
+ it 'removes a milestone' do
+ within(milestone_widget) do
+ click_button "No milestone"
+
+ wait_for_requests
+
+ page.within('[data-testid="select-milestone"]') do
+ expect(page).not_to have_content(milestone1.title)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb
index d509d124de0..615f568420e 100644
--- a/spec/support/shared_examples/features/sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb
@@ -5,6 +5,7 @@ RSpec.shared_examples 'issue boards sidebar' do
before do
first_card.click
+ wait_for_requests
end
it 'shows sidebar when clicking issue' do
@@ -41,6 +42,14 @@ RSpec.shared_examples 'issue boards sidebar' do
end
end
+ context 'editing issue milestone', :js do
+ it_behaves_like 'milestone sidebar widget'
+ end
+
+ context 'editing issue due date', :js do
+ it_behaves_like 'date sidebar widget'
+ end
+
context 'in notifications subscription' do
it 'displays notifications toggle', :aggregate_failures do
page.within('[data-testid="sidebar-notifications"]') do
diff --git a/spec/support/shared_examples/features/snippets_shared_examples.rb b/spec/support/shared_examples/features/snippets_shared_examples.rb
index bd1a67f3bb5..c402333107c 100644
--- a/spec/support/shared_examples/features/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/features/snippets_shared_examples.rb
@@ -20,7 +20,7 @@ RSpec.shared_examples 'paginated snippets' do |remote: false|
end
RSpec.shared_examples 'tabs with counts' do
- let(:tabs) { page.all('.snippet-scope-menu li') }
+ let(:tabs) { page.all('.js-snippets-nav-tabs li') }
it 'shows a tab for All snippets and count' do
tab = tabs[0]
diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
index 7ced8508a31..a456b76b324 100644
--- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
@@ -138,11 +138,26 @@ RSpec.shared_examples 'User updates wiki page' do
end
context 'when using the content editor' do
- before do
- click_button 'Use the new editor'
+ context 'with feature flag on' do
+ before do
+ click_button 'Edit rich text'
+ end
+
+ it_behaves_like 'edits content using the content editor'
end
- it_behaves_like 'edits content using the content editor'
+ context 'with feature flag off' do
+ before do
+ stub_feature_flags(wiki_switch_between_content_editor_raw_markdown: false)
+ visit(wiki_path(wiki))
+
+ click_link('Edit')
+
+ click_button 'Use the new editor'
+ end
+
+ it_behaves_like 'edits content using the content editor'
+ end
end
end
diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
index 96df5a5f972..eec911f3b6f 100644
--- a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
@@ -161,7 +161,7 @@ RSpec.shared_examples 'User views a wiki page' do
commit = wiki.commit
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
- expect(page).to have_content('by John Doe')
+ expect(page).to have_content('by Sidney Jones')
expect(page).to have_content('updated home')
expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions')
expect(page).to have_content('some link')
@@ -174,7 +174,7 @@ RSpec.shared_examples 'User views a wiki page' do
commit = wiki.commit('HEAD^')
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
- expect(page).to have_content('by John Doe')
+ expect(page).to have_content('by Sidney Jones')
expect(page).to have_content('updated home')
expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions')
expect(page).to have_content('some link')
@@ -188,7 +188,7 @@ RSpec.shared_examples 'User views a wiki page' do
commit = wiki.commit('HEAD^')
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
- expect(page).to have_content('by John Doe')
+ expect(page).to have_content('by Sidney Jones')
expect(page).to have_content('created page: home')
expect(page).to have_content('Showing 1 changed file with 4 additions and 0 deletions')
expect(page).to have_content('Look at this')
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
deleted file mode 100644
index 7707e79386c..00000000000
--- a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'resource mentions migration' do |migration_class, resource_class_name|
- it 'migrates resource mentions' do
- join = migration_class::JOIN
- conditions = migration_class::QUERY_CONDITIONS
- resource_class = "#{Gitlab::BackgroundMigration::UserMentions::Models}::#{resource_class_name}".constantize
-
- expect do
- subject.perform(resource_class_name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
- end.to change { user_mentions.count }.by(1)
-
- user_mention = user_mentions.last
- expect(user_mention.mentioned_users_ids.sort).to eq(mentioned_users.pluck(:id).sort)
- expect(user_mention.mentioned_groups_ids.sort).to eq([group.id])
- expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id)
-
- # check that performing the same job twice does not fail and does not change counts
- expect do
- subject.perform(resource_class_name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
- end.to change { user_mentions.count }.by(0)
- end
-end
-
-RSpec.shared_examples 'resource notes mentions migration' do |migration_class, resource_class_name|
- it 'migrates mentions from note' do
- join = migration_class::JOIN
- conditions = migration_class::QUERY_CONDITIONS
-
- # there are 5 notes for each noteable_type, but two do not have mentions and
- # another one's noteable_id points to an inexistent resource
- expect(notes.where(noteable_type: resource_class_name).count).to eq 5
- expect(user_mentions.count).to eq 0
-
- expect do
- subject.perform(resource_class_name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
- end.to change { user_mentions.count }.by(2)
-
- # check that the user_mention for regular note is created
- user_mention = user_mentions.first
- expect(Note.find(user_mention.note_id).system).to be false
- expect(user_mention.mentioned_users_ids.sort).to eq(users.pluck(:id).sort)
- expect(user_mention.mentioned_groups_ids.sort).to eq([group.id])
- expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id)
-
- # check that the user_mention for system note is created
- user_mention = user_mentions.second
- expect(Note.find(user_mention.note_id).system).to be true
- expect(user_mention.mentioned_users_ids.sort).to eq(users.pluck(:id).sort)
- expect(user_mention.mentioned_groups_ids.sort).to eq([group.id])
- expect(user_mention.mentioned_groups_ids.sort).not_to include(inaccessible_group.id)
-
- # check that performing the same job twice does not fail and does not change counts
- expect do
- subject.perform(resource_class_name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
- end.to change { user_mentions.count }.by(0)
- end
-end
-
-RSpec.shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes|
- before do
- stub_const("#{described_class.name}::BATCH_SIZE", 1)
- end
-
- it 'schedules background migrations' do
- Sidekiq::Testing.fake! do
- freeze_time do
- resource_count = is_for_notes ? Note.count : resource_class.count
- expect(resource_count).to eq 5
-
- migrate!
-
- migration = described_class::MIGRATION
- join = described_class::JOIN
- conditions = described_class::QUERY_CONDITIONS
- delay = described_class::DELAY
-
- expect(migration).to be_scheduled_delayed_migration(1 * delay, resource_class.name, join, conditions, is_for_notes, resource1.id, resource1.id)
- expect(migration).to be_scheduled_delayed_migration(2 * delay, resource_class.name, join, conditions, is_for_notes, resource2.id, resource2.id)
- expect(migration).to be_scheduled_delayed_migration(3 * delay, resource_class.name, join, conditions, is_for_notes, resource3.id, resource3.id)
- expect(BackgroundMigrationWorker.jobs.size).to eq 3
- end
- end
- end
-end
-
-RSpec.shared_examples 'resource migration not run' do |migration_class, resource_class_name|
- it 'does not migrate mentions' do
- join = migration_class::JOIN
- conditions = migration_class::QUERY_CONDITIONS
- resource_class = "#{Gitlab::BackgroundMigration::UserMentions::Models}::#{resource_class_name}".constantize
-
- expect do
- subject.perform(resource_class_name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
- end.to change { user_mentions.count }.by(0)
- end
-end
-
-RSpec.shared_examples 'resource notes migration not run' do |migration_class, resource_class_name|
- it 'does not migrate mentions' do
- join = migration_class::JOIN
- conditions = migration_class::QUERY_CONDITIONS
-
- expect do
- subject.perform(resource_class_name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
- end.to change { user_mentions.count }.by(0)
- end
-end
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
index bd8bdd70ce5..bce889b454d 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
@@ -9,7 +9,7 @@ RSpec.shared_examples_for 'value stream analytics event' do
it { expect(described_class.identifier).to be_a_kind_of(Symbol) }
it { expect(instance.object_type.ancestors).to include(ApplicationRecord) }
it { expect(instance).to respond_to(:timestamp_projection) }
- it { expect(instance).to respond_to(:markdown_description) }
+ it { expect(instance).to respond_to(:html_description) }
it { expect(instance.column_list).to be_a_kind_of(Array) }
describe '#apply_query_customization' do
diff --git a/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb
index 41d3d76b66b..03344584361 100644
--- a/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attributes, additional_attributes = []|
- let(:prohibited_attributes) { %i[remote_url my_attributes my_ids token my_id test] }
+ let(:prohibited_attributes) { %w[remote_url my_attributes my_ids token my_id test] }
let(:import_export_config) { Gitlab::ImportExport::Config.new.to_h }
let(:project_relation_factory) { Gitlab::ImportExport::Project::RelationFactory }
@@ -8,7 +8,7 @@ RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attrib
let(:relation_hash) { (permitted_attributes + prohibited_attributes).map(&:to_s).zip([]).to_h }
let(:relation_name) { project_relation_factory.overrides[relation_sym]&.to_sym || relation_sym }
let(:relation_class) { project_relation_factory.relation_class(relation_name) }
- let(:excluded_keys) { import_export_config.dig(:excluded_keys, relation_sym) || [] }
+ let(:excluded_keys) { (import_export_config.dig(:excluded_attributes, relation_sym) || []).map(&:to_s) }
let(:cleaned_hash) do
Gitlab::ImportExport::AttributeCleaner.new(
@@ -18,7 +18,7 @@ RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attrib
).clean
end
- let(:permitted_hash) { subject.permit(relation_sym, relation_hash) }
+ let(:permitted_hash) { subject.permit(relation_sym, relation_hash).transform_keys { |k| k.to_s } }
if described_class.new.permitted_attributes_defined?(relation_sym)
it 'contains only attributes that are defined as permitted in the import/export config' do
@@ -26,11 +26,11 @@ RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attrib
end
it 'does not contain attributes that would be cleaned with AttributeCleaner' do
- expect(cleaned_hash.keys + additional_attributes.to_a).to include(*permitted_hash.keys)
+ expect(cleaned_hash.keys + additional_attributes.to_a.map(&:to_s)).to include(*permitted_hash.keys)
end
it 'does not contain prohibited attributes that are not related to given relation' do
- expect(permitted_hash.keys).not_to include(*prohibited_attributes.map(&:to_s))
+ expect(permitted_hash.keys).not_to include(*prohibited_attributes)
end
else
it 'is disabled' do
diff --git a/spec/support/shared_examples/lib/gitlab/redis/multi_store_feature_flags_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/redis/multi_store_feature_flags_shared_examples.rb
new file mode 100644
index 00000000000..046c70bf779
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/redis/multi_store_feature_flags_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'multi store feature flags' do |use_primary_and_secondary_stores, use_primary_store_as_default|
+ context "with feature flag :#{use_primary_and_secondary_stores} is enabled" do
+ before do
+ stub_feature_flags(use_primary_and_secondary_stores => true)
+ end
+
+ it 'multi store is enabled' do
+ expect(subject.use_primary_and_secondary_stores?).to be true
+ end
+ end
+
+ context "with feature flag :#{use_primary_and_secondary_stores} is disabled" do
+ before do
+ stub_feature_flags(use_primary_and_secondary_stores => false)
+ end
+
+ it 'multi store is disabled' do
+ expect(subject.use_primary_and_secondary_stores?).to be false
+ end
+ end
+
+ context "with feature flag :#{use_primary_store_as_default} is enabled" do
+ before do
+ stub_feature_flags(use_primary_store_as_default => true)
+ end
+
+ it 'primary store is enabled' do
+ expect(subject.use_primary_store_as_default?).to be true
+ end
+ end
+
+ context "with feature flag :#{use_primary_store_as_default} is disabled" do
+ before do
+ stub_feature_flags(use_primary_store_as_default => false)
+ end
+
+ it 'primary store is disabled' do
+ expect(subject.use_primary_store_as_default?).to be false
+ end
+ end
+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 7ccd9533811..8f3a93de509 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
@@ -4,17 +4,12 @@ RSpec.shared_examples 'it has loose foreign keys' do
let(:factory_name) { nil }
let(:table_name) { described_class.table_name }
let(:connection) { described_class.connection }
-
- it 'includes the LooseForeignKey module' do
- expect(described_class.ancestors).to include(LooseForeignKey)
- end
-
- it 'responds to #loose_foreign_key_definitions' do
- expect(described_class).to respond_to(:loose_foreign_key_definitions)
- end
+ let(:fully_qualified_table_name) { "#{connection.current_schema}.#{table_name}" }
+ let(:deleted_records) { LooseForeignKeys::DeletedRecord.where(fully_qualified_table_name: fully_qualified_table_name) }
it 'has at least one loose foreign key definition' do
- expect(described_class.loose_foreign_key_definitions.size).to be > 0
+ definitions = Gitlab::Database::LooseForeignKeys.definitions_by_table[table_name]
+ expect(definitions.size).to be > 0
end
it 'has the deletion trigger present' do
@@ -32,9 +27,11 @@ RSpec.shared_examples 'it has loose foreign keys' do
it 'records record deletions' do
model = create(factory_name) # rubocop: disable Rails/SaveBang
- model.destroy!
- deleted_record = LooseForeignKeys::DeletedRecord.find_by(fully_qualified_table_name: "#{connection.current_schema}.#{table_name}", primary_key_value: model.id)
+ # using delete to avoid cross-database modification errors when associations with dependent option are present
+ model.delete
+
+ deleted_record = deleted_records.find_by(primary_key_value: model.id)
expect(deleted_record).not_to be_nil
end
@@ -42,11 +39,36 @@ RSpec.shared_examples 'it has loose foreign keys' do
it 'cleans up record deletions' do
model = create(factory_name) # rubocop: disable Rails/SaveBang
- expect { model.destroy! }.to change { LooseForeignKeys::DeletedRecord.count }.by(1)
+ expect { model.delete }.to change { deleted_records.count }.by(1)
LooseForeignKeys::ProcessDeletedRecordsService.new(connection: connection).execute
- expect(LooseForeignKeys::DeletedRecord.status_pending.count).to be(0)
- expect(LooseForeignKeys::DeletedRecord.status_processed.count).to be(1)
+ expect(deleted_records.status_pending.count).to be(0)
+ expect(deleted_records.status_processed.count).to be(1)
+ end
+end
+
+RSpec.shared_examples 'cleanup by a loose foreign key' do
+ 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 }
+ end
+
+ def find_model
+ model.class.find_by(id: model.id)
+ end
+
+ it 'deletes the model' do
+ parent.delete
+
+ expect(find_model).to be_present
+
+ LooseForeignKeys::ProcessDeletedRecordsService.new(connection: model.connection).execute
+
+ if foreign_key_definition.on_delete.eql?(:async_delete)
+ expect(find_model).not_to be_present
+ else
+ expect(find_model[foreign_key_definition.column]).to eq(nil)
+ end
end
end
diff --git a/spec/support/shared_examples/mailers/notify_shared_examples.rb b/spec/support/shared_examples/mailers/notify_shared_examples.rb
index e1f7a9030e2..20ed380fb18 100644
--- a/spec/support/shared_examples/mailers/notify_shared_examples.rb
+++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb
@@ -161,6 +161,12 @@ RSpec.shared_examples 'it should not have Gmail Actions links' do
end
end
+RSpec.shared_examples 'it should show Gmail Actions Join now link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text('Join now') }
+end
+
RSpec.shared_examples 'it should show Gmail Actions View Issue link' do
it_behaves_like 'it should have Gmail Actions links'
diff --git a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
index c06083ba952..6e8c340582a 100644
--- a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
+++ b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
@@ -1,7 +1,11 @@
# frozen_string_literal: true
RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
- let(:db_config_name) { ::Gitlab::Database.db_config_names.first }
+ let(:db_config_name) do
+ db_config_name = ::Gitlab::Database.db_config_names.first
+ db_config_name += "_replica" if db_role == :secondary
+ db_config_name
+ end
let(:expected_payload_defaults) do
result = {}
@@ -39,15 +43,15 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_write_count: record_write_query ? 1 : 0,
db_cached_count: record_cached_query ? 1 : 0,
db_primary_cached_count: record_cached_query ? 1 : 0,
- "db_primary_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
+ "db_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
db_primary_count: record_query ? 1 : 0,
- "db_primary_#{db_config_name}_count": record_query ? 1 : 0,
+ "db_#{db_config_name}_count": record_query ? 1 : 0,
db_primary_duration_s: record_query ? 0.002 : 0.0,
- "db_primary_#{db_config_name}_duration_s": record_query ? 0.002 : 0.0,
+ "db_#{db_config_name}_duration_s": record_query ? 0.002 : 0.0,
db_primary_wal_count: record_wal_query ? 1 : 0,
- "db_primary_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
+ "db_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
db_primary_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
- "db_primary_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
+ "db_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
})
elsif db_role == :replica
transform_hash(expected_payload_defaults, {
@@ -55,15 +59,15 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_write_count: record_write_query ? 1 : 0,
db_cached_count: record_cached_query ? 1 : 0,
db_replica_cached_count: record_cached_query ? 1 : 0,
- "db_replica_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
+ "db_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
db_replica_count: record_query ? 1 : 0,
- "db_replica_#{db_config_name}_count": record_query ? 1 : 0,
+ "db_#{db_config_name}_count": record_query ? 1 : 0,
db_replica_duration_s: record_query ? 0.002 : 0.0,
- "db_replica_#{db_config_name}_duration_s": record_query ? 0.002 : 0.0,
+ "db_#{db_config_name}_duration_s": record_query ? 0.002 : 0.0,
db_replica_wal_count: record_wal_query ? 1 : 0,
- "db_replica_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
+ "db_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
db_replica_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
- "db_replica_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
+ "db_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
})
else
transform_hash(expected_payload_defaults, {
@@ -71,15 +75,15 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_write_count: record_write_query ? 1 : 0,
db_cached_count: record_cached_query ? 1 : 0,
db_primary_cached_count: 0,
- "db_primary_#{db_config_name}_cached_count": 0,
+ "db_#{db_config_name}_cached_count": 0,
db_primary_count: 0,
- "db_primary_#{db_config_name}_count": 0,
+ "db_#{db_config_name}_count": 0,
db_primary_duration_s: 0.0,
- "db_primary_#{db_config_name}_duration_s": 0.0,
+ "db_#{db_config_name}_duration_s": 0.0,
db_primary_wal_count: 0,
- "db_primary_#{db_config_name}_wal_count": 0,
+ "db_#{db_config_name}_wal_count": 0,
db_primary_wal_cached_count: 0,
- "db_primary_#{db_config_name}_wal_cached_count": 0
+ "db_#{db_config_name}_wal_cached_count": 0
})
end
@@ -105,7 +109,11 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
end
RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do |db_role|
- let(:db_config_name) { ::Gitlab::Database.db_config_name(ApplicationRecord.retrieve_connection) }
+ let(:db_config_name) do
+ db_config_name = ::Gitlab::Database.db_config_names.first
+ db_config_name += "_replica" if db_role == :secondary
+ db_config_name
+ end
it 'increments only db counters' do
if record_query
diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
index 03f565e0aac..fe85daa7235 100644
--- a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
+++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
@@ -80,15 +80,22 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
it 'calls InternalId.generate_next and sets internal id attribute' do
iid = rand(1..1000)
- expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid)
+ # Need to do this before evaluating instance otherwise it gets set
+ # already in factory
+ allow(InternalId).to receive(:generate_next).and_return(iid)
+
subject
expect(read_internal_id).to eq(iid)
+
+ expect(InternalId).to have_received(:generate_next).with(instance, scope_attrs, usage, any_args)
end
it 'does not overwrite an existing internal id' do
write_internal_id(4711)
- expect { subject }.not_to change { read_internal_id }
+ allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/347091') do
+ expect { subject }.not_to change { read_internal_id }
+ end
end
context 'when the instance has an internal ID set' do
@@ -101,6 +108,7 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
.to receive(:track_greatest)
.with(instance, scope_attrs, usage, internal_id, any_args)
.and_return(internal_id)
+
subject
end
end
@@ -110,7 +118,11 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
context 'when the internal id has been changed' do
context 'when the internal id is automatically set' do
it 'clears it on the instance' do
- expect_iid_to_be_set_and_rollback
+ write_internal_id(nil)
+
+ allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/347091') do
+ expect_iid_to_be_set_and_rollback
+ end
expect(read_internal_id).to be_nil
end
@@ -120,7 +132,9 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true|
it 'does not clear it on the instance' do
write_internal_id(100)
- expect_iid_to_be_set_and_rollback
+ allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/347091') do
+ expect_iid_to_be_set_and_rollback
+ end
expect(read_internal_id).not_to be_nil
end
diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
index 72659dd5f3b..e6b270c6188 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -71,7 +71,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
it "does not call #{integration_name} API" do
result = subject.execute(sample_data)
- expect(result).to be(false)
+ expect(result).to be_falsy
expect(WebMock).not_to have_requested(:post, webhook_url)
end
end
@@ -113,7 +113,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
context "with protected branch" do
before do
- create(:protected_branch, project: project, name: "a-protected-branch")
+ create(:protected_branch, :create_branch_on_repository, project: project, name: "a-protected-branch")
end
let(:sample_data) do
@@ -309,7 +309,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
context "with protected branch" do
before do
- create(:protected_branch, project: project, name: "a-protected-branch")
+ create(:protected_branch, :create_branch_on_repository, project: project, name: "a-protected-branch")
end
let(:sample_data) do
@@ -355,5 +355,11 @@ RSpec.shared_examples "chat integration" do |integration_name|
end
end
end
+
+ context 'deployment events' do
+ let(:sample_data) { Gitlab::DataBuilder::Deployment.build(create(:deployment), Time.now) }
+
+ it_behaves_like "untriggered #{integration_name} integration"
+ end
end
end
diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
index 2d4c0b60f2b..ad15f82be5e 100644
--- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
@@ -305,7 +305,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name|
context 'on a protected branch' do
before do
- create(:protected_branch, project: project, name: 'a-protected-branch')
+ create(:protected_branch, :create_branch_on_repository, project: project, name: 'a-protected-branch')
end
let(:data) do
@@ -347,7 +347,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name|
context 'on a protected branch with protected branches defined using wildcards' do
before do
- create(:protected_branch, project: project, name: '*-stable')
+ create(:protected_branch, :create_branch_on_repository, repository_branch_name: '1-stable', project: project, name: '*-stable')
end
let(:data) do
@@ -560,7 +560,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name|
context 'on a protected branch' do
before do
- create(:protected_branch, project: project, name: 'a-protected-branch')
+ create(:protected_branch, :create_branch_on_repository, project: project, name: 'a-protected-branch')
end
let(:pipeline) do
@@ -590,7 +590,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name|
context 'on a protected branch with protected branches defined usin wildcards' do
before do
- create(:protected_branch, project: project, name: '*-stable')
+ create(:protected_branch, :create_branch_on_repository, repository_branch_name: '1-stable', project: project, name: '*-stable')
end
let(:pipeline) do
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index a2909c66e22..d5d137922eb 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -301,10 +301,6 @@ RSpec.shared_examples_for "member creation" do
end
context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
- before do
- stub_experiments(invite_members_for_task: true)
- end
-
it 'creates a member_task with the correct attributes', :aggregate_failures do
task_project = source.is_a?(Group) ? create(:project, group: source) : source
described_class.new(source, user, :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id).execute
@@ -397,10 +393,6 @@ RSpec.shared_examples_for "bulk member creation" do
end
context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
- before do
- stub_experiments(invite_members_for_task: true)
- end
-
it 'creates a member_task with the correct attributes', :aggregate_failures do
task_project = source.is_a?(Group) ? create(:project, group: source) : source
members = described_class.add_users(source, [user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id)
diff --git a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb
index f08ee820463..23026167b19 100644
--- a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb
+++ b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb
@@ -23,7 +23,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
let_it_be(:component_file_other_file_md5, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, file_md5: 'other_md5') }
let_it_be(:component_file_other_file_sha256, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, file_sha256: 'other_sha256') }
let_it_be(:component_file_other_container, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component2_1, architecture: architecture2_1) }
- let_it_be_with_refind(:component_file_with_file_type_source) { create("debian_#{container_type}_component_file", :source, component: component1_1) }
+ let_it_be_with_refind(:component_file_with_file_type_sources) { create("debian_#{container_type}_component_file", :sources, component: component1_1) }
let_it_be(:component_file_with_file_type_di_packages, freeze: can_freeze) { create("debian_#{container_type}_component_file", :di_packages, component: component1_1, architecture: architecture1_1) }
subject { component_file_with_architecture }
@@ -43,8 +43,8 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
it { is_expected.to belong_to(:architecture).class_name("Packages::Debian::#{container_type.capitalize}Architecture").inverse_of(:files) }
end
- context 'with :source file_type' do
- subject { component_file_with_file_type_source }
+ context 'with :sources file_type' do
+ subject { component_file_with_file_type_sources }
it { is_expected.to belong_to(:architecture).class_name("Packages::Debian::#{container_type.capitalize}Architecture").inverse_of(:files).optional }
end
@@ -66,8 +66,8 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
it { is_expected.to validate_presence_of(:architecture) }
end
- context 'with :source file_type' do
- subject { component_file_with_file_type_source }
+ context 'with :sources file_type' do
+ subject { component_file_with_file_type_sources }
it { is_expected.to validate_absence_of(:architecture) }
end
@@ -135,10 +135,10 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
end
describe '.with_file_type' do
- subject { described_class.with_file_type(:source) }
+ subject { described_class.with_file_type(:sources) }
it do
- expect(subject.to_a).to contain_exactly(component_file_with_file_type_source)
+ expect(subject.to_a).to contain_exactly(component_file_with_file_type_sources)
end
end
@@ -214,9 +214,9 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
end
context 'with a Source file_type' do
- subject { component_file_with_file_type_source.relative_path }
+ subject { component_file_with_file_type_sources.relative_path }
- it { is_expected.to eq("#{component1_1.name}/source/Source") }
+ it { is_expected.to eq("#{component1_1.name}/source/Sources") }
end
context 'with a DI Packages file_type' do
diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb
index ac6a843663f..73e22b97abc 100644
--- a/spec/support/shared_examples/namespaces/traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_examples.rb
@@ -205,6 +205,58 @@ RSpec.shared_examples 'namespace traversal' do
end
end
+ shared_examples '#ancestors_upto' do
+ let(:parent) { create(:group) }
+ let(:child) { create(:group, parent: parent) }
+ let(:child2) { create(:group, parent: child) }
+
+ it 'returns all ancestors when no namespace is given' do
+ expect(child2.ancestors_upto).to contain_exactly(child, parent)
+ end
+
+ it 'includes ancestors upto but excluding the given ancestor' do
+ expect(child2.ancestors_upto(parent)).to contain_exactly(child)
+ end
+
+ context 'with asc hierarchy_order' do
+ it 'returns the correct ancestor ids' do
+ expect(child2.ancestors_upto(hierarchy_order: :asc)).to eq([child, parent])
+ end
+ end
+
+ context 'with desc hierarchy_order' do
+ it 'returns the correct ancestor ids' do
+ expect(child2.ancestors_upto(hierarchy_order: :desc)).to eq([parent, child])
+ end
+ end
+
+ describe '#recursive_self_and_ancestor_ids' do
+ it 'is equivalent to ancestors_upto' do
+ recursive_result = child2.recursive_ancestors_upto(parent)
+ linear_result = child2.ancestors_upto(parent)
+ expect(linear_result).to match_array recursive_result
+ end
+
+ it 'makes a recursive query' do
+ expect { child2.recursive_ancestors_upto.try(:load) }.to make_queries_matching(/WITH RECURSIVE/)
+ end
+ end
+ end
+
+ describe '#ancestors_upto' do
+ context 'with use_traversal_ids_for_ancestors_upto enabled' do
+ include_examples '#ancestors_upto'
+ end
+
+ context 'with use_traversal_ids_for_ancestors_upto disabled' do
+ before do
+ stub_feature_flags(use_traversal_ids_for_ancestors_upto: false)
+ end
+
+ include_examples '#ancestors_upto'
+ end
+ end
+
describe '#descendants' do
let!(:another_group) { create(:group) }
let!(:another_group_nested) { create(:group, parent: another_group) }
diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
index 4c09c1c2a3b..3d52ed30c62 100644
--- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
@@ -213,6 +213,12 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { is_expected.to contain_exactly(deep_nested_group_1, deep_nested_group_2) }
end
+
+ context 'with offset and limit' do
+ subject { described_class.where(id: [group_1, group_2]).offset(1).limit(1).self_and_descendants }
+
+ it { is_expected.to contain_exactly(group_2, nested_group_2, deep_nested_group_2) }
+ end
end
describe '.self_and_descendants' do
@@ -242,6 +248,19 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { is_expected.to contain_exactly(deep_nested_group_1.id, deep_nested_group_2.id) }
end
+
+ context 'with offset and limit' do
+ subject do
+ described_class
+ .where(id: [group_1, group_2])
+ .limit(1)
+ .offset(1)
+ .self_and_descendant_ids
+ .pluck(:id)
+ end
+
+ it { is_expected.to contain_exactly(group_2.id, nested_group_2.id, deep_nested_group_2.id) }
+ end
end
describe '.self_and_descendant_ids' do
diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
index e45be21f152..9f4fdcf7ba1 100644
--- a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
@@ -173,3 +173,65 @@ RSpec.shared_examples 'rejects Composer access with unknown project id' do
end
end
end
+
+RSpec.shared_examples 'Composer access with deploy tokens' do
+ shared_examples 'a deploy token for Composer GET requests' do
+ context 'with deploy token headers' do
+ let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ before do
+ group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { basic_auth_header(deploy_token.username, 'bar') }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+ end
+ end
+
+ context 'group deploy token' do
+ let(:deploy_token) { deploy_token_for_group }
+
+ it_behaves_like 'a deploy token for Composer GET requests'
+ end
+
+ context 'project deploy token' do
+ let(:deploy_token) { deploy_token_for_project }
+
+ it_behaves_like 'a deploy token for Composer GET requests'
+ end
+end
+
+RSpec.shared_examples 'Composer publish with deploy tokens' do
+ shared_examples 'a deploy token for Composer publish requests' do
+ let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { basic_auth_header(deploy_token.username, 'bar') }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+ end
+
+ context 'group deploy token' do
+ let(:deploy_token) { deploy_token_for_group }
+
+ it_behaves_like 'a deploy token for Composer publish requests'
+ end
+
+ context 'group deploy token' do
+ let(:deploy_token) { deploy_token_for_project }
+
+ it_behaves_like 'a deploy token for Composer publish requests'
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
index 20606ae942d..71f3a0235be 100644
--- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
@@ -178,6 +178,54 @@ RSpec.shared_examples 'rejects invalid recipe' do
end
end
+RSpec.shared_examples 'handling empty values for username and channel' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:recipe_path) { "#{package.name}/#{package.version}/#{package_username}/#{channel}" }
+
+ where(:username, :channel, :status) do
+ 'username' | 'channel' | :ok
+ 'username' | '_' | :bad_request
+ '_' | 'channel' | :bad_request_or_not_found
+ '_' | '_' | :ok_or_not_found
+ end
+
+ with_them do
+ let(:package_username) do
+ if username == 'username'
+ package.conan_metadatum.package_username
+ else
+ username
+ end
+ end
+
+ before do
+ project.add_maintainer(user) # avoid any permission issue
+ end
+
+ it 'returns the correct status code' do |example|
+ project_level = example.full_description.include?('api/v4/projects')
+
+ expected_status = case status
+ when :ok_or_not_found
+ project_level ? :ok : :not_found
+ when :bad_request_or_not_found
+ project_level ? :bad_request : :not_found
+ else
+ status
+ end
+
+ if expected_status == :ok
+ package.conan_metadatum.update!(package_username: package_username, package_channel: channel)
+ end
+
+ subject
+
+ expect(response).to have_gitlab_http_status(expected_status)
+ end
+ end
+end
+
RSpec.shared_examples 'rejects invalid file_name' do |invalid_file_name|
let(:file_name) { invalid_file_name }
@@ -300,6 +348,7 @@ RSpec.shared_examples 'recipe snapshot endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'empty recipe for not found package'
+ it_behaves_like 'handling empty values for username and channel'
context 'with existing package' do
it 'returns a hash of files with their md5 hashes' do
@@ -324,6 +373,7 @@ RSpec.shared_examples 'package snapshot endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'empty recipe for not found package'
+ it_behaves_like 'handling empty values for username and channel'
context 'with existing package' do
it 'returns a hash of md5 values for the files' do
@@ -344,12 +394,14 @@ RSpec.shared_examples 'recipe download_urls endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'recipe download_urls'
+ it_behaves_like 'handling empty values for username and channel'
end
RSpec.shared_examples 'package download_urls endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'package download_urls'
+ it_behaves_like 'handling empty values for username and channel'
end
RSpec.shared_examples 'recipe upload_urls endpoint' do
@@ -362,6 +414,7 @@ RSpec.shared_examples 'recipe upload_urls endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid upload_url params'
+ it_behaves_like 'handling empty values for username and channel'
it 'returns a set of upload urls for the files requested' do
subject
@@ -423,6 +476,7 @@ RSpec.shared_examples 'package upload_urls endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid upload_url params'
+ it_behaves_like 'handling empty values for username and channel'
it 'returns a set of upload urls for the files requested' do
expected_response = {
@@ -458,6 +512,7 @@ RSpec.shared_examples 'delete package endpoint' do
let(:recipe_path) { package.conan_recipe_path }
it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'handling empty values for username and channel'
it 'returns unauthorized for users without valid permission' do
subject
@@ -568,12 +623,14 @@ RSpec.shared_examples 'recipe file download endpoint' do
it_behaves_like 'a public project with packages'
it_behaves_like 'an internal project with packages'
it_behaves_like 'a private project with packages'
+ it_behaves_like 'handling empty values for username and channel'
end
RSpec.shared_examples 'package file download endpoint' do
it_behaves_like 'a public project with packages'
it_behaves_like 'an internal project with packages'
it_behaves_like 'a private project with packages'
+ it_behaves_like 'handling empty values for username and channel'
context 'tracking the conan_package.tgz download' do
let(:package_file) { package.package_files.find_by(file_name: ::Packages::Conan::FileMetadatum::PACKAGE_BINARY) }
@@ -598,6 +655,7 @@ RSpec.shared_examples 'workhorse authorize endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'workhorse authorization'
+ it_behaves_like 'handling empty values for username and channel'
end
RSpec.shared_examples 'workhorse recipe file upload endpoint' do
@@ -619,6 +677,7 @@ RSpec.shared_examples 'workhorse recipe file upload endpoint' do
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'uploads a package file'
it_behaves_like 'creates build_info when there is a job'
+ it_behaves_like 'handling empty values for username and channel'
end
RSpec.shared_examples 'workhorse package file upload endpoint' do
@@ -640,6 +699,7 @@ RSpec.shared_examples 'workhorse package file upload endpoint' do
it_behaves_like 'rejects invalid file_name', 'conaninfo.txttest'
it_behaves_like 'uploads a package file'
it_behaves_like 'creates build_info when there is a job'
+ it_behaves_like 'handling empty values for username and channel'
context 'tracking the conan_package.tgz upload' do
let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
index 62dbac3fd4d..8bffd1f71e9 100644
--- a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
@@ -18,19 +18,19 @@ RSpec.shared_examples 'snippet edit usage data counters' do
end
end
- context 'when user is not sessionless' do
+ context 'when user is not sessionless', :clean_gitlab_redis_sessions do
before do
session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d')
session_hash = { 'warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]] }
- Gitlab::Redis::SharedState.with do |redis|
+ Gitlab::Redis::Sessions.with do |redis|
redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash))
end
cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id
end
- it 'tracks usage data actions', :clean_gitlab_redis_shared_state do
+ it 'tracks usage data actions', :clean_gitlab_redis_sessions do
expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_snippet_editor_edit_action)
post_graphql_mutation(mutation)
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
index 367c6d4fa3a..882c79cb03f 100644
--- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
@@ -55,7 +55,7 @@ RSpec.shared_examples 'group and project packages query' do
end
it 'deals with metadata' do
- expect(target_shas).to contain_exactly(composer_metadatum.target_sha)
+ expect(target_shas.compact).to contain_exactly(composer_metadatum.target_sha)
end
it 'returns the count of the packages' do
diff --git a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
index 673d7741017..c5e5803c0a7 100644
--- a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
+++ b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
@@ -28,4 +28,34 @@ RSpec.shared_examples 'issuable participants endpoint' do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'with a confidential note' do
+ let!(:note) do
+ create(
+ :note,
+ :confidential,
+ project: project,
+ noteable: entity,
+ author: create(:user)
+ )
+ end
+
+ it 'returns a full list of participants' do
+ get api("/projects/#{project.id}/#{area}/#{entity.iid}/participants", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ participant_ids = json_response.map { |el| el['id'] }
+ expect(participant_ids).to match_array([entity.author_id, note.author_id])
+ end
+
+ context 'when user cannot see a confidential note' do
+ it 'returns a limited list of participants' do
+ get api("/projects/#{project.id}/#{area}/#{entity.iid}/participants", create(:user))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ participant_ids = json_response.map { |el| el['id'] }
+ expect(participant_ids).to match_array([entity.author_id])
+ end
+ end
+ end
end
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 19677e92001..8d6d85732be 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
@@ -41,19 +41,6 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
# query count can slightly change between the examples so we're using a custom threshold
expect { get(url, headers: headers) }.not_to exceed_query_limit(control).with_threshold(4)
end
-
- context 'with packages_npm_abbreviated_metadata disabled' do
- before do
- stub_feature_flags(packages_npm_abbreviated_metadata: false)
- end
-
- it 'calls the presenter without including metadata' do
- expect(::Packages::Npm::PackagePresenter)
- .to receive(:new).with(anything, anything, include_metadata: false).and_call_original
-
- subject
- end
- end
end
shared_examples 'reject metadata request' do |status:|
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 878cbc10a24..6568d51b90e 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
@@ -391,7 +391,7 @@ RSpec.shared_examples 'rejects nuget access with invalid target id' do
context 'with a target id with invalid integers' do
using RSpec::Parameterized::TableSyntax
- let(:target) { OpenStruct.new(id: id) }
+ let(:target) { double(id: id) }
where(:id, :status) do
'/../' | :bad_request
@@ -411,7 +411,7 @@ end
RSpec.shared_examples 'rejects nuget access with unknown target id' do
context 'with an unknown target' do
- let(:target) { OpenStruct.new(id: 1234567890) }
+ let(:target) { double(id: 1234567890) }
context 'as anonymous' do
it_behaves_like 'rejects nuget packages access', :anonymous, :unauthorized
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
index 06c51add438..aff086d1ba3 100644
--- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -346,7 +346,8 @@ RSpec.shared_examples 'a pypi user namespace endpoint' do
end
with_them do
- let_it_be_with_reload(:group) { create(:namespace) }
+ # only groups are supported, so this "group" is actually the wrong namespace type
+ let_it_be_with_reload(:group) { create(:user_namespace) }
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) }
before do
diff --git a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb
index c979fdc2bb0..7fd20fc3909 100644
--- a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb
@@ -126,7 +126,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
SHA256: #{package_files[4].file_sha256}
EOF
- expected_main_source_content = <<~EOF
+ expected_main_sources_content = <<~EOF
Package: #{package.name}
Binary: sample-dev, libsample0, sample-udeb
Version: #{package.version}
@@ -158,7 +158,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
check_component_file(current_time.round, 'main', :di_packages, 'amd64', expected_main_amd64_di_content)
check_component_file(current_time.round, 'main', :di_packages, 'arm64', nil)
- check_component_file(current_time.round, 'main', :source, nil, expected_main_source_content)
+ check_component_file(current_time.round, 'main', :sources, nil, expected_main_sources_content)
check_component_file(current_time.round, 'contrib', :packages, 'all', nil)
check_component_file(current_time.round, 'contrib', :packages, 'amd64', nil)
@@ -168,7 +168,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
check_component_file(current_time.round, 'contrib', :di_packages, 'amd64', nil)
check_component_file(current_time.round, 'contrib', :di_packages, 'arm64', nil)
- check_component_file(current_time.round, 'contrib', :source, nil, nil)
+ check_component_file(current_time.round, 'contrib', :sources, nil, nil)
main_amd64_size = expected_main_amd64_content.length
main_amd64_md5sum = Digest::MD5.hexdigest(expected_main_amd64_content)
@@ -182,9 +182,9 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
main_amd64_di_md5sum = Digest::MD5.hexdigest(expected_main_amd64_di_content)
main_amd64_di_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_di_content)
- main_source_size = expected_main_source_content.length
- main_source_md5sum = Digest::MD5.hexdigest(expected_main_source_content)
- main_source_sha256 = Digest::SHA256.hexdigest(expected_main_source_content)
+ main_sources_size = expected_main_sources_content.length
+ main_sources_md5sum = Digest::MD5.hexdigest(expected_main_sources_content)
+ main_sources_sha256 = Digest::SHA256.hexdigest(expected_main_sources_content)
expected_release_content = <<~EOF
Codename: unstable
@@ -199,14 +199,14 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-amd64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-arm64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-arm64/Packages
- d41d8cd98f00b204e9800998ecf8427e 0 contrib/source/Source
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/source/Sources
d41d8cd98f00b204e9800998ecf8427e 0 main/binary-all/Packages
d41d8cd98f00b204e9800998ecf8427e 0 main/debian-installer/binary-all/Packages
#{main_amd64_md5sum} #{main_amd64_size} main/binary-amd64/Packages
#{main_amd64_di_md5sum} #{main_amd64_di_size} main/debian-installer/binary-amd64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 main/binary-arm64/Packages
d41d8cd98f00b204e9800998ecf8427e 0 main/debian-installer/binary-arm64/Packages
- #{main_source_md5sum} #{main_source_size} main/source/Source
+ #{main_sources_md5sum} #{main_sources_size} main/source/Sources
SHA256:
#{contrib_all_sha256} #{contrib_all_size} contrib/binary-all/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages
@@ -214,14 +214,14 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-arm64/Packages
- e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Source
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Sources
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-all/Packages
#{main_amd64_sha256} #{main_amd64_size} main/binary-amd64/Packages
#{main_amd64_di_sha256} #{main_amd64_di_size} main/debian-installer/binary-amd64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-arm64/Packages
- #{main_source_sha256} #{main_source_size} main/source/Source
+ #{main_sources_sha256} #{main_sources_size} main/source/Sources
EOF
check_release_files(expected_release_content)
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
new file mode 100644
index 00000000000..0d3e158d358
--- /dev/null
+++ b/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb
@@ -0,0 +1,212 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'it runs background migration jobs' do |tracking_database, metric_name|
+ describe 'defining the job attributes' do
+ it 'defines the data_consistency as always' do
+ expect(described_class.get_data_consistency).to eq(:always)
+ end
+
+ it 'defines the retry count in sidekiq_options' do
+ expect(described_class.sidekiq_options['retry']).to eq(3)
+ end
+
+ it 'defines the feature_category as database' do
+ expect(described_class.get_feature_category).to eq(:database)
+ end
+
+ it 'defines the urgency as throttled' do
+ expect(described_class.get_urgency).to eq(:throttled)
+ end
+
+ it 'defines the loggable_arguments' do
+ expect(described_class.loggable_arguments).to match_array([0, 1])
+ end
+ end
+
+ describe '.tracking_database' do
+ it 'does not raise an error' do
+ expect { described_class.tracking_database }.not_to raise_error
+ end
+
+ it 'overrides the method to return the tracking database' do
+ expect(described_class.tracking_database).to eq(tracking_database)
+ end
+ end
+
+ describe '.unhealthy_metric_name' do
+ it 'does not raise an error' do
+ expect { described_class.unhealthy_metric_name }.not_to raise_error
+ end
+
+ it 'overrides the method to return the unhealthy metric name' do
+ expect(described_class.unhealthy_metric_name).to eq(metric_name)
+ end
+ end
+
+ describe '.minimum_interval' do
+ it 'returns 2 minutes' do
+ expect(described_class.minimum_interval).to eq(2.minutes.to_i)
+ end
+ end
+
+ describe '#perform' do
+ let(:worker) { described_class.new }
+
+ before do
+ allow(worker).to receive(:jid).and_return(1)
+ allow(worker).to receive(:always_perform?).and_return(false)
+
+ allow(Postgresql::ReplicationSlot).to receive(:lag_too_great?).and_return(false)
+ end
+
+ it 'performs jobs using the coordinator for the worker' do
+ expect_next_instance_of(Gitlab::BackgroundMigration::JobCoordinator) do |coordinator|
+ allow(coordinator).to receive(:with_shared_connection).and_yield
+
+ expect(coordinator.worker_class).to eq(described_class)
+ expect(coordinator).to receive(:perform).with('Foo', [10, 20])
+ end
+
+ worker.perform('Foo', [10, 20])
+ end
+
+ context 'when lease can be obtained' do
+ let(:coordinator) { double('job coordinator') }
+
+ before do
+ allow(Gitlab::BackgroundMigration).to receive(:coordinator_for_database)
+ .with(tracking_database)
+ .and_return(coordinator)
+
+ allow(coordinator).to receive(:with_shared_connection).and_yield
+ end
+
+ it 'sets up the shared connection before checking replication' do
+ expect(coordinator).to receive(:with_shared_connection).and_yield.ordered
+ expect(Postgresql::ReplicationSlot).to receive(:lag_too_great?).and_return(false).ordered
+
+ expect(coordinator).to receive(:perform).with('Foo', [10, 20])
+
+ worker.perform('Foo', [10, 20])
+ end
+
+ it 'performs a background migration' do
+ expect(coordinator).to receive(:perform).with('Foo', [10, 20])
+
+ worker.perform('Foo', [10, 20])
+ end
+
+ context 'when lease_attempts is 1' do
+ it 'performs a background migration' do
+ expect(coordinator).to receive(:perform).with('Foo', [10, 20])
+
+ worker.perform('Foo', [10, 20], 1)
+ end
+ end
+
+ it 'can run scheduled job and retried job concurrently' do
+ expect(coordinator)
+ .to receive(:perform)
+ .with('Foo', [10, 20])
+ .exactly(2).time
+
+ worker.perform('Foo', [10, 20])
+ worker.perform('Foo', [10, 20], described_class::MAX_LEASE_ATTEMPTS - 1)
+ end
+
+ it 'sets the class that will be executed as the caller_id' do
+ expect(coordinator).to receive(:perform) do
+ expect(Gitlab::ApplicationContext.current).to include('meta.caller_id' => 'Foo')
+ end
+
+ worker.perform('Foo', [10, 20])
+ end
+ end
+
+ context 'when lease not obtained (migration of same class was performed recently)' do
+ let(:timeout) { described_class.minimum_interval }
+ let(:lease_key) { "#{described_class.name}:Foo" }
+ let(:coordinator) { double('job coordinator') }
+
+ before do
+ allow(Gitlab::BackgroundMigration).to receive(:coordinator_for_database)
+ .with(tracking_database)
+ .and_return(coordinator)
+
+ allow(coordinator).to receive(:with_shared_connection).and_yield
+
+ expect(coordinator).not_to receive(:perform)
+
+ Gitlab::ExclusiveLease.new(lease_key, timeout: timeout).try_obtain
+ end
+
+ it 'reschedules the migration and decrements the lease_attempts' do
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(a_kind_of(Numeric), 'Foo', [10, 20], 4)
+
+ worker.perform('Foo', [10, 20], 5)
+ end
+
+ context 'when lease_attempts is 1' do
+ let(:lease_key) { "#{described_class.name}:Foo:retried" }
+
+ it 'reschedules the migration and decrements the lease_attempts' do
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(a_kind_of(Numeric), 'Foo', [10, 20], 0)
+
+ worker.perform('Foo', [10, 20], 1)
+ end
+ end
+
+ context 'when lease_attempts is 0' do
+ let(:lease_key) { "#{described_class.name}:Foo:retried" }
+
+ it 'gives up performing the migration' do
+ expect(described_class).not_to receive(:perform_in)
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: 'Foo',
+ message: 'Job could not get an exclusive lease after several tries. Giving up.',
+ job_id: 1)
+
+ worker.perform('Foo', [10, 20], 0)
+ end
+ end
+ end
+
+ context 'when database is not healthy' do
+ before do
+ expect(Postgresql::ReplicationSlot).to receive(:lag_too_great?).and_return(true)
+ end
+
+ it 'reschedules a migration if the database is not healthy' do
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(a_kind_of(Numeric), 'Foo', [10, 20], 4)
+
+ worker.perform('Foo', [10, 20])
+ end
+
+ it 'increments the unhealthy counter' do
+ counter = Gitlab::Metrics.counter(metric_name, 'msg')
+
+ expect(described_class).to receive(:perform_in)
+
+ expect { worker.perform('Foo', [10, 20]) }.to change { counter.get }.by(1)
+ end
+
+ context 'when lease_attempts is 0' do
+ it 'gives up performing the migration' do
+ expect(described_class).not_to receive(:perform_in)
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: 'Foo',
+ message: 'Database was unhealthy after several tries. Giving up.',
+ job_id: 1)
+
+ worker.perform('Foo', [10, 20], 0)
+ end
+ end
+ end
+ end
+end