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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-11-17 14:33:21 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-11-17 14:33:21 +0300
commit7021455bd1ed7b125c55eb1b33c5a01f2bc55ee0 (patch)
tree5bdc2229f5198d516781f8d24eace62fc7e589e9 /spec/support
parent185b095e93520f96e9cfc31d9c3e69b498cdab7c (diff)
Add latest changes from gitlab-org/gitlab@15-6-stable-eev15.6.0-rc42
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/database/query_recorder.rb9
-rw-r--r--spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb4
-rw-r--r--spec/support/google_api/cloud_platform_helpers.rb2
-rw-r--r--spec/support/helpers/bare_repo_operations.rb44
-rw-r--r--spec/support/helpers/ci/template_helpers.rb45
-rw-r--r--spec/support/helpers/content_security_policy_helpers.rb20
-rw-r--r--spec/support/helpers/database/multiple_databases_helpers.rb (renamed from spec/support/database/multiple_databases.rb)75
-rw-r--r--spec/support/helpers/features/access_token_helpers.rb19
-rw-r--r--spec/support/helpers/features/releases_helpers.rb2
-rw-r--r--spec/support/helpers/filter_spec_helper.rb5
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb4
-rw-r--r--spec/support/helpers/full_name_helper.rb9
-rw-r--r--spec/support/helpers/git_helpers.rb11
-rw-r--r--spec/support/helpers/graphql_helpers.rb2
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb25
-rw-r--r--spec/support/helpers/navbar_structure_helper.rb6
-rw-r--r--spec/support/helpers/reference_parser_helpers.rb4
-rw-r--r--spec/support/helpers/search_helpers.rb4
-rw-r--r--spec/support/helpers/stub_configuration.rb4
-rw-r--r--spec/support/helpers/stub_feature_flags.rb4
-rw-r--r--spec/support/helpers/test_env.rb42
-rw-r--r--spec/support/helpers/usage_data_helpers.rb12
-rw-r--r--spec/support/matchers/exceed_query_limit.rb2
-rw-r--r--spec/support/matchers/graphql_matchers.rb6
-rw-r--r--spec/support/migration.rb36
-rw-r--r--spec/support/models/ci/partitioning_testing/cascade_check.rb (renamed from spec/support/models/partitionable_check.rb)25
-rw-r--r--spec/support/models/ci/partitioning_testing/partition_identifiers.rb13
-rw-r--r--spec/support/models/ci/partitioning_testing/rspec_hooks.rb19
-rw-r--r--spec/support/models/ci/partitioning_testing/schema_helpers.rb86
-rw-r--r--spec/support/multiple_databases.rb25
-rw-r--r--spec/support/rate_limiter.rb7
-rw-r--r--spec/support/redis.rb6
-rw-r--r--spec/support/rspec.rb3
-rw-r--r--spec/support/rspec_order_todo.yml27
-rw-r--r--spec/support/services/issuable_update_service_shared_examples.rb40
-rw-r--r--spec/support/services/migrate_to_ghost_user_service_shared_examples.rb3
-rw-r--r--spec/support/shared_contexts/container_repositories_shared_context.rb1
-rw-r--r--spec/support/shared_contexts/graphql/resolvers/runners_resolver_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/jobs/handling_retried_jobs_shared_context.rb26
-rw-r--r--spec/support/shared_contexts/lib/sbom/package_url_shared_contexts.rb26
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb5
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb1
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb4
-rw-r--r--spec/support/shared_contexts/policies/project_policy_table_shared_context.rb57
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb4
-rw-r--r--spec/support/shared_examples/ci/retryable_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/controllers/preferred_language_switcher_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/features/access_tokens_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/features/confidential_notes_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/packages_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/runners_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb304
-rw-r--r--spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/features/variable_list_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/finders/issues_finder_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/mutations/incident_management/timeline_events_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/graphql/notes_creation_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/lib/cache_helpers_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/lib/email/email_shared_examples.rb140
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb46
-rw-r--r--spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/sentry/client_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/mailers/notify_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb150
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/models/concerns/limitable_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb2
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb84
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb75
-rw-r--r--spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb119
-rw-r--r--spec/support/shared_examples/requests/api/discussions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb170
-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/graphql/projects/branch_protections/access_level_request_examples.rb77
-rw-r--r--spec/support/shared_examples/requests/api/issues_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/members_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/alert_management_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/base_rpm_service_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb112
-rw-r--r--spec/support/shared_examples/services/merge_status_updated_trigger_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/uploaders/object_storage_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb56
-rw-r--r--spec/support/sidekiq_middleware.rb9
-rw-r--r--spec/support/webmock.rb7
106 files changed, 2089 insertions, 596 deletions
diff --git a/spec/support/database/query_recorder.rb b/spec/support/database/query_recorder.rb
new file mode 100644
index 00000000000..1050120e528
--- /dev/null
+++ b/spec/support/database/query_recorder.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ # Truncate the query_recorder log file before starting the suite
+ config.before(:suite) do
+ log_path = Rails.root.join(Gitlab::Database::QueryAnalyzers::QueryRecorder::LOG_FILE)
+ File.write(log_path, '') if File.exist?(log_path)
+ end
+end
diff --git a/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb b/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb
index e02bf66507a..e9a13f7bf63 100644
--- a/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb
+++ b/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb
@@ -10,8 +10,8 @@ RSpec.shared_examples 'a correct instrumented metric value' do |params|
end
before do
- if described_class.respond_to?(:relation) && described_class.relation.respond_to?(:connection)
- allow(described_class.relation.connection).to receive(:transaction_open?).and_return(false)
+ if metric.respond_to?(:relation, true) && metric.send(:relation).respond_to?(:connection)
+ allow(metric.send(:relation).connection).to receive(:transaction_open?).and_return(false)
end
end
diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb
index 840f948e377..b9752577c76 100644
--- a/spec/support/google_api/cloud_platform_helpers.rb
+++ b/spec/support/google_api/cloud_platform_helpers.rb
@@ -157,7 +157,7 @@ module GoogleApi
def cloud_platform_projects_billing_info_body(project_id, billing_enabled)
{
"name": "projects/#{project_id}/billingInfo",
- "projectId": "#{project_id}",
+ "projectId": project_id.to_s,
"billingAccountName": "account-name",
"billingEnabled": billing_enabled
}
diff --git a/spec/support/helpers/bare_repo_operations.rb b/spec/support/helpers/bare_repo_operations.rb
deleted file mode 100644
index e29e12a15f6..00000000000
--- a/spec/support/helpers/bare_repo_operations.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-require 'zlib'
-
-class BareRepoOperations
- include Gitlab::Popen
-
- def initialize(path_to_repo)
- @path_to_repo = path_to_repo
- end
-
- def commit_tree(tree_id, msg, parent: Gitlab::Git::EMPTY_TREE_ID)
- commit_tree_args = ['commit-tree', tree_id, '-m', msg]
- commit_tree_args += ['-p', parent] unless parent == Gitlab::Git::EMPTY_TREE_ID
- commit_id = execute(commit_tree_args)
-
- commit_id[0]
- end
-
- private
-
- def execute(args, allow_failure: false)
- output, status = popen(base_args + args, nil) do |stdin|
- yield stdin if block_given?
- end
-
- unless status == 0
- if allow_failure
- return []
- else
- raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}"
- end
- end
-
- output.split("\n")
- end
-
- def base_args
- [
- Gitlab.config.git.bin_path,
- "--git-dir=#{@path_to_repo}"
- ]
- end
-end
diff --git a/spec/support/helpers/ci/template_helpers.rb b/spec/support/helpers/ci/template_helpers.rb
index 2e9b6f748cd..2cdd242ac22 100644
--- a/spec/support/helpers/ci/template_helpers.rb
+++ b/spec/support/helpers/ci/template_helpers.rb
@@ -5,6 +5,51 @@ module Ci
def template_registry_host
'registry.gitlab.com'
end
+
+ def public_image_exist?(registry, repository, image)
+ public_image_manifest(registry, repository, image).present?
+ end
+
+ def public_image_manifest(registry, repository, reference)
+ token = public_image_repository_token(registry, repository)
+
+ response = with_net_connect_allowed do
+ Gitlab::HTTP.get(image_manifest_url(registry, repository, reference),
+ headers: { 'Authorization' => "Bearer #{token}" })
+ end
+
+ return unless response.success?
+
+ Gitlab::Json.parse(response.body)
+ end
+
+ def public_image_repository_token(registry, repository)
+ @public_image_repository_tokens ||= {}
+ @public_image_repository_tokens[[registry, repository]] ||=
+ begin
+ response = with_net_connect_allowed do
+ Gitlab::HTTP.get(image_manifest_url(registry, repository, 'latest'))
+ end
+
+ return unless response.unauthorized?
+
+ www_authenticate = response.headers['www-authenticate']
+ return unless www_authenticate
+
+ realm, service, scope = www_authenticate.split(',').map { |s| s[/\w+="(.*)"/, 1] }
+ token_response = with_net_connect_allowed do
+ Gitlab::HTTP.get(realm, query: { service: service, scope: scope })
+ end
+
+ return unless token_response.success?
+
+ token_response['token']
+ end
+ end
+
+ def image_manifest_url(registry, repository, reference)
+ "#{registry}/v2/#{repository}/manifests/#{reference}"
+ end
end
end
diff --git a/spec/support/helpers/content_security_policy_helpers.rb b/spec/support/helpers/content_security_policy_helpers.rb
index c9f15e65c74..230075ead70 100644
--- a/spec/support/helpers/content_security_policy_helpers.rb
+++ b/spec/support/helpers/content_security_policy_helpers.rb
@@ -1,20 +1,14 @@
# frozen_string_literal: true
module ContentSecurityPolicyHelpers
- # Expecting 2 calls to current_content_security_policy by default, once for
- # the call that's being tested and once for the call in ApplicationController
- def setup_csp_for_controller(controller_class, times = 2)
+ # Expecting 2 calls to current_content_security_policy by default:
+ # 1. call that's being tested
+ # 2. call in ApplicationController
+ def setup_csp_for_controller(controller_class, csp = ActionDispatch::ContentSecurityPolicy.new, times: 2)
expect_next_instance_of(controller_class) do |controller|
- expect(controller).to receive(:current_content_security_policy)
- .and_return(ActionDispatch::ContentSecurityPolicy.new).exactly(times).times
- end
- end
-
- # Expecting 2 calls to current_content_security_policy by default, once for
- # the call that's being tested and once for the call in ApplicationController
- def setup_existing_csp_for_controller(controller_class, csp, times = 2)
- expect_next_instance_of(controller_class) do |controller|
- expect(controller).to receive(:current_content_security_policy).and_return(csp).exactly(times).times
+ expect(controller)
+ .to receive(:current_content_security_policy).exactly(times).times
+ .and_return(csp)
end
end
end
diff --git a/spec/support/database/multiple_databases.rb b/spec/support/helpers/database/multiple_databases_helpers.rb
index b6341c2caec..16f5168ca29 100644
--- a/spec/support/database/multiple_databases.rb
+++ b/spec/support/helpers/database/multiple_databases_helpers.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Database
- module MultipleDatabases
+ module MultipleDatabasesHelpers
def skip_if_multiple_databases_not_setup
skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
end
@@ -52,17 +52,17 @@ module Database
#
# rubocop:disable Database/MultipleDatabases
def with_reestablished_active_record_base(reconnect: true)
- connection_classes = ActiveRecord::Base.connection_handler.connection_pool_names.map(&:constantize).to_h do |klass|
- [klass, klass.connection_db_config]
- end
+ connection_classes = ActiveRecord::Base
+ .connection_handler
+ .connection_pool_names
+ .map(&:constantize)
+ .index_with(&:connection_db_config)
original_handler = ActiveRecord::Base.connection_handler
new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
ActiveRecord::Base.connection_handler = new_handler
- if reconnect
- connection_classes.each { |klass, db_config| klass.establish_connection(db_config) }
- end
+ connection_classes.each { |klass, db_config| klass.establish_connection(db_config) } if reconnect
yield
ensure
@@ -95,9 +95,12 @@ module Database
module ActiveRecordBaseEstablishConnection
def establish_connection(*args)
# rubocop:disable Database/MultipleDatabases
- if connected? && connection&.transaction_open? && ActiveRecord::Base.connection_handler == ActiveRecord::Base.default_connection_handler
- raise "Cannot re-establish '#{self}.establish_connection' within an open transaction (#{connection&.open_transactions.to_i}). " \
- "Use `with_reestablished_active_record_base` instead or add `:reestablished_active_record_base` to rspec context."
+ if connected? &&
+ connection&.transaction_open? &&
+ ActiveRecord::Base.connection_handler == ActiveRecord::Base.default_connection_handler
+ raise "Cannot re-establish '#{self}.establish_connection' within an open transaction " \
+ "(#{connection&.open_transactions.to_i}). Use `with_reestablished_active_record_base` " \
+ "instead or add `:reestablished_active_record_base` to rspec context."
end
# rubocop:enable Database/MultipleDatabases
@@ -106,56 +109,4 @@ module Database
end
end
-RSpec.configure do |config|
- # Ensure database versions are memoized to prevent query counts from
- # being affected by version checks. Note that
- # Gitlab::Database.check_postgres_version_and_print_warning is called
- # at startup, but that generates its own
- # `Gitlab::Database::Reflection` so the result is not memoized by
- # callers of `ApplicationRecord.database.version`, such as
- # `Gitlab::Database::AsWithMaterialized.materialized_supported?`.
- # TODO This can be removed once https://gitlab.com/gitlab-org/gitlab/-/issues/325639 is completed.
- [ApplicationRecord, ::Ci::ApplicationRecord].each { |record| record.database.version }
-
- config.around(:each, :reestablished_active_record_base) do |example|
- with_reestablished_active_record_base(reconnect: example.metadata.fetch(:reconnect, true)) do
- example.run
- end
- end
-
- config.around(:each, :add_ci_connection) do |example|
- with_added_ci_connection do
- example.run
- end
- end
-
- config.append_after(:context, :migration) do
- recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables
- end
-
- config.around(:each, :migration) do |example|
- self.class.use_transactional_tests = false
-
- migration_schema = example.metadata[:migration]
- migration_schema = :gitlab_main if migration_schema == true
- base_model = Gitlab::Database.schemas_to_base_models.fetch(migration_schema).first
-
- # Migration require an `ActiveRecord::Base` to point to desired database
- if base_model != ActiveRecord::Base
- with_reestablished_active_record_base do
- reconfigure_db_connection(
- model: ActiveRecord::Base,
- config_model: base_model
- )
-
- example.run
- end
- else
- example.run
- end
-
- self.class.use_transactional_tests = true
- end
-end
-
ActiveRecord::Base.singleton_class.prepend(::Database::ActiveRecordBaseEstablishConnection) # rubocop:disable Database/MultipleDatabases
diff --git a/spec/support/helpers/features/access_token_helpers.rb b/spec/support/helpers/features/access_token_helpers.rb
new file mode 100644
index 00000000000..f4bdb70c160
--- /dev/null
+++ b/spec/support/helpers/features/access_token_helpers.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+module Spec
+ module Support
+ module Helpers
+ module AccessTokenHelpers
+ def active_access_tokens
+ find("[data-testid='active-tokens']")
+ end
+
+ def created_access_token
+ within('[data-testid=access-token-section]') do
+ find('[data-testid=toggle-visibility-button]').click
+ find_field('new-access-token').value
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/features/releases_helpers.rb b/spec/support/helpers/features/releases_helpers.rb
index 9cce9c4882d..a24b99bbe61 100644
--- a/spec/support/helpers/features/releases_helpers.rb
+++ b/spec/support/helpers/features/releases_helpers.rb
@@ -39,7 +39,7 @@ module Spec
wait_for_all_requests
- click_button("#{branch_name}")
+ click_button(branch_name.to_s)
end
end
diff --git a/spec/support/helpers/filter_spec_helper.rb b/spec/support/helpers/filter_spec_helper.rb
index ca844b33ba8..7beed9c7755 100644
--- a/spec/support/helpers/filter_spec_helper.rb
+++ b/spec/support/helpers/filter_spec_helper.rb
@@ -90,10 +90,11 @@ module FilterSpecHelper
#
# Returns a String
def invalidate_reference(reference)
- if reference =~ /\A(.+)?[^\d]\d+\z/
+ case reference
+ when /\A(.+)?[^\d]\d+\z/
# Integer-based reference with optional project prefix
reference.gsub(/\d+\z/) { |i| i.to_i + 10_000 }
- elsif reference =~ /\A(.+@)?(\h{7,40}\z)/
+ when /\A(.+@)?(\h{7,40}\z)/
# SHA-based reference with optional prefix
reference.gsub(/\h{7,40}\z/) { |v| v.reverse }
else
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index 93122ca3d0c..677cea7b804 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -254,6 +254,10 @@ module FilteredSearchHelpers
expect(page).to have_css '.gl-filtered-search-token', text: "Assignee = #{value}"
end
+ def expect_unioned_assignee_token(value)
+ expect(page).to have_css '.gl-filtered-search-token', text: "Assignee is one of #{value}"
+ end
+
def expect_author_token(value)
expect(page).to have_css '.gl-filtered-search-token', text: "Author = #{value}"
end
diff --git a/spec/support/helpers/full_name_helper.rb b/spec/support/helpers/full_name_helper.rb
new file mode 100644
index 00000000000..a41c0da74d4
--- /dev/null
+++ b/spec/support/helpers/full_name_helper.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module FullNameHelper
+ def full_name(first_name, last_name)
+ "#{first_name} #{last_name}"
+ end
+end
+
+FullNameHelper.prepend_mod
diff --git a/spec/support/helpers/git_helpers.rb b/spec/support/helpers/git_helpers.rb
deleted file mode 100644
index 72bba419116..00000000000
--- a/spec/support/helpers/git_helpers.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module GitHelpers
- def rugged_repo(repository)
- path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- File.join(TestEnv.repos_path, repository.disk_path + '.git')
- end
-
- Rugged::Repository.new(path)
- end
-end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index b2fc6ae3286..bd0efc96bd8 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -378,7 +378,7 @@ module GraphqlHelpers
def field_with_params(name, attributes = {})
namerized = GraphqlHelpers.fieldnamerize(name.to_s)
- return "#{namerized}" if attributes.blank?
+ return namerized.to_s if attributes.blank?
field_params = if attributes.is_a?(Hash)
"(#{attributes_to_graphql(attributes)})"
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 912e7d24b25..72524453f34 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -142,6 +142,29 @@ module KubernetesHelpers
WebMock.stub_request(method, ingresses_url).to_return(response)
end
+ def stub_server_min_version_failed_request
+ WebMock.stub_request(:get, service.api_url + '/version').to_return(
+ status: [500, "Internal Server Error"],
+ body: {}.to_json)
+ end
+
+ def stub_server_min_version(min_version)
+ response = kube_response({
+ "major": "1", # not used, just added here to be a bit more realistic purposes
+ "minor": min_version.to_s
+ })
+
+ WebMock.stub_request( :get, service.api_url + '/version')
+ .with(
+ headers: {
+ 'Accept' => '*/*',
+ 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
+ 'Authorization' => 'Bearer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ 'User-Agent' => 'Ruby'
+ })
+ .to_return(response)
+ end
+
def stub_kubeclient_knative_services(options = {})
namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : ""
@@ -537,7 +560,7 @@ module KubernetesHelpers
},
"spec" => {
"containers" => [
- { "name" => "#{container_name}" },
+ { "name" => container_name.to_s },
{ "name" => "#{container_name}-1" }
]
},
diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb
index b44552d6479..e1ed3ffacec 100644
--- a/spec/support/helpers/navbar_structure_helper.rb
+++ b/spec/support/helpers/navbar_structure_helper.rb
@@ -90,7 +90,11 @@ module NavbarStructureHelper
_('Kubernetes'),
new_nav_item: {
nav_item: _('Observability'),
- nav_sub_items: []
+ nav_sub_items: [
+ _('Dashboards'),
+ _('Explore'),
+ _('Manage Dashboards')
+ ]
}
)
end
diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb
index b9796ebbe62..370dedabd9b 100644
--- a/spec/support/helpers/reference_parser_helpers.rb
+++ b/spec/support/helpers/reference_parser_helpers.rb
@@ -12,14 +12,14 @@ module ReferenceParserHelpers
end
RSpec.shared_examples 'no project N+1 queries' do
- it 'avoids N+1 queries in #nodes_visible_to_user' do
+ it 'avoids N+1 queries in #nodes_visible_to_user', :use_sql_query_cache do
context = Banzai::RenderContext.new(project, user)
request = lambda do |links|
described_class.new(context).nodes_visible_to_user(user, links)
end
- control = ActiveRecord::QueryRecorder.new { request.call(control_links) }
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { request.call(control_links) }
create(:group_member, group: project.group) if project.group
create(:project_member, project: project)
diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb
index 581ef07752e..7d0f8c09933 100644
--- a/spec/support/helpers/search_helpers.rb
+++ b/spec/support/helpers/search_helpers.rb
@@ -33,13 +33,13 @@ module SearchHelpers
end
def select_search_scope(scope)
- page.within '.search-filter' do
+ page.within '[data-testid="search-filter"]' do
click_link scope
end
end
def has_search_scope?(scope)
- page.within '.search-filter' do
+ page.within '[data-testid="search-filter"]' do
has_link?(scope)
end
end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index f41457d2420..24c768258a1 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -38,6 +38,10 @@ module StubConfiguration
allow(Rails.application.routes).to receive(:default_url_options).and_return(url_options)
end
+ def stub_dependency_proxy_setting(messages)
+ allow(Gitlab.config.dependency_proxy).to receive_messages(to_settings(messages))
+ end
+
def stub_gravatar_setting(messages)
allow(Gitlab.config.gravatar).to receive_messages(to_settings(messages))
end
diff --git a/spec/support/helpers/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb
index f1654e55b7e..e301e29afc2 100644
--- a/spec/support/helpers/stub_feature_flags.rb
+++ b/spec/support/helpers/stub_feature_flags.rb
@@ -37,6 +37,10 @@ module StubFeatureFlags
# Enable `ci_live_trace` feature flag only on the specified projects.
def stub_feature_flags(features)
features.each do |feature_name, actors|
+ unless Feature::Definition.get(feature_name)
+ ActiveSupport::Deprecation.warn "Invalid Feature Flag #{feature_name} stubbed"
+ end
+
# Remove feature flag overwrite
feature = Feature.get(feature_name) # rubocop:disable Gitlab/AvoidFeatureGet
feature.remove
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index c58353558df..e1b461cf37e 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -11,6 +11,8 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'signed-commits' => 'c7794c1',
+ 'gpg-signed' => '8a852d5',
+ 'x509-signed' => 'a4df3c8',
'not-merged-branch' => 'b83d6e3',
'branch-merged' => '498214d',
'empty-branch' => '7efb185',
@@ -43,7 +45,7 @@ module TestEnv
'video' => '8879059',
'crlf-diff' => '5938907',
'conflict-start' => '824be60',
- 'conflict-resolvable' => '1450cd6',
+ 'conflict-resolvable' => '1450cd639e0bc6721eb02800169e464f212cde06',
'conflict-binary-file' => '259a6fb',
'conflict-contains-conflict-markers' => '78a3086',
'conflict-missing-side' => 'eb227b3',
@@ -282,15 +284,30 @@ module TestEnv
unless File.directory?(repo_path)
start = Time.now
system(*%W(#{Gitlab.config.git.bin_path} clone --quiet -- #{clone_url} #{repo_path}))
- system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} remote remove origin))
puts "==> #{repo_path} set up in #{Time.now - start} seconds...\n"
end
- set_repo_refs(repo_path, refs)
+ create_bundle = !File.file?(repo_bundle_path)
- unless File.file?(repo_bundle_path)
+ unless set_repo_refs(repo_path, refs)
+ # Prefer not to fetch over the network. Only fetch when we have failed to
+ # set all the required local branches. This would happen when a new
+ # branch is added to BRANCH_SHA, in which case we want to update
+ # everything.
+ unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin))
+ raise 'Could not fetch test seed repository.'
+ end
+
+ unless set_repo_refs(repo_path, refs)
+ raise "Could not update test seed repository, please delete #{repo_path} and try again"
+ end
+
+ create_bundle = true
+ end
+
+ if create_bundle
start = Time.now
- system(git_env, *%W(#{Gitlab.config.git.bin_path} -C #{repo_path} bundle create #{repo_bundle_path} --all))
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} -C #{repo_path} bundle create #{repo_bundle_path} --exclude refs/remotes/* --all))
puts "==> #{repo_bundle_path} generated in #{Time.now - start} seconds...\n"
end
end
@@ -392,20 +409,13 @@ module TestEnv
end
def set_repo_refs(repo_path, branch_sha)
- instructions = branch_sha.map { |branch, sha| "update refs/heads/#{branch}\x00#{sha}\x00" }.join("\x00") << "\x00"
- update_refs = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
- reset = proc do
- Dir.chdir(repo_path) do
- IO.popen(update_refs, "w") { |io| io.write(instructions) }
- $?.success?
+ IO.popen(%W[#{Gitlab.config.git.bin_path} -C #{repo_path} update-ref --stdin -z], "w") do |io|
+ branch_sha.each do |branch, sha|
+ io.write("update refs/heads/#{branch}\x00#{sha}\x00\x00")
end
end
- # Try to reset without fetching to avoid using the network.
- unless reset.call
- raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin))
- raise "Could not update test seed repository, please delete #{repo_path} and try again" unless reset.call
- end
+ $?.success?
end
def component_timed_setup(component, install_dir:, version:, task:, fresh_install: true, task_args: [])
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index b4f0cbd8527..92a946db337 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -67,24 +67,12 @@ module UsageDataHelpers
projects_with_repositories_enabled
projects_with_error_tracking_enabled
projects_with_enabled_alert_integrations
- projects_with_expiration_policy_enabled
- projects_with_expiration_policy_enabled_with_keep_n_unset
- projects_with_expiration_policy_enabled_with_keep_n_set_to_1
- projects_with_expiration_policy_enabled_with_keep_n_set_to_5
- projects_with_expiration_policy_enabled_with_keep_n_set_to_10
- projects_with_expiration_policy_enabled_with_keep_n_set_to_25
- projects_with_expiration_policy_enabled_with_keep_n_set_to_50
projects_with_expiration_policy_enabled_with_older_than_unset
projects_with_expiration_policy_enabled_with_older_than_set_to_7d
projects_with_expiration_policy_enabled_with_older_than_set_to_14d
projects_with_expiration_policy_enabled_with_older_than_set_to_30d
projects_with_expiration_policy_enabled_with_older_than_set_to_60d
projects_with_expiration_policy_enabled_with_older_than_set_to_90d
- projects_with_expiration_policy_enabled_with_cadence_set_to_1d
- projects_with_expiration_policy_enabled_with_cadence_set_to_7d
- projects_with_expiration_policy_enabled_with_cadence_set_to_14d
- projects_with_expiration_policy_enabled_with_cadence_set_to_1month
- projects_with_expiration_policy_enabled_with_cadence_set_to_3month
projects_with_terraform_reports
projects_with_terraform_states
pages_domains
diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb
index bfcaf9552b3..6d7658b7c33 100644
--- a/spec/support/matchers/exceed_query_limit.rb
+++ b/spec/support/matchers/exceed_query_limit.rb
@@ -333,7 +333,7 @@ RSpec::Matchers.define :issue_same_number_of_queries_as do
or_fewer_msg = "or fewer" if @or_fewer
threshold_msg = "(+/- #{threshold})" unless threshold == 0
- ["#{expected_count}", or_fewer_msg, threshold_msg].compact.join(' ')
+ [expected_count.to_s, or_fewer_msg, threshold_msg].compact.join(' ')
end
def skip_cached
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index db7d4269945..155a6dba52c 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -169,7 +169,11 @@ RSpec::Matchers.define :have_graphql_type do |expected, opts = {}|
include GraphQLTypeHelpers
match do |object|
- expect(object.type).to eq(nullified(expected, opts[:null]))
+ if object.type.list?
+ expect(object.type.unwrap).to eq(nullified(expected, opts[:null]))
+ else
+ expect(object.type).to eq(nullified(expected, opts[:null]))
+ end
end
failure_message do |object|
diff --git a/spec/support/migration.rb b/spec/support/migration.rb
index 490aa836d74..2a69630a29a 100644
--- a/spec/support/migration.rb
+++ b/spec/support/migration.rb
@@ -16,14 +16,42 @@ RSpec.configure do |config|
schema_migrate_down!
end
+ config.after(:context, :migration) do
+ Gitlab::CurrentSettings.clear_in_memory_application_settings!
+ end
+
+ config.append_after(:context, :migration) do
+ recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables
+ end
+
+ config.around(:each, :migration) do |example|
+ self.class.use_transactional_tests = false
+
+ migration_schema = example.metadata[:migration]
+ migration_schema = :gitlab_main if migration_schema == true
+ base_model = Gitlab::Database.schemas_to_base_models.fetch(migration_schema).first
+
+ # Migration require an `ActiveRecord::Base` to point to desired database
+ if base_model != ActiveRecord::Base
+ with_reestablished_active_record_base do
+ reconfigure_db_connection(
+ model: ActiveRecord::Base,
+ config_model: base_model
+ )
+
+ example.run
+ end
+ else
+ example.run
+ end
+
+ self.class.use_transactional_tests = true
+ end
+
# Each example may call `migrate!`, so we must ensure we are migrated down every time
config.before(:each, :migration) do
use_fake_application_settings
schema_migrate_down!
end
-
- config.after(:context, :migration) do
- Gitlab::CurrentSettings.clear_in_memory_application_settings!
- end
end
diff --git a/spec/support/models/partitionable_check.rb b/spec/support/models/ci/partitioning_testing/cascade_check.rb
index 2c09c1b3408..f553a47ef4f 100644
--- a/spec/support/models/partitionable_check.rb
+++ b/spec/support/models/ci/partitioning_testing/cascade_check.rb
@@ -10,37 +10,18 @@ module PartitioningTesting
def check_partition_cascade_value
raise 'Partition value not found' unless partition_scope_value
- raise 'Default value detected' if partition_id == 100
return if partition_id == partition_scope_value
raise "partition_id was expected to equal #{partition_scope_value} but it was #{partition_id}."
end
end
-
- module DefaultPartitionValue
- extend ActiveSupport::Concern
-
- class_methods do
- def current_partition_value
- current = super
-
- if current == 100
- 54321
- else
- current
- end
- end
- end
- end
end
Ci::Partitionable::Testing::PARTITIONABLE_MODELS.each do |klass|
+ next if klass == 'Ci::Pipeline'
+
model = klass.safe_constantize
- if klass == 'Ci::Pipeline'
- model.prepend(PartitioningTesting::DefaultPartitionValue)
- else
- model.include(PartitioningTesting::CascadeCheck)
- end
+ model.include(PartitioningTesting::CascadeCheck)
end
diff --git a/spec/support/models/ci/partitioning_testing/partition_identifiers.rb b/spec/support/models/ci/partitioning_testing/partition_identifiers.rb
new file mode 100644
index 00000000000..aa091095fb6
--- /dev/null
+++ b/spec/support/models/ci/partitioning_testing/partition_identifiers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Ci
+ module PartitioningTesting
+ module PartitionIdentifiers
+ module_function
+
+ def ci_testing_partition_id
+ 99999
+ end
+ end
+ end
+end
diff --git a/spec/support/models/ci/partitioning_testing/rspec_hooks.rb b/spec/support/models/ci/partitioning_testing/rspec_hooks.rb
new file mode 100644
index 00000000000..39b15ba8721
--- /dev/null
+++ b/spec/support/models/ci/partitioning_testing/rspec_hooks.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ config.include Ci::PartitioningTesting::PartitionIdentifiers
+
+ config.around(:each, :ci_partitionable) do |example|
+ Ci::PartitioningTesting::SchemaHelpers.with_routing_tables do
+ example.run
+ end
+ end
+
+ config.before(:all) do
+ Ci::PartitioningTesting::SchemaHelpers.setup
+ end
+
+ config.after(:all) do
+ Ci::PartitioningTesting::SchemaHelpers.teardown
+ end
+end
diff --git a/spec/support/models/ci/partitioning_testing/schema_helpers.rb b/spec/support/models/ci/partitioning_testing/schema_helpers.rb
new file mode 100644
index 00000000000..712178710da
--- /dev/null
+++ b/spec/support/models/ci/partitioning_testing/schema_helpers.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+module Ci
+ module PartitioningTesting
+ module SchemaHelpers
+ DEFAULT_PARTITION = 100
+
+ module_function
+
+ def with_routing_tables
+ Ci::BuildMetadata.table_name = :p_ci_builds_metadata
+ yield
+ ensure
+ Ci::BuildMetadata.table_name = :ci_builds_metadata
+ end
+
+ # We're dropping the default values here to ensure that the application code
+ # populates the `partition_id` value and it's not falling back on the
+ # database default one. We should be able to clean this up after
+ # partitioning the tables and substituting the routing table in the model:
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/377822
+ #
+ def setup(connection: Ci::ApplicationRecord.connection)
+ each_partitionable_table do |table_name|
+ change_column_default(table_name, from: DEFAULT_PARTITION, to: nil, connection: connection)
+ change_column_default("p_#{table_name}", from: DEFAULT_PARTITION, to: nil, connection: connection)
+ create_test_partition(table_name, connection: connection)
+ end
+ end
+
+ def teardown(connection: Ci::ApplicationRecord.connection)
+ each_partitionable_table do |table_name|
+ drop_test_partition(table_name, connection: connection)
+ change_column_default(table_name, from: nil, to: DEFAULT_PARTITION, connection: connection)
+ change_column_default("p_#{table_name}", from: nil, to: DEFAULT_PARTITION, connection: connection)
+ end
+ end
+
+ def each_partitionable_table
+ ::Ci::Partitionable::Testing::PARTITIONABLE_MODELS.each do |klass|
+ model = klass.safe_constantize
+ table_name = model.table_name.delete_prefix('p_')
+
+ yield(table_name)
+
+ model.reset_column_information if model.connected?
+ end
+ end
+
+ def change_column_default(table_name, from:, to:, connection:)
+ return unless table_available?(table_name, connection: connection)
+
+ connection.change_column_default(table_name, :partition_id, from: from, to: to)
+ end
+
+ def create_test_partition(table_name, connection:)
+ return unless table_available?("p_#{table_name}", connection: connection)
+
+ drop_test_partition(table_name, connection: connection)
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{full_partition_name(table_name)}
+ PARTITION OF p_#{table_name}
+ FOR VALUES IN (#{PartitioningTesting::PartitionIdentifiers.ci_testing_partition_id});
+ SQL
+ end
+
+ def drop_test_partition(table_name, connection:)
+ return unless table_available?(table_name, connection: connection)
+
+ connection.execute(<<~SQL)
+ DROP TABLE IF EXISTS #{full_partition_name(table_name)};
+ SQL
+ end
+
+ def table_available?(table_name, connection:)
+ connection.table_exists?(table_name) &&
+ connection.column_exists?(table_name, :partition_id)
+ end
+
+ def full_partition_name(table_name)
+ "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}._test_gitlab_#{table_name}_partition"
+ end
+ end
+ end
+end
diff --git a/spec/support/multiple_databases.rb b/spec/support/multiple_databases.rb
new file mode 100644
index 00000000000..616cf00269c
--- /dev/null
+++ b/spec/support/multiple_databases.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ # Ensure database versions are memoized to prevent query counts from
+ # being affected by version checks. Note that
+ # Gitlab::Database.check_postgres_version_and_print_warning is called
+ # at startup, but that generates its own
+ # `Gitlab::Database::Reflection` so the result is not memoized by
+ # callers of `ApplicationRecord.database.version`, such as
+ # `Gitlab::Database::AsWithMaterialized.materialized_supported?`.
+ # TODO This can be removed once https://gitlab.com/gitlab-org/gitlab/-/issues/325639 is completed.
+ [ApplicationRecord, ::Ci::ApplicationRecord].each { |record| record.database.version }
+
+ config.around(:each, :reestablished_active_record_base) do |example|
+ with_reestablished_active_record_base(reconnect: example.metadata.fetch(:reconnect, true)) do
+ example.run
+ end
+ end
+
+ config.around(:each, :add_ci_connection) do |example|
+ with_added_ci_connection do
+ example.run
+ end
+ end
+end
diff --git a/spec/support/rate_limiter.rb b/spec/support/rate_limiter.rb
new file mode 100644
index 00000000000..525d593c293
--- /dev/null
+++ b/spec/support/rate_limiter.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ config.before(:each, :disable_rate_limiter) do
+ allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
+ end
+end
diff --git a/spec/support/redis.rb b/spec/support/redis.rb
index d00d6562966..6d313c8aa16 100644
--- a/spec/support/redis.rb
+++ b/spec/support/redis.rb
@@ -19,4 +19,10 @@ RSpec.configure do |config|
public_send("redis_#{underscored_name}_cleanup!")
end
end
+
+ config.before(:suite) do
+ Gitlab::Redis::ALL_CLASSES.each do |instance_class|
+ instance_class.with(&:flushdb)
+ end
+ end
end
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index 6795d2f6d2a..71dfc3fd5a3 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -11,6 +11,9 @@ require_relative "helpers/fast_rails_root"
RSpec::Expectations.configuration.on_potential_false_positives = :raise
RSpec.configure do |config|
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/379686
+ config.threadsafe = false
+
# Re-run failures locally with `--only-failures`
config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', './spec/examples.txt')
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 30220b04fa2..67b7023f1ff 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -997,7 +997,6 @@
- './ee/spec/graphql/types/vulnerability_severity_enum_spec.rb'
- './ee/spec/graphql/types/vulnerability_sort_enum_spec.rb'
- './ee/spec/graphql/types/vulnerability_state_enum_spec.rb'
-- './ee/spec/graphql/types/vulnerability_type_spec.rb'
- './ee/spec/graphql/types/vulnerable_dependency_type_spec.rb'
- './ee/spec/graphql/types/vulnerable_kubernetes_resource_type_spec.rb'
- './ee/spec/graphql/types/vulnerable_package_type_spec.rb'
@@ -1870,7 +1869,6 @@
- './ee/spec/models/ee/ci/secure_file_spec.rb'
- './ee/spec/models/ee/clusters/agent_spec.rb'
- './ee/spec/models/ee/description_version_spec.rb'
-- './ee/spec/models/ee/event_collection_spec.rb'
- './ee/spec/models/ee/event_spec.rb'
- './ee/spec/models/ee/gpg_key_spec.rb'
- './ee/spec/models/ee/group_group_link_spec.rb'
@@ -3223,7 +3221,6 @@
- './ee/spec/services/security/security_orchestration_policies/sync_opened_merge_requests_service_spec.rb'
- './ee/spec/services/security/security_orchestration_policies/sync_open_merge_requests_head_pipeline_service_spec.rb'
- './ee/spec/services/security/security_orchestration_policies/validate_policy_service_spec.rb'
-- './ee/spec/services/security/store_findings_metadata_service_spec.rb'
- './ee/spec/services/security/store_grouped_scans_service_spec.rb'
- './ee/spec/services/security/store_scan_service_spec.rb'
- './ee/spec/services/security/store_scans_service_spec.rb'
@@ -3379,7 +3376,6 @@
- './ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb'
- './ee/spec/views/registrations/welcome/show.html.haml_spec.rb'
- './ee/spec/views/search/_category.html.haml_spec.rb'
-- './ee/spec/views/shared/access_tokens/_table.html.haml_spec.rb'
- './ee/spec/views/shared/billings/_billing_plan_actions.html.haml_spec.rb'
- './ee/spec/views/shared/billings/_billing_plan.html.haml_spec.rb'
- './ee/spec/views/shared/billings/_billing_plans.html.haml_spec.rb'
@@ -4091,7 +4087,6 @@
- './spec/features/incidents/user_creates_new_incident_spec.rb'
- './spec/features/incidents/user_filters_incidents_by_status_spec.rb'
- './spec/features/incidents/user_searches_incidents_spec.rb'
-- './spec/features/incidents/user_views_incident_spec.rb'
- './spec/features/invites_spec.rb'
- './spec/features/issuables/issuable_list_spec.rb'
- './spec/features/issuables/markdown_references/internal_references_spec.rb'
@@ -5873,14 +5868,6 @@
- './spec/lib/error_tracking/collector/payload_validator_spec.rb'
- './spec/lib/error_tracking/collector/sentry_auth_parser_spec.rb'
- './spec/lib/error_tracking/collector/sentry_request_parser_spec.rb'
-- './spec/lib/error_tracking/sentry_client/api_urls_spec.rb'
-- './spec/lib/error_tracking/sentry_client/event_spec.rb'
-- './spec/lib/error_tracking/sentry_client/issue_link_spec.rb'
-- './spec/lib/error_tracking/sentry_client/issue_spec.rb'
-- './spec/lib/error_tracking/sentry_client/pagination_parser_spec.rb'
-- './spec/lib/error_tracking/sentry_client/projects_spec.rb'
-- './spec/lib/error_tracking/sentry_client/repo_spec.rb'
-- './spec/lib/error_tracking/sentry_client_spec.rb'
- './spec/lib/error_tracking/stacktrace_builder_spec.rb'
- './spec/lib/event_filter_spec.rb'
- './spec/lib/expand_variables_spec.rb'
@@ -8400,7 +8387,6 @@
- './spec/models/error_tracking/error_event_spec.rb'
- './spec/models/error_tracking/error_spec.rb'
- './spec/models/error_tracking/project_error_tracking_setting_spec.rb'
-- './spec/models/event_collection_spec.rb'
- './spec/models/event_spec.rb'
- './spec/models/experiment_spec.rb'
- './spec/models/experiment_subject_spec.rb'
@@ -9464,16 +9450,6 @@
- './spec/routing/projects/security/configuration_controller_routing_spec.rb'
- './spec/routing/routing_spec.rb'
- './spec/routing/uploads_routing_spec.rb'
-- './spec/scripts/changed-feature-flags_spec.rb'
-- './spec/scripts/determine-qa-tests_spec.rb'
-- './spec/scripts/failed_tests_spec.rb'
-- './spec/scripts/lib/glfm/parse_examples_spec.rb'
-- './spec/scripts/lib/glfm/shared_spec.rb'
-- './spec/scripts/lib/glfm/update_example_snapshots_spec.rb'
-- './spec/scripts/lib/glfm/update_specification_spec.rb'
-- './spec/scripts/pipeline_test_report_builder_spec.rb'
-- './spec/scripts/setup/find_jh_branch_spec.rb'
-- './spec/scripts/trigger-build_spec.rb'
- './spec/serializers/accessibility_error_entity_spec.rb'
- './spec/serializers/accessibility_reports_comparer_entity_spec.rb'
- './spec/serializers/accessibility_reports_comparer_serializer_spec.rb'
@@ -10082,7 +10058,6 @@
- './spec/services/members/request_access_service_spec.rb'
- './spec/services/members/standard_member_builder_spec.rb'
- './spec/services/members/unassign_issuables_service_spec.rb'
-- './spec/services/members/update_service_spec.rb'
- './spec/services/merge_requests/add_context_service_spec.rb'
- './spec/services/merge_requests/add_spent_time_service_spec.rb'
- './spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb'
@@ -10768,7 +10743,6 @@
- './spec/views/registrations/welcome/show.html.haml_spec.rb'
- './spec/views/search/_results.html.haml_spec.rb'
- './spec/views/search/show.html.haml_spec.rb'
-- './spec/views/shared/access_tokens/_table.html.haml_spec.rb'
- './spec/views/shared/deploy_tokens/_form.html.haml_spec.rb'
- './spec/views/shared/groups/_dropdown.html.haml_spec.rb'
- './spec/views/shared/issuable/_sidebar.html.haml_spec.rb'
@@ -10922,7 +10896,6 @@
- './spec/workers/environments/canary_ingress/update_worker_spec.rb'
- './spec/workers/error_tracking_issue_link_worker_spec.rb'
- './spec/workers/every_sidekiq_worker_spec.rb'
-- './spec/workers/experiments/record_conversion_event_worker_spec.rb'
- './spec/workers/expire_build_artifacts_worker_spec.rb'
- './spec/workers/export_csv_worker_spec.rb'
- './spec/workers/external_service_reactive_caching_worker_spec.rb'
diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb
index 94061b140f4..b85c3904127 100644
--- a/spec/support/services/issuable_update_service_shared_examples.rb
+++ b/spec/support/services/issuable_update_service_shared_examples.rb
@@ -6,18 +6,48 @@ RSpec.shared_examples 'issuable update service' do
end
context 'changing state' do
- before do
- expect(project).to receive(:execute_hooks).once
- end
+ let(:hook_event) { :"#{closed_issuable.class.name.underscore.to_sym}_hooks" }
context 'to reopened' do
- it 'executes hooks only once' do
+ let(:expected_payload) do
+ include(
+ changes: include(
+ state_id: { current: 1, previous: 2 },
+ updated_at: { current: kind_of(Time), previous: kind_of(Time) }
+ ),
+ object_attributes: include(
+ state: 'opened',
+ action: 'reopen'
+ )
+ )
+ end
+
+ it 'executes hooks' do
+ expect(project).to receive(:execute_hooks).with(expected_payload, hook_event)
+ expect(project).to receive(:execute_integrations).with(expected_payload, hook_event)
+
described_class.new(project: project, current_user: user, params: { state_event: 'reopen' }).execute(closed_issuable)
end
end
context 'to closed' do
- it 'executes hooks only once' do
+ let(:expected_payload) do
+ include(
+ changes: include(
+ state_id: { current: 2, previous: 1 },
+ updated_at: { current: kind_of(Time), previous: kind_of(Time) }
+ ),
+ object_attributes: include(
+ state: 'closed',
+ action: 'close'
+ )
+ )
+ end
+
+ it 'executes hooks' do
+ expect(project).to receive(:execute_hooks).with(expected_payload, hook_event)
+ expect(project).to receive(:execute_integrations).with(expected_payload, hook_event)
+
described_class.new(project: project, current_user: user, params: { state_event: 'close' }).execute(open_issuable)
end
end
diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
index 1e291a90163..ae98ce689e3 100644
--- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -57,7 +57,7 @@ RSpec.shared_examples "migrating a deleted user's associated records to the ghos
context "race conditions" do
context "when #{record_class_name} migration fails and is rolled back" do
before do
- expect_any_instance_of(ActiveRecord::Associations::CollectionProxy)
+ allow_any_instance_of(ActiveRecord::Associations::CollectionProxy)
.to receive(:update_all).and_raise(ActiveRecord::StatementTimeout)
end
@@ -68,6 +68,7 @@ RSpec.shared_examples "migrating a deleted user's associated records to the ghos
end
it "doesn't unblock a previously-blocked user" do
+ expect(user.starred_projects).to receive(:update_all).and_call_original
user.block
expect { service.execute }.to raise_error(ActiveRecord::StatementTimeout)
diff --git a/spec/support/shared_contexts/container_repositories_shared_context.rb b/spec/support/shared_contexts/container_repositories_shared_context.rb
index a74b09d38bd..c3040a77517 100644
--- a/spec/support/shared_contexts/container_repositories_shared_context.rb
+++ b/spec/support/shared_contexts/container_repositories_shared_context.rb
@@ -14,7 +14,6 @@ RSpec.shared_context 'importable repositories' do
before do
stub_application_setting(container_registry_import_created_before: 1.day.ago)
stub_feature_flags(
- container_registry_phase_2_deny_list: false,
container_registry_migration_limit_gitlab_org: false,
container_registry_migration_phase2_all_plans: false
)
diff --git a/spec/support/shared_contexts/graphql/resolvers/runners_resolver_shared_context.rb b/spec/support/shared_contexts/graphql/resolvers/runners_resolver_shared_context.rb
index aa857cfdb70..1480b5f98e7 100644
--- a/spec/support/shared_contexts/graphql/resolvers/runners_resolver_shared_context.rb
+++ b/spec/support/shared_contexts/graphql/resolvers/runners_resolver_shared_context.rb
@@ -17,6 +17,6 @@ RSpec.shared_context 'runners resolver setup' do
end
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token: 'mnopqr', description: 'group runner', contacted_at: 2.seconds.ago) }
- let_it_be(:subgroup_runner) { create(:ci_runner, :group, groups: [subgroup], token: 'mnopqr', description: 'subgroup runner', contacted_at: 1.second.ago) }
+ let_it_be(:subgroup_runner) { create(:ci_runner, :group, groups: [subgroup], token: '123456', description: 'subgroup runner', contacted_at: 1.second.ago) }
let_it_be(:instance_runner) { create(:ci_runner, :instance, description: 'shared runner', token: 'stuvxz', contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) }
end
diff --git a/spec/support/shared_contexts/jobs/handling_retried_jobs_shared_context.rb b/spec/support/shared_contexts/jobs/handling_retried_jobs_shared_context.rb
new file mode 100644
index 00000000000..428eff77373
--- /dev/null
+++ b/spec/support/shared_contexts/jobs/handling_retried_jobs_shared_context.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'when handling retried jobs' do |url|
+ let(:set_name) { 'retry' }
+ # Account for Sidekiq retry jitter
+ # https://github.com/mperham/sidekiq/blob/3575ccb44c688dd08bfbfd937696260b12c622fb/lib/sidekiq/job_retry.rb#L217
+ let(:schedule_jitter) { 10 }
+
+ # Try to mimic as closely as possible what Sidekiq will actually
+ # do to retry a job.
+ def retry_in(klass, time, args = 0)
+ message = Gitlab::Json.generate(
+ 'class' => klass.name,
+ 'args' => [args],
+ 'retry' => true
+ )
+
+ allow(klass).to receive(:sidekiq_retry_in_block).and_return(proc { time.to_i })
+
+ begin
+ Sidekiq::JobRetry.new(Sidekiq).local(klass, message, klass.queue) { raise 'boom' }
+ rescue Sidekiq::JobRetry::Skip
+ # Sidekiq scheduled the retry
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/lib/sbom/package_url_shared_contexts.rb b/spec/support/shared_contexts/lib/sbom/package_url_shared_contexts.rb
new file mode 100644
index 00000000000..263cf9f5e19
--- /dev/null
+++ b/spec/support/shared_contexts/lib/sbom/package_url_shared_contexts.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'oj'
+
+def parameterized_test_matrix(invalid: false)
+ test_cases_path = File.join(
+ File.expand_path(__dir__), '..', '..', '..', '..', 'fixtures', 'lib', 'sbom', 'package-url-test-cases.json')
+ test_cases = Gitlab::Json.parse(File.read(test_cases_path))
+
+ test_cases.filter { _1.delete('is_invalid') == invalid }.each_with_object({}) do |test_case, memo|
+ description = test_case.delete('description')
+ memo[description] = test_case.symbolize_keys
+ end
+end
+
+RSpec.shared_context 'with valid purl examples' do
+ where do
+ parameterized_test_matrix(invalid: false)
+ end
+end
+
+RSpec.shared_context 'with invalid purl examples' do
+ where do
+ parameterized_test_matrix(invalid: true)
+ end
+end
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 064e40287be..af35a5ff068 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -67,8 +67,8 @@ RSpec.shared_context 'project navbar structure' do
{
nav_item: _('Deployments'),
nav_sub_items: [
- _('Feature Flags'),
_('Environments'),
+ _('Feature Flags'),
_('Releases')
]
},
@@ -85,8 +85,7 @@ RSpec.shared_context 'project navbar structure' do
_('Metrics'),
_('Error Tracking'),
_('Alerts'),
- _('Incidents'),
- _('Product Analytics')
+ _('Incidents')
]
},
{
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 bb1b794c2b6..a6226fe903b 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -76,6 +76,7 @@ RSpec.shared_context 'GroupPolicy context' do
register_group_runners
read_billing
edit_billing
+ admin_member_access_request
]
end
diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
index fc7255a4a20..6e2caa853f8 100644
--- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -68,7 +68,7 @@ RSpec.shared_context 'ProjectPolicy context' do
admin_project admin_project_member admin_snippet admin_terraform_state
admin_wiki create_deploy_token destroy_deploy_token
push_to_delete_protected_branch read_deploy_token update_snippet
- destroy_upload
+ destroy_upload admin_member_access_request rename_project
]
end
@@ -83,7 +83,7 @@ RSpec.shared_context 'ProjectPolicy context' do
let(:base_owner_permissions) do
%i[
archive_project change_namespace change_visibility_level destroy_issue
- destroy_merge_request manage_owners remove_fork_project remove_project rename_project
+ destroy_merge_request manage_owners remove_fork_project remove_project
set_issue_created_at set_issue_iid set_issue_updated_at
set_note_created_at
]
diff --git a/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
index b18ce14eba6..d9ea7bc7f82 100644
--- a/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
@@ -126,6 +126,63 @@ RSpec.shared_context 'ProjectPolicyTable context' do
end
# This table is based on permission_table_for_guest_feature_access,
+ # but takes into account note confidentiality. It is required on the context
+ # to have one regular note and one confidential note.
+ #
+ # project_level, :feature_access_level, :membership, :admin_mode, :expected_count
+ def permission_table_for_notes_feature_access
+ :public | :enabled | :admin | true | 2
+ :public | :enabled | :admin | false | 1
+ :public | :enabled | :reporter | nil | 2
+ :public | :enabled | :guest | nil | 1
+ :public | :enabled | :non_member | nil | 1
+ :public | :enabled | :anonymous | nil | 1
+
+ :public | :private | :admin | true | 2
+ :public | :private | :admin | false | 0
+ :public | :private | :reporter | nil | 2
+ :public | :private | :guest | nil | 1
+ :public | :private | :non_member | nil | 0
+ :public | :private | :anonymous | nil | 0
+
+ :public | :disabled | :reporter | nil | 0
+ :public | :disabled | :guest | nil | 0
+ :public | :disabled | :non_member | nil | 0
+ :public | :disabled | :anonymous | nil | 0
+
+ :internal | :enabled | :admin | true | 2
+ :internal | :enabled | :admin | false | 1
+ :internal | :enabled | :reporter | nil | 2
+ :internal | :enabled | :guest | nil | 1
+ :internal | :enabled | :non_member | nil | 1
+ :internal | :enabled | :anonymous | nil | 0
+
+ :internal | :private | :admin | true | 2
+ :internal | :private | :admin | false | 0
+ :internal | :private | :reporter | nil | 2
+ :internal | :private | :guest | nil | 1
+ :internal | :private | :non_member | nil | 0
+ :internal | :private | :anonymous | nil | 0
+
+ :internal | :disabled | :reporter | nil | 0
+ :internal | :disabled | :guest | nil | 0
+ :internal | :disabled | :non_member | nil | 0
+ :internal | :disabled | :anonymous | nil | 0
+
+ :private | :private | :admin | true | 2
+ :private | :private | :admin | false | 0
+ :private | :private | :reporter | nil | 2
+ :private | :private | :guest | nil | 1
+ :private | :private | :non_member | nil | 0
+ :private | :private | :anonymous | nil | 0
+
+ :private | :disabled | :reporter | nil | 0
+ :private | :disabled | :guest | nil | 0
+ :private | :disabled | :non_member | nil | 0
+ :private | :disabled | :anonymous | nil | 0
+ end
+
+ # This table is based on permission_table_for_guest_feature_access,
# but with a slight twist.
# Some features can be hidden away to GUEST, when project is private.
# (see ProjectFeature::PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT)
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
index fa048b76e18..7df4b7635d3 100644
--- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -3,6 +3,7 @@
RSpec.shared_examples 'multiple issue boards' do
context 'authorized user' do
before do
+ stub_feature_flags(apollo_boards: false)
parent.add_maintainer(user)
login_as(user)
@@ -121,6 +122,7 @@ RSpec.shared_examples 'multiple issue boards' do
context 'unauthorized user' do
before do
+ stub_feature_flags(apollo_boards: false)
visit boards_path
wait_for_requests
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 cd4432af4ed..a9edf18d562 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
@@ -35,7 +35,7 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' 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).not_to receive(:create_wiki_repository)
expect(parent.wiki.repository).not_to receive(:ensure_repository)
expect { subject.run }.not_to raise_error
@@ -75,7 +75,7 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
describe 'unsuccessful response' do
shared_examples 'does not raise an error' do
it 'does not raise an error' do
- expect(parent.wiki).not_to receive(:ensure_repository)
+ expect(parent.wiki).not_to receive(:create_wiki_repository)
expect(parent.wiki.repository).not_to receive(:ensure_repository)
expect { subject.run }.not_to raise_error
diff --git a/spec/support/shared_examples/ci/retryable_shared_examples.rb b/spec/support/shared_examples/ci/retryable_shared_examples.rb
new file mode 100644
index 00000000000..4622dbe4e31
--- /dev/null
+++ b/spec/support/shared_examples/ci/retryable_shared_examples.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a retryable job' do
+ describe '#enqueue_immediately?' do
+ it 'defaults to false' do
+ expect(subject.enqueue_immediately?).to eq(false)
+ end
+ end
+
+ describe '#set_enqueue_immediately!' do
+ it 'changes #enqueue_immediately? to true' do
+ expect { subject.set_enqueue_immediately! }
+ .to change(subject, :enqueue_immediately?).from(false).to(true)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/preferred_language_switcher_shared_examples.rb b/spec/support/shared_examples/controllers/preferred_language_switcher_shared_examples.rb
new file mode 100644
index 00000000000..74456e62eb8
--- /dev/null
+++ b/spec/support/shared_examples/controllers/preferred_language_switcher_shared_examples.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'switches to user preferred language' do |msg_id_example|
+ context 'with preferred_language in cookies' do
+ render_views
+ let(:user_preferred_language) { 'zh_CN' }
+
+ subject { get :new }
+
+ before do
+ cookies['preferred_language'] = user_preferred_language
+ end
+
+ it 'renders new template with cookies preferred language' do
+ expect(subject).to render_template(:new)
+ expect(response).to have_gitlab_http_status(:ok)
+
+ expected_text = Gitlab::I18n.with_locale(user_preferred_language) { _(msg_id_example) }
+ expect(response.body).to include(expected_text)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/access_tokens_shared_examples.rb b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
index cd255abd7a8..32a7b32ac72 100644
--- a/spec/support/shared_examples/features/access_tokens_shared_examples.rb
+++ b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
@@ -9,13 +9,7 @@ RSpec.shared_examples 'resource access tokens missing access rights' do
end
RSpec.shared_examples 'resource access tokens creation' do |resource_type|
- def active_resource_access_tokens
- find("[data-testid='active-tokens']")
- end
-
- def created_resource_access_token
- find_field('new-access-token').value
- end
+ include Spec::Support::Helpers::AccessTokenHelpers
it 'allows creation of an access token', :aggregate_failures do
name = 'My access token'
@@ -34,12 +28,12 @@ RSpec.shared_examples 'resource access tokens creation' do |resource_type|
click_on "Create #{resource_type} access token"
- expect(active_resource_access_tokens).to have_text(name)
- expect(active_resource_access_tokens).to have_text('in')
- expect(active_resource_access_tokens).to have_text('read_api')
- expect(active_resource_access_tokens).to have_text('read_repository')
- expect(active_resource_access_tokens).to have_text('Guest')
- expect(created_resource_access_token).not_to be_empty
+ expect(active_access_tokens).to have_text(name)
+ expect(active_access_tokens).to have_text('in')
+ expect(active_access_tokens).to have_text('read_api')
+ expect(active_access_tokens).to have_text('read_repository')
+ expect(active_access_tokens).to have_text('Guest')
+ expect(created_access_token).to match(/[\w-]{20}/)
end
end
@@ -105,14 +99,14 @@ RSpec.shared_examples 'resource access tokens creation disallowed' do |error_mes
end
RSpec.shared_examples 'active resource access tokens' do
- def active_resource_access_tokens
+ def active_access_tokens
find("[data-testid='active-tokens']")
end
it 'shows active access tokens' do
visit resource_settings_access_tokens_path
- expect(active_resource_access_tokens).to have_text(resource_access_token.name)
+ expect(active_access_tokens).to have_text(resource_access_token.name)
end
context 'when User#time_display_relative is false' do
@@ -123,13 +117,13 @@ RSpec.shared_examples 'active resource access tokens' do
it 'shows absolute times for expires_at' do
visit resource_settings_access_tokens_path
- expect(active_resource_access_tokens).to have_text(PersonalAccessToken.last.expires_at.strftime('%b %-d'))
+ expect(active_access_tokens).to have_text(PersonalAccessToken.last.expires_at.strftime('%b %-d'))
end
end
end
RSpec.shared_examples 'inactive resource access tokens' do |no_active_tokens_text|
- def active_resource_access_tokens
+ def active_access_tokens
find("[data-testid='active-tokens']")
end
@@ -137,14 +131,14 @@ RSpec.shared_examples 'inactive resource access tokens' do |no_active_tokens_tex
visit resource_settings_access_tokens_path
accept_gl_confirm(button_text: 'Revoke') { click_on 'Revoke' }
- expect(active_resource_access_tokens).to have_text(no_active_tokens_text)
+ expect(active_access_tokens).to have_text(no_active_tokens_text)
end
it 'removes expired tokens from active section' do
resource_access_token.update!(expires_at: 5.days.ago)
visit resource_settings_access_tokens_path
- expect(active_resource_access_tokens).to have_text(no_active_tokens_text)
+ expect(active_access_tokens).to have_text(no_active_tokens_text)
end
context 'when resource access token creation is not allowed' do
@@ -156,7 +150,7 @@ RSpec.shared_examples 'inactive resource access tokens' do |no_active_tokens_tex
visit resource_settings_access_tokens_path
accept_gl_confirm(button_text: 'Revoke') { click_on 'Revoke' }
- expect(active_resource_access_tokens).to have_text(no_active_tokens_text)
+ expect(active_access_tokens).to have_text(no_active_tokens_text)
end
end
end
diff --git a/spec/support/shared_examples/features/confidential_notes_shared_examples.rb b/spec/support/shared_examples/features/confidential_notes_shared_examples.rb
new file mode 100644
index 00000000000..289da025af6
--- /dev/null
+++ b/spec/support/shared_examples/features/confidential_notes_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.shared_examples 'confidential notes on issuables' do
+ include Spec::Support::Helpers::Features::NotesHelpers
+
+ context 'when user does not have permissions' do
+ it 'does not show confidential note checkbox' do
+ issuable_parent.add_guest(user)
+ sign_in(user)
+ visit(issuable_path)
+
+ page.within('.new-note') do
+ expect(page).not_to have_selector('[data-testid="internal-note-checkbox"]')
+ end
+ end
+ end
+
+ context 'when user has permissions' do
+ it 'creates confidential note' do
+ issuable_parent.add_reporter(user)
+ sign_in(user)
+ visit(issuable_path)
+
+ find('[data-testid="internal-note-checkbox"]').click
+ add_note('Confidential note')
+
+ page.within('.note-header') do
+ expect(page).to have_selector('[data-testid="internal-note-indicator"]')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb
index 7863548e7f3..f01e3c88dad 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -313,5 +313,21 @@ RSpec.shared_examples 'edits content using the content editor' do
expect(page).not_to have_css(suggestions_dropdown)
end
+
+ it 'scrolls selected item into view when navigating with keyboard' do
+ type_in_content_editor ':'
+
+ expect(find(suggestions_dropdown)).to have_text('hundred points symbol')
+
+ expect(dropdown_scroll_top).to be 0
+
+ send_keys :arrow_up
+
+ expect(dropdown_scroll_top).to be > 100
+ end
+
+ def dropdown_scroll_top
+ evaluate_script("document.querySelector('#{suggestions_dropdown} .gl-new-dropdown-inner').scrollTop")
+ end
end
end
diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
index 456175e7113..2cfe353d5d7 100644
--- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -21,15 +21,10 @@ RSpec.shared_examples 'a creatable merge request' do
expect(page).to have_content user.name
end
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
-
+ click_button 'Select milestone'
+ click_button milestone.title
expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
+ expect(page).to have_button milestone.title
click_button 'Labels'
page.within '.dropdown-menu-labels' do
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index 2fff4137934..ea6d1655694 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -20,14 +20,10 @@ RSpec.shared_examples 'an editable merge request' do
expect(page).to have_content user.name
end
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
+ click_button 'Select milestone'
+ click_button milestone.title
expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
+ expect(page).to have_button milestone.title
click_button 'Labels'
page.within '.dropdown-menu-labels' do
diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb
index 7aad5e2de80..f09cf0613a1 100644
--- a/spec/support/shared_examples/features/packages_shared_examples.rb
+++ b/spec/support/shared_examples/features/packages_shared_examples.rb
@@ -14,7 +14,7 @@ RSpec.shared_examples 'packages list' do |check_project_name: false|
end
def package_table_row(index)
- page.all("#{packages_table_selector} > [data-testid=\"package-row\"]")[index].text
+ page.all("#{packages_table_selector} [data-testid=\"package-row\"]")[index].text
end
end
diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb
index 1d4af944187..a7bc19da45f 100644
--- a/spec/support/shared_examples/features/runners_shared_examples.rb
+++ b/spec/support/shared_examples/features/runners_shared_examples.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
RSpec.shared_examples 'shows and resets runner registration token' do
- include Spec::Support::Helpers::ModalHelpers
include Spec::Support::Helpers::Features::RunnersHelpers
+ include Spec::Support::Helpers::ModalHelpers
before do
click_on dropdown_text
@@ -146,6 +146,23 @@ RSpec.shared_examples 'pauses, resumes and deletes a runner' do
end
end
+RSpec.shared_examples 'deletes runners in bulk' do
+ describe 'when selecting all for deletion', :js do
+ before do
+ check s_('Runners|Select all')
+ click_button s_('Runners|Delete selected')
+
+ within_modal do
+ click_on "Permanently delete #{runner_count} runners"
+ end
+
+ wait_for_requests
+ end
+
+ it_behaves_like 'shows no runners registered'
+ end
+end
+
RSpec.shared_examples 'filters by tag' do
it 'shows correct runner when tag matches' do
expect(page).to have_content found_runner
diff --git a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb
new file mode 100644
index 00000000000..4d242d0e719
--- /dev/null
+++ b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb
@@ -0,0 +1,304 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a redacted search results' do
+ let_it_be(:user) { create(:user) }
+
+ let_it_be(:accessible_group) { create(:group, :private) }
+ let_it_be(:accessible_project) { create(:project, :repository, :private, name: 'accessible_project') }
+
+ let_it_be(:group_member) { create(:group_member, group: accessible_group, user: user) }
+
+ let_it_be(:inaccessible_group) { create(:group, :private) }
+ let_it_be(:inaccessible_project) { create(:project, :repository, :private, name: 'inaccessible_project') }
+
+ let(:search) { 'anything' }
+
+ subject(:result) { search_service.search_objects }
+
+ def found_blob(project)
+ Gitlab::Search::FoundBlob.new(project: project)
+ end
+
+ def found_wiki_page(project)
+ Gitlab::Search::FoundWikiPage.new(found_blob(project))
+ end
+
+ def ar_relation(klass, *objects)
+ klass.id_in(objects.map(&:id))
+ end
+
+ def kaminari_array(*objects)
+ Kaminari.paginate_array(objects).page(1).per(20)
+ end
+
+ before do
+ accessible_project.add_maintainer(user)
+
+ allow(search_service)
+ .to receive_message_chain(:search_results, :objects)
+ .and_return(unredacted_results)
+ end
+
+ context 'for issues' do
+ let(:readable) { create(:issue, project: accessible_project) }
+ let(:unreadable) { create(:issue, project: inaccessible_project) }
+ let(:unredacted_results) { ar_relation(Issue, readable, unreadable) }
+ let(:scope) { 'issues' }
+
+ it 'redacts the inaccessible issue' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'Issue', id: unreadable.id, ability: :read_issue }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+ end
+
+ context 'for notes' do
+ let(:readable_merge_request) do
+ create(:merge_request_with_diffs, target_project: accessible_project, source_project: accessible_project)
+ end
+
+ let(:readable_note_on_commit) { create(:note_on_commit, project: accessible_project) }
+ let(:readable_diff_note) { create(:diff_note_on_commit, project: accessible_project) }
+ let(:readable_note_on_mr) do
+ create(:discussion_note_on_merge_request, noteable: readable_merge_request, project: accessible_project)
+ end
+
+ let(:readable_diff_note_on_mr) do
+ create(:diff_note_on_merge_request, noteable: readable_merge_request, project: accessible_project)
+ end
+
+ let(:readable_note_on_project_snippet) do
+ create(:note_on_project_snippet, noteable: readable_merge_request, project: accessible_project)
+ end
+
+ let(:unreadable_merge_request) do
+ create(:merge_request_with_diffs, target_project: inaccessible_project, source_project: inaccessible_project)
+ end
+
+ let(:unreadable_note_on_commit) { create(:note_on_commit, project: inaccessible_project) }
+ let(:unreadable_diff_note) { create(:diff_note_on_commit, project: inaccessible_project) }
+ let(:unreadable_note_on_mr) do
+ create(:discussion_note_on_merge_request, noteable: unreadable_merge_request, project: inaccessible_project)
+ end
+
+ let(:unreadable_note_on_project_snippet) do
+ create(:note_on_project_snippet, noteable: unreadable_merge_request, project: inaccessible_project)
+ end
+
+ let(:unredacted_results) do
+ ar_relation(Note,
+ readable_note_on_commit,
+ readable_diff_note,
+ readable_note_on_mr,
+ readable_diff_note_on_mr,
+ readable_note_on_project_snippet,
+ unreadable_note_on_commit,
+ unreadable_diff_note,
+ unreadable_note_on_mr,
+ unreadable_note_on_project_snippet)
+ end
+
+ let(:scope) { 'notes' }
+
+ it 'redacts the inaccessible notes' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'Note', id: unreadable_note_on_commit.id, ability: :read_note },
+ { class_name: 'DiffNote', id: unreadable_diff_note.id, ability: :read_note },
+ { class_name: 'DiscussionNote', id: unreadable_note_on_mr.id, ability: :read_note },
+ { class_name: 'Note', id: unreadable_note_on_project_snippet.id, ability: :read_note }
+ ])))
+
+ expect(result).to contain_exactly(readable_note_on_commit,
+ readable_diff_note,
+ readable_note_on_mr,
+ readable_diff_note_on_mr,
+ readable_note_on_project_snippet)
+ end
+ end
+
+ context 'for merge_requests' do
+ let(:readable) { create(:merge_request, source_project: accessible_project) }
+ let(:unreadable) { create(:merge_request, source_project: inaccessible_project) }
+ let(:unredacted_results) { ar_relation(MergeRequest, readable, unreadable) }
+ let(:scope) { 'merge_requests' }
+
+ it 'redacts the inaccessible merge request' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'MergeRequest', id: unreadable.id, ability: :read_merge_request }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+
+ context 'with :with_api_entity_associations' do
+ let(:unredacted_results) { ar_relation(MergeRequest.with_api_entity_associations, readable, unreadable) }
+
+ it_behaves_like "redaction limits N+1 queries", limit: 8
+ end
+ end
+
+ context 'for blobs' do
+ let(:readable) { found_blob(accessible_project) }
+ let(:unreadable) { found_blob(inaccessible_project) }
+ let(:unredacted_results) { kaminari_array(readable, unreadable) }
+ let(:scope) { 'blobs' }
+
+ it 'redacts the inaccessible blob' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'Gitlab::Search::FoundBlob', id: unreadable.id, ability: :read_blob }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+ end
+
+ context 'for wiki blobs' do
+ let(:readable) { found_wiki_page(accessible_project) }
+ let(:unreadable) { found_wiki_page(inaccessible_project) }
+ let(:unredacted_results) { kaminari_array(readable, unreadable) }
+ let(:scope) { 'wiki_blobs' }
+
+ it 'redacts the inaccessible blob' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'Gitlab::Search::FoundWikiPage', id: unreadable.id, ability: :read_wiki_page }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+ end
+
+ context 'for project snippets' do
+ let(:readable) { create(:project_snippet, project: accessible_project) }
+ let(:unreadable) { create(:project_snippet, project: inaccessible_project) }
+ let(:unredacted_results) { ar_relation(ProjectSnippet, readable, unreadable) }
+ let(:scope) { 'snippet_titles' }
+
+ it 'redacts the inaccessible snippet' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'ProjectSnippet', id: unreadable.id, ability: :read_snippet }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+
+ context 'with :with_api_entity_associations' do
+ it_behaves_like "redaction limits N+1 queries", limit: 14
+ end
+ end
+
+ context 'for personal snippets' do
+ let(:readable) { create(:personal_snippet, :private, author: user) }
+ let(:unreadable) { create(:personal_snippet, :private) }
+ let(:unredacted_results) { ar_relation(PersonalSnippet, readable, unreadable) }
+ let(:scope) { 'snippet_titles' }
+
+ it 'redacts the inaccessible snippet' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'PersonalSnippet', id: unreadable.id, ability: :read_snippet }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+
+ context 'with :with_api_entity_associations' do
+ it_behaves_like "redaction limits N+1 queries", limit: 4
+ end
+ end
+
+ context 'for commits' do
+ let(:readable) { accessible_project.commit }
+ let(:unreadable) { inaccessible_project.commit }
+ let(:unredacted_results) { kaminari_array(readable, unreadable) }
+ let(:scope) { 'commits' }
+
+ it 'redacts the inaccessible commit' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'Commit', id: unreadable.id, ability: :read_commit }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+ end
+
+ context 'for users' do
+ let(:other_user) { create(:user) }
+ let(:unredacted_results) { ar_relation(User, user, other_user) }
+ let(:scope) { 'users' }
+
+ it 'passes the users through' do
+ # Users are always visible to everyone
+ expect(result).to contain_exactly(user, other_user)
+ end
+ end
+end
+
+RSpec.shared_examples "redaction limits N+1 queries" do |limit:|
+ it 'does not exceed the query limit' do
+ # issuing the query to remove the data loading call
+ unredacted_results.to_a
+
+ # only the calls from the redaction are left
+ query = ActiveRecord::QueryRecorder.new { result }
+
+ # these are the project authorization calls, which are not preloaded
+ expect(query.count).to be <= limit
+ end
+end
diff --git a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
index 84dc2b20ddc..cc74c977064 100644
--- a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
+++ b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
@@ -1,23 +1,23 @@
# frozen_string_literal: true
RSpec.shared_examples 'search timeouts' do |scope|
+ let(:additional_params) { {} }
+
context 'when search times out' do
before do
- stub_feature_flags(search_page_vertical_nav: false)
allow_next_instance_of(SearchService) do |service|
allow(service).to receive(:search_objects).and_raise(ActiveRecord::QueryCanceled)
end
- visit(search_path(search: 'test', scope: scope))
+ visit(search_path(search: 'test', scope: scope, **additional_params))
end
it 'renders timeout information' do
- # expect(page).to have_content('This endpoint has been requested too many times.')
expect(page).to have_content('Your search timed out')
end
it 'sets tab count to 0' do
- expect(page.find('.search-filter .active')).to have_text('0')
+ expect(page.find('[data-testid="search-filter"] .active')).to have_text('0')
end
end
end
diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb
index d1e5046a39e..f0b72cfaee3 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -31,8 +31,8 @@ RSpec.shared_examples 'variable list' do |is_admin|
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Protected"] svg[data-testid="mobile-issue-close-icon"]')).to be_present
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']").text).to eq('key')
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Options')}']")).to have_content(s_('CiVariables|Protected'))
end
end
@@ -46,26 +46,26 @@ RSpec.shared_examples 'variable list' do |is_admin|
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="close-icon"]')).to be_present
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']").text).to eq('key')
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Options')}']")).not_to have_content(s_('CiVariables|Masked'))
end
end
it 'reveals and hides variables' do
page.within('[data-testid="ci-variable-table"]') do
expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
- expect(page).to have_content('*' * 17)
+ expect(page).to have_content('*' * 5)
click_button('Reveal value')
expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
expect(first('.js-ci-variable-row td[data-label="Value"]').text).to eq(variable.value)
- expect(page).not_to have_content('*' * 17)
+ expect(page).not_to have_content('*' * 5)
click_button('Hide value')
expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
- expect(page).to have_content('*' * 17)
+ expect(page).to have_content('*' * 5)
end
end
@@ -116,7 +116,8 @@ RSpec.shared_examples 'variable list' do |is_admin|
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="close-icon"]')).to be_present
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Options')}']")).to have_content(s_('CiVariables|Protected'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Options')}']")).not_to have_content(s_('CiVariables|Masked'))
end
end
@@ -144,7 +145,7 @@ RSpec.shared_examples 'variable list' do |is_admin|
end
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="mobile-issue-close-icon"]')).to be_present
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Options')}']")).to have_content(s_('CiVariables|Masked'))
end
end
diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
index f62c9c00006..8b3a344a841 100644
--- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
+++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
@@ -585,7 +585,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
end
context 'when full-text search is disabled' do
- let(:search_term) { 'somet' }
+ let(:search_term) { 'ometh' }
before do
stub_feature_flags(issues_full_text_search: false)
diff --git a/spec/support/shared_examples/graphql/mutations/incident_management/timeline_events_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/incident_management/timeline_events_shared_examples.rb
new file mode 100644
index 00000000000..fbfd1af2601
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/incident_management/timeline_events_shared_examples.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'timeline event mutation responds with validation error' do |error_message:|
+ it 'responds with a validation error' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to match_array([error_message])
+ end
+end
diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
index 0aa3bf8944f..bdd4dbfe209 100644
--- a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
@@ -78,7 +78,7 @@ RSpec.shared_examples 'a Note mutation when there are rate limit validation erro
context 'when the user is in the allowlist' do
before do
- stub_application_setting(notes_create_limit_allowlist: ["#{current_user.username}"])
+ stub_application_setting(notes_create_limit_allowlist: [current_user.username.to_s])
end
it_behaves_like 'a Note mutation that creates a Note'
diff --git a/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb b/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
index 3017f62a7c9..aadc061f175 100644
--- a/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
@@ -25,7 +25,7 @@ RSpec.shared_examples 'group and projects packages resolver' do
end
%w[CREATED_DESC NAME_DESC VERSION_DESC TYPE_ASC].each do |order|
- context "#{order}" do
+ context order.to_s do
let(:args) { { sort: order } }
it { is_expected.to eq([maven_package, conan_package]) }
@@ -33,7 +33,7 @@ RSpec.shared_examples 'group and projects packages resolver' do
end
%w[CREATED_ASC NAME_ASC VERSION_ASC TYPE_DESC].each do |order|
- context "#{order}" do
+ context order.to_s do
let(:args) { { sort: order } }
it { is_expected.to eq([conan_package, maven_package]) }
diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
index 2d7da9f9f00..67576a18c80 100644
--- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
@@ -86,4 +86,25 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
eq("`alpha` and `deprecated` arguments cannot be passed at the same time")
)
end
+
+ describe 'visible?' do
+ let(:ctx) { {} }
+
+ it 'defaults to true' do
+ expect(subject).to be_visible(ctx)
+ end
+
+ context 'when subject is deprecated' do
+ let(:arguments) { { deprecated: { milestone: '1.10', reason: :renamed } } }
+
+ it 'defaults to true' do
+ expect(subject(arguments)).to be_visible(ctx)
+ end
+
+ it 'returns false if `remove_deprecated` is true in context' do
+ ctx = { remove_deprecated: true }
+ expect(subject(arguments)).not_to be_visible(ctx)
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
index 2e00abe2f8e..6cdd7954b5f 100644
--- a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
+++ b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
@@ -129,6 +129,7 @@ RSpec.shared_examples_for 'collection cache helper' do
before do
allow(::Gitlab::Metrics::WebTransaction).to receive(:current).and_return(transaction)
allow(transaction).to receive(:increment)
+ allow(Gitlab::ApplicationContext).to receive(:current_context_attribute).with(any_args).and_call_original
allow(Gitlab::ApplicationContext).to receive(:current_context_attribute).with(:caller_id).and_return(caller_id)
end
diff --git a/spec/support/shared_examples/lib/email/email_shared_examples.rb b/spec/support/shared_examples/lib/email/email_shared_examples.rb
new file mode 100644
index 00000000000..26655a71fec
--- /dev/null
+++ b/spec/support/shared_examples/lib/email/email_shared_examples.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+# Set the particular setting as a key-value pair
+# Setting method is different depending on klass and must be defined in the calling spec
+def stub_email_setting(key_value_pairs)
+ case setting_name
+ when :incoming_email
+ stub_incoming_email_setting(key_value_pairs)
+ when :service_desk_email
+ stub_service_desk_email_setting(key_value_pairs)
+ end
+end
+
+RSpec.shared_examples_for 'enabled? method for email' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.enabled? }
+
+ where(:value, :address, :result) do
+ false | nil | false
+ false | 'replies+%{key}@example.com' | false
+ true | nil | false
+ true | 'replies+%{key}@example.com' | true
+ end
+
+ with_them do
+ before do
+ stub_email_setting(enabled: value)
+ stub_email_setting(address: address)
+ end
+
+ it { is_expected.to eq result }
+ end
+end
+
+RSpec.shared_examples_for 'supports_wildcard? method for email' do
+ subject { described_class.supports_wildcard? }
+
+ before do
+ stub_incoming_email_setting(address: value)
+ end
+
+ context 'when address contains the wildcard placeholder' do
+ let(:value) { 'replies+%{key}@example.com' }
+
+ it 'confirms that wildcard is supported' do
+ expect(subject).to be_truthy
+ end
+ end
+
+ context "when address doesn't contain the wildcard placeholder" do
+ let(:value) { 'replies@example.com' }
+
+ it 'returns that wildcard is not supported' do
+ expect(subject).to be_falsey
+ end
+ end
+
+ context 'when address is nil' do
+ let(:value) { nil }
+
+ it 'returns that wildcard is not supported' do
+ expect(subject).to be_falsey
+ end
+ end
+end
+
+RSpec.shared_examples_for 'unsubscribe_address method for email' do
+ before do
+ stub_incoming_email_setting(address: 'replies+%{key}@example.com')
+ end
+
+ it 'returns the address with interpolated reply key and unsubscribe suffix' do
+ expect(described_class.unsubscribe_address('key'))
+ .to eq("replies+key#{Gitlab::Email::Common::UNSUBSCRIBE_SUFFIX}@example.com")
+ end
+end
+
+RSpec.shared_examples_for 'key_from_fallback_message_id method for email' do
+ it 'returns reply key' do
+ expect(described_class.key_from_fallback_message_id('reply-key@localhost')).to eq('key')
+ end
+end
+
+RSpec.shared_examples_for 'supports_issue_creation? method for email' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.supports_issue_creation? }
+
+ where(:enabled_value, :supports_wildcard_value, :result) do
+ false | false | false
+ false | true | false
+ true | false | false
+ true | true | true
+ end
+
+ with_them do
+ before do
+ allow(described_class).to receive(:enabled?).and_return(enabled_value)
+ allow(described_class).to receive(:supports_wildcard?).and_return(supports_wildcard_value)
+ end
+
+ it { is_expected.to eq result }
+ end
+end
+
+RSpec.shared_examples_for 'reply_address method for email' do
+ before do
+ stub_incoming_email_setting(address: "replies+%{key}@example.com")
+ end
+
+ it "returns the address with an interpolated reply key" do
+ expect(described_class.reply_address("key")).to eq("replies+key@example.com")
+ end
+end
+
+RSpec.shared_examples_for 'scan_fallback_references method for email' do
+ let(:references) do
+ '<issue_1@localhost>' \
+ ' <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost>' \
+ ',<exchange@microsoft.com>'
+ end
+
+ it 'returns reply key' do
+ expect(described_class.scan_fallback_references(references))
+ .to eq(%w[issue_1@localhost
+ reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost
+ exchange@microsoft.com])
+ end
+end
+
+RSpec.shared_examples_for 'common email methods' do
+ it_behaves_like 'enabled? method for email'
+ it_behaves_like 'supports_wildcard? method for email'
+ it_behaves_like 'key_from_fallback_message_id method for email'
+ it_behaves_like 'supports_issue_creation? method for email'
+ it_behaves_like 'reply_address method for email'
+ it_behaves_like 'unsubscribe_address method for email'
+ it_behaves_like 'scan_fallback_references method for email'
+end
diff --git a/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb
index d14216ec5ff..22b4f9f583c 100644
--- a/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb
@@ -3,9 +3,6 @@
RSpec.shared_context 'reconfigures connection stack' do |db_config_name|
before do
skip_if_multiple_databases_not_setup
-
- # Due to lib/gitlab/database/load_balancing/configuration.rb:92 requiring RequestStore
- # we cannot use stub_feature_flags(force_no_sharing_primary_model: true)
Gitlab::Database.database_base_models.each do |_, model_class|
allow(model_class.load_balancer.configuration).to receive(:use_dedicated_connection?).and_return(true)
end
diff --git a/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb
index db2f2f2d0f0..e97b9ad969f 100644
--- a/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb
@@ -15,6 +15,16 @@ RSpec.shared_examples 'subscribes to event' do
it_behaves_like 'an idempotent worker'
end
+RSpec.shared_examples 'ignores the published event' do
+ include AfterNextHelpers
+
+ it 'does not consume the published event', :sidekiq_inline do
+ expect_next(described_class).not_to receive(:handle_event)
+
+ ::Gitlab::EventStore.publish(event)
+ end
+end
+
def consume_event(subscriber:, event:)
subscriber.new.perform(event.class.name, event.data)
end
diff --git a/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb
deleted file mode 100644
index fdca326dbea..00000000000
--- a/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'tracks assignment and records the subject' do |experiment, subject_type|
- before do
- stub_experiments(experiment => true)
- end
-
- it 'tracks the assignment', :experiment do
- expect(experiment(experiment))
- .to track(:assignment)
- .with_context(subject_type => subject)
- .on_next_instance
-
- action
- end
-
- it 'records the subject' do
- expect(Experiment).to receive(:add_subject).with(experiment.to_s, variant: anything, subject: subject)
-
- action
- end
-end
diff --git a/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb
new file mode 100644
index 00000000000..f26b9a4a7bd
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+def raw_repo_without_container(repository)
+ Gitlab::Git::Repository.new(repository.shard,
+ "#{repository.disk_path}.git",
+ repository.repo_type.identifier_for_container(repository.container),
+ repository.container.full_path)
+end
+
+RSpec.shared_examples 'Gitaly feature flag actors are inferred from repository' do
+ it 'captures correct actors' do
+ service.repository_actor = repository
+
+ expect(service.repository_actor.flipper_id).to eql(repository.flipper_id)
+
+ if expected_project.nil?
+ expect(service.project_actor).to be(nil)
+ else
+ expect(service.project_actor.flipper_id).to eql(expected_project.flipper_id)
+ end
+
+ if expected_group.nil?
+ expect(service.group_actor).to be(nil)
+ else
+ expect(service.group_actor.flipper_id).to eql(expected_group.flipper_id)
+ end
+ end
+
+ it 'does not issues SQL queries after the first invocation' do
+ service.repository_actor = repository
+
+ service.repository_actor
+ service.project_actor
+ service.group_actor
+
+ recorder = ActiveRecord::QueryRecorder.new do
+ 3.times do
+ service.repository_actor
+ service.project_actor
+ service.group_actor
+ end
+ end
+
+ expect(recorder.count).to be(0)
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb
index 4b4a7f4ce9d..a2a4c57d62e 100644
--- a/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb
@@ -52,7 +52,7 @@ RSpec.shared_examples 'acts as branch pipeline' do |jobs|
context 'when branch pipeline' do
let(:pipeline_branch) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch) }
- let(:pipeline) { service.execute!(:push).payload }
+ let(:pipeline) { service.execute(:push).payload }
it 'includes a job' do
expect(pipeline.builds.pluck(:name)).to match_array(jobs)
diff --git a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
index 71b32005c55..e0b411e1e2a 100644
--- a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
+++ b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
@@ -78,8 +78,8 @@ end
# Expects to following variables:
# - subject
# - sentry_api_response
-# - sentry_url, token - only if enabled_by_default: false
-RSpec.shared_examples 'Sentry API response size limit' do |enabled_by_default: false|
+# - sentry_url, token
+RSpec.shared_examples 'Sentry API response size limit' do
let(:invalid_deep_size) { instance_double(Gitlab::Utils::DeepSize, valid?: false) }
before do
@@ -89,35 +89,8 @@ RSpec.shared_examples 'Sentry API response size limit' do |enabled_by_default: f
.and_return(invalid_deep_size)
end
- if enabled_by_default
- it 'raises an exception when response is too large' do
- expect { subject }.to raise_error(ErrorTracking::SentryClient::ResponseInvalidSizeError,
- 'Sentry API response is too big. Limit is 1 MB.')
- end
- else
- context 'when guarded by feature flag' do
- let(:client) do
- ErrorTracking::SentryClient.new(sentry_url, token, validate_size_guarded_by_feature_flag: feature_flag)
- end
-
- context 'with feature flag enabled' do
- let(:feature_flag) { true }
-
- it 'raises an exception when response is too large' do
- expect { subject }.to raise_error(ErrorTracking::SentryClient::ResponseInvalidSizeError,
- 'Sentry API response is too big. Limit is 1 MB.')
- end
- end
-
- context 'with feature flag disabled' do
- let(:feature_flag) { false }
-
- it 'does not check the limit and thus not raise' do
- expect { subject }.not_to raise_error
-
- expect(Gitlab::Utils::DeepSize).not_to have_received(:new)
- end
- end
- end
+ it 'raises an exception when response is too large' do
+ expect { subject }.to raise_error(ErrorTracking::SentryClient::ResponseInvalidSizeError,
+ 'Sentry API response is too big. Limit is 1 MB.')
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 20ed380fb18..919311adc96 100644
--- a/spec/support/shared_examples/mailers/notify_shared_examples.rb
+++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb
@@ -44,12 +44,12 @@ end
RSpec.shared_examples 'an email with X-GitLab headers containing IDs' do
it 'has X-GitLab-*-ID header' do
- is_expected.to have_header "X-GitLab-#{model.class.name}-ID", "#{model.id}"
+ is_expected.to have_header "X-GitLab-#{model.class.name}-ID", model.id.to_s
end
it 'has X-GitLab-*-IID header if model has iid defined' do
if model.respond_to?(:iid)
- is_expected.to have_header "X-GitLab-#{model.class.name}-IID", "#{model.iid}"
+ is_expected.to have_header "X-GitLab-#{model.class.name}-IID", model.iid.to_s
else
expect(subject.header["X-GitLab-#{model.class.name}-IID"]).to eq nil
end
diff --git a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
index 3f187a7e9e4..ef4b08c7865 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
@@ -37,7 +37,8 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
Gitlab::WithRequestStore.with_request_store do
subscriber.sql(event)
- expected = if db_role == :primary
+ expected = case db_role
+ when :primary
transform_hash(expected_payload_defaults, {
db_count: record_query ? 1 : 0,
db_write_count: record_write_query ? 1 : 0,
@@ -53,7 +54,7 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_primary_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
+ when :replica
transform_hash(expected_payload_defaults, {
db_count: record_query ? 1 : 0,
db_write_count: record_write_query ? 1 : 0,
diff --git a/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb
new file mode 100644
index 00000000000..f0581333b28
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::BaseSlackNotification do |factory:|
+ describe '#execute' do
+ let_it_be(:project) { create(:project, :repository, :wiki_repo) }
+ let_it_be(:integration) { create(factory, branches_to_be_notified: 'all', project: project) }
+
+ before do
+ stub_request(:post, integration.webhook)
+ end
+
+ it 'uses only known events', :aggregate_failures do
+ described_class::SUPPORTED_EVENTS_FOR_USAGE_LOG.each do |action|
+ expect(
+ Gitlab::UsageDataCounters::HLLRedisCounter.known_event?("i_ecosystem_slack_service_#{action}_notification")
+ ).to be true
+ end
+ end
+
+ context 'when hook data includes a user object' do
+ let_it_be(:user) { create_default(:user) }
+
+ shared_examples 'increases the usage data counter' do |event_name|
+ subject(:execute) { integration.execute(data) }
+
+ it 'increases the usage data counter' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:track_event).with(event_name, values: user.id).and_call_original
+
+ execute
+ end
+
+ it_behaves_like 'Snowplow event tracking' do
+ let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
+ let(:category) { described_class.to_s }
+ let(:action) { 'perform_integrations_action' }
+ let(:namespace) { project.namespace }
+ let(:label) { 'redis_hll_counters.ecosystem.ecosystem_total_unique_counts_monthly' }
+ let(:property) { event_name }
+ end
+ end
+
+ context 'when event is not supported for usage log' do
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
+
+ it 'does not increase the usage data counter' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .not_to receive(:track_event).with('i_ecosystem_slack_service_pipeline_notification', values: user.id)
+
+ integration.execute(data)
+ end
+ end
+
+ context 'for issue notification' do
+ let_it_be(:issue) { create(:issue, project: project) }
+
+ let(:data) { issue.to_hook_data(user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_issue_notification'
+ end
+
+ context 'for push notification' do
+ let(:data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_push_notification'
+ end
+
+ context 'for deployment notification' do
+ let_it_be(:deployment) { create(:deployment, project: project, user: user) }
+
+ let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.current) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_deployment_notification'
+ end
+
+ context 'for wiki_page notification' do
+ let_it_be(:wiki_page) do
+ create(:wiki_page, wiki: project.wiki, message: 'user created page: Awesome wiki_page')
+ end
+
+ let(:data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') }
+
+ before do
+ # Skip this method that is not relevant to this test to prevent having
+ # to update project which is frozen
+ allow(project.wiki).to receive(:after_wiki_activity)
+ end
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_wiki_page_notification'
+ end
+
+ context 'for merge_request notification' do
+ let_it_be(:merge_request) { create(:merge_request, source_project: project) }
+
+ let(:data) { merge_request.to_hook_data(user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_merge_request_notification'
+ end
+
+ context 'for note notification' do
+ let_it_be(:issue_note) { create(:note_on_issue, project: project, note: 'issue note') }
+
+ let(:data) { Gitlab::DataBuilder::Note.build(issue_note, user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_note_notification'
+ end
+
+ context 'for tag_push notification' do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+ let(:newrev) { '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b' } # gitlab-test: git rev-parse refs/tags/v1.1.0
+ let(:ref) { 'refs/tags/v1.1.0' }
+ let(:data) do
+ Git::TagHooksService.new(project, user, change: { oldrev: oldrev, newrev: newrev, ref: ref }).send(:push_data)
+ end
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_tag_push_notification'
+ end
+
+ context 'for confidential note notification' do
+ let_it_be(:confidential_issue_note) do
+ create(:note_on_issue, project: project, note: 'issue note', confidential: true)
+ end
+
+ let(:data) { Gitlab::DataBuilder::Note.build(confidential_issue_note, user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_note_notification'
+ end
+
+ context 'for confidential issue notification' do
+ let_it_be(:issue) { create(:issue, project: project, confidential: true) }
+
+ let(:data) { issue.to_hook_data(user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_issue_notification'
+ end
+ end
+
+ context 'when hook data does not include a user' do
+ let(:data) { Gitlab::DataBuilder::Pipeline.build(create(:ci_pipeline, project: project)) }
+
+ it 'does not increase the usage data counter' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+
+ integration.execute(data)
+ end
+ 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 7512a9f2855..974fc8f402a 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
@@ -152,7 +152,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'issue events' do
- let_it_be(:issue) { create(:issue) }
+ let_it_be(:issue) { create(:issue, project: project) }
let(:data) { issue.to_hook_data(user) }
@@ -192,7 +192,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'merge request events' do
- let_it_be(:merge_request) { create(:merge_request) }
+ let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let(:data) { merge_request.to_hook_data(user) }
@@ -210,7 +210,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'wiki page events' do
- let_it_be(:wiki_page) { create(:wiki_page, wiki: project.wiki, message: 'user created page: Awesome wiki_page') }
+ let_it_be(:wiki_page) { create(:wiki_page, wiki: project.wiki, project: project, message: 'user created page: Awesome wiki_page') }
let(:data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') }
@@ -228,7 +228,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'deployment events' do
- let_it_be(:deployment) { create(:deployment) }
+ let_it_be(:deployment) { create(:deployment, project: project) }
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, 'created', Time.current) }
@@ -275,8 +275,8 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
describe 'Push events' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository, creator: user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
before do
allow(chat_integration).to receive_messages(
@@ -327,7 +327,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'on a protected branch' do
- before do
+ before(:all) do
create(:protected_branch, :create_branch_on_repository, project: project, name: 'a-protected-branch')
end
@@ -369,7 +369,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'on a protected branch with protected branches defined using wildcards' do
- before do
+ before(:all) do
create(:protected_branch, :create_branch_on_repository, repository_branch_name: '1-stable', project: project, name: '*-stable')
end
@@ -450,8 +450,8 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
describe 'Note events' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository, creator: user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
before do
allow(chat_integration).to receive_messages(
@@ -519,8 +519,8 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
describe 'Pipeline events' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository, creator: user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
let(:pipeline) do
create(:ci_pipeline,
project: project, status: status,
@@ -582,7 +582,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'on a protected branch' do
- before do
+ before(:all) do
create(:protected_branch, :create_branch_on_repository, project: project, name: 'a-protected-branch')
end
@@ -612,7 +612,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'on a protected branch with protected branches defined usin wildcards' do
- before do
+ before(:all) do
create(:protected_branch, :create_branch_on_repository, repository_branch_name: '1-stable', project: project, name: '*-stable')
end
@@ -673,7 +673,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
- let(:deployment) do
+ let_it_be(:deployment) do
create(:deployment, :success, project: project, sha: project.commit.sha, ref: project.default_branch)
end
@@ -692,11 +692,11 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
it_behaves_like "triggered #{integration_name} integration", event_type: "deployment"
context 'on a protected branch' do
- before do
+ before(:all) do
create(:protected_branch, :create_branch_on_repository, project: project, name: 'a-protected-branch')
end
- let(:deployment) do
+ let_it_be(:deployment) do
create(:deployment, :success, project: project, sha: project.commit.sha, ref: 'a-protected-branch')
end
diff --git a/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb b/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb
index 3d393e6dcb5..c6d6e00c781 100644
--- a/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'includes Limitable concern' do
describe '#exceeds_limits?' do
- let(:plan_limits) { create(:plan_limits, :default_plan) }
+ let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) }
context 'without plan limits configured' do
it { expect(subject.exceeds_limits?).to eq false }
@@ -26,7 +26,7 @@ RSpec.shared_examples 'includes Limitable concern' do
end
describe 'validations' do
- let(:plan_limits) { create(:plan_limits, :default_plan) }
+ let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) }
it { is_expected.to be_a(Limitable) }
diff --git a/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb b/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
index 174b8609337..ac34ee32c6d 100644
--- a/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
@@ -7,6 +7,11 @@ RSpec.shared_examples 'ttl_expirable' do
it_behaves_like 'having unique enum values'
+ describe 'default values', :freeze_time do
+ it { expect(described_class.new.read_at).to be_like_time(Time.zone.now) }
+ it { expect(described_class.new(read_at: 1.day.ago).read_at).to be_like_time(1.day.ago) }
+ end
+
describe 'validations' do
it { is_expected.to validate_presence_of(:status) }
end
@@ -38,7 +43,7 @@ RSpec.shared_examples 'ttl_expirable' do
end
end
- describe '#read', :freeze_time do
+ describe '#read!', :freeze_time do
let_it_be(:old_read_at) { 1.day.ago }
let_it_be(:item1) { create(class_symbol, read_at: old_read_at) }
diff --git a/spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb
new file mode 100644
index 00000000000..08fab45e41b
--- /dev/null
+++ b/spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::BaseCi do
+ describe 'default values' do
+ it { expect(subject.category).to eq(:ci) }
+ end
+end
diff --git a/spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb
new file mode 100644
index 00000000000..5d7e7633a23
--- /dev/null
+++ b/spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::BaseMonitoring do
+ describe 'default values' do
+ it { expect(subject.category).to eq(:monitoring) }
+ end
+end
diff --git a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
index e35ac9c0d0d..7dfdd24177e 100644
--- a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
@@ -6,6 +6,10 @@ RSpec.shared_examples Integrations::BaseSlashCommands do
it { is_expected.to have_many :chat_names }
end
+ describe 'default values' do
+ it { expect(subject.category).to eq(:chat) }
+ end
+
describe '#valid_token?' do
subject { described_class.new }
diff --git a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
index 31ec25249d7..a764d47d7c0 100644
--- a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
+++ b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
@@ -38,7 +38,7 @@ RSpec.shared_examples Integrations::HasWebHook do
end
describe '#url_variables' do
- it 'returns a string' do
+ it 'returns a hash' do
expect(integration.url_variables).to be_a(Hash)
end
end
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 23026167b19..5be0f6349ea 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
@@ -199,7 +199,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
expect(component_file)
.to receive(:update_column)
- .with(:file_store, ::Packages::PackageFileUploader::Store::LOCAL)
+ .with('file_store', ::Packages::PackageFileUploader::Store::LOCAL)
.and_call_original
expect { subject }.to change { component_file.size }.from(nil).to(74)
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index b1aa90449e1..7e69a6663d5 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -161,9 +161,10 @@ RSpec.shared_examples 'wiki model' do
let(:wiki_pages) { subject.list_pages }
before do
- subject.create_page('index', 'This is an index')
+ # The order is intentional
subject.create_page('index2', 'This is an index2')
- subject.create_page('an index3', 'This is an index3')
+ subject.create_page('index', 'This is an index')
+ subject.create_page('index3', 'This is an index3')
end
it 'returns an array of WikiPage instances' do
@@ -183,13 +184,47 @@ RSpec.shared_examples 'wiki model' do
context 'with limit option' do
it 'returns limited set of pages' do
- expect(subject.list_pages(limit: 1).count).to eq(1)
+ expect(
+ subject.list_pages(limit: 1).map(&:title)
+ ).to eql(%w[index])
+ end
+
+ it 'returns all set of pages if limit is more than the total pages' do
+ expect(subject.list_pages(limit: 4).count).to eq(3)
+ end
+
+ it 'returns all set of pages if limit is 0' do
+ expect(subject.list_pages(limit: 0).count).to eq(3)
+ end
+ end
+
+ context 'with offset option' do
+ it 'returns offset-ed set of pages' do
+ expect(
+ subject.list_pages(offset: 1).map(&:title)
+ ).to eq(%w[index2 index3])
+
+ expect(
+ subject.list_pages(offset: 2).map(&:title)
+ ).to eq(["index3"])
+ expect(subject.list_pages(offset: 3).count).to eq(0)
+ expect(subject.list_pages(offset: 4).count).to eq(0)
+ end
+
+ it 'returns all set of pages if offset is 0' do
+ expect(subject.list_pages(offset: 0).count).to eq(3)
+ end
+
+ it 'can combines with limit' do
+ expect(
+ subject.list_pages(offset: 1, limit: 1).map(&:title)
+ ).to eq(["index2"])
end
end
context 'with sorting options' do
it 'returns pages sorted by title by default' do
- pages = ['an index3', 'index', 'index2']
+ pages = %w[index index2 index3]
expect(subject.list_pages.map(&:title)).to eq(pages)
expect(subject.list_pages(direction: 'desc').map(&:title)).to eq(pages.reverse)
@@ -200,24 +235,14 @@ RSpec.shared_examples 'wiki model' do
let(:pages) { subject.list_pages(load_content: true) }
it 'loads WikiPage content' do
- expect(pages.first.content).to eq('This is an index3')
- expect(pages.second.content).to eq('This is an index')
- expect(pages.third.content).to eq('This is an index2')
+ expect(pages.first.content).to eq('This is an index')
+ expect(pages.second.content).to eq('This is an index2')
+ expect(pages.third.content).to eq('This is an index3')
end
end
end
- context 'list pages with legacy wiki rpcs' do
- before do
- stub_feature_flags(wiki_list_page_with_normal_repository_rpcs: false)
- end
-
- it_behaves_like 'wiki model #list_pages'
- end
-
- context 'list pages with normal repository rpcs' do
- it_behaves_like 'wiki model #list_pages'
- end
+ it_behaves_like 'wiki model #list_pages'
end
describe '#sidebar_entries' do
@@ -821,29 +846,6 @@ RSpec.shared_examples 'wiki model' do
end
end
- describe '#ensure_repository' do
- context 'if the repository exists' do
- it 'does not create the repository' do
- expect(subject.repository.exists?).to eq(true)
- expect(subject.repository.raw).not_to receive(:create_repository)
-
- subject.ensure_repository
- end
- end
-
- context 'if the repository does not exist' do
- let(:wiki_container) { wiki_container_without_repo }
-
- it 'creates the repository' do
- expect(subject.repository.exists?).to eq(false)
-
- subject.ensure_repository
-
- expect(subject.repository.exists?).to eq(true)
- end
- end
- end
-
describe '#hook_attrs' do
it 'returns a hash with values' do
expect(subject.hook_attrs).to be_a Hash
diff --git a/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb b/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb
index e725de8ad31..f5431b29ee2 100644
--- a/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb
@@ -12,49 +12,60 @@ RSpec.shared_examples 'does not exceed the issuable size limit' do
project.add_maintainer(user3)
end
- context 'when feature flag is turned on' do
- context "when the number of users of issuable does exceed the limit" do
- before do
- stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 2)
+ context "when the number of users of issuable does exceed the limit" do
+ before do
+ stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 2)
+ end
+
+ it 'will not add more than the allowed number of users' do
+ allow_next_instance_of(update_service) do |service|
+ expect(service).not_to receive(:execute)
end
- it 'will not add more than the allowed number of users' do
- allow_next_instance_of(update_service) do |service|
- expect(service).not_to receive(:execute)
- end
+ note = described_class.new(project, user, opts.merge(
+ note: note_text,
+ noteable_type: noteable_type,
+ noteable_id: issuable.id,
+ confidential: false
+ )).execute
- note = described_class.new(project, user, opts.merge(
- note: note_text,
- noteable_type: noteable_type,
- noteable_id: issuable.id,
- confidential: false
- )).execute
+ expect(note.errors[:validation]).to match_array([validation_message])
+ end
+ end
- expect(note.errors[:validation]).to match_array([validation_message])
- end
+ context "when the number of users does not exceed the limit" do
+ before do
+ stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 6)
end
- context "when the number of users does not exceed the limit" do
- before do
- stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 6)
+ it 'calls execute and does not return an error' do
+ allow_next_instance_of(update_service) do |service|
+ expect(service).to receive(:execute).and_call_original
end
- it 'calls execute and does not return an error' do
- allow_next_instance_of(update_service) do |service|
- expect(service).to receive(:execute).and_call_original
- end
-
- note = described_class.new(project, user, opts.merge(
- note: note_text,
- noteable_type: noteable_type,
- noteable_id: issuable.id,
- confidential: false
- )).execute
+ note = described_class.new(project, user, opts.merge(
+ note: note_text,
+ noteable_type: noteable_type,
+ noteable_id: issuable.id,
+ confidential: false
+ )).execute
- expect(note.errors[:validation]).to be_empty
- end
+ expect(note.errors[:validation]).to be_empty
end
end
+end
+
+RSpec.shared_examples 'does not exceed the issuable size limit with ff off' do
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:user3) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ project.add_maintainer(user1)
+ project.add_maintainer(user2)
+ project.add_maintainer(user3)
+ end
context 'when feature flag is off' do
before do
diff --git a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
index 59e641e2af6..2170025824f 100644
--- a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
+++ b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
@@ -1,21 +1,98 @@
# frozen_string_literal: true
RSpec.shared_examples 'GET resource access tokens available' do
- let_it_be(:active_resource_access_token) { create(:personal_access_token, user: bot_user) }
+ let_it_be(:active_resource_access_token) { create(:personal_access_token, user: access_token_user) }
- it 'retrieves active resource access tokens' do
- subject
+ it 'retrieves active access tokens' do
+ get_access_tokens
- token_entities = assigns(:active_resource_access_tokens)
+ token_entities = assigns(:active_access_tokens)
expect(token_entities.length).to eq(1)
expect(token_entities[0][:name]).to eq(active_resource_access_token.name)
end
it 'lists all available scopes' do
- subject
+ get_access_tokens
expect(assigns(:scopes)).to eq(Gitlab::Auth.resource_bot_scopes)
end
+
+ it 'returns for json response' do
+ get_access_tokens_json
+
+ expect(json_response.count).to eq(1)
+ end
+end
+
+RSpec.shared_examples 'GET access tokens are paginated and ordered' do
+ before do
+ create(:personal_access_token, user: access_token_user)
+ end
+
+ context "when multiple access tokens are returned" do
+ before do
+ allow(Kaminari.config).to receive(:default_per_page).and_return(1)
+ create(:personal_access_token, user: access_token_user)
+ end
+
+ it "returns paginated response", :aggregate_failures do
+ get_access_tokens_with_page
+ expect(assigns(:active_access_tokens).count).to eq(1)
+
+ expect_header('X-Per-Page', '1')
+ expect_header('X-Page', '1')
+ expect_header('X-Next-Page', '2')
+ expect_header('X-Total', '2')
+ end
+ end
+
+ context "when access_token_pagination feature flag is disabled" do
+ before do
+ stub_feature_flags(access_token_pagination: false)
+ create(:personal_access_token, user: access_token_user)
+ end
+
+ it "returns all tokens in system" do
+ get_access_tokens_with_page
+ expect(assigns(:active_access_tokens).count).to eq(2)
+ end
+ end
+
+ context "when tokens returned are ordered" do
+ let(:expires_1_day_from_now) { 1.day.from_now.to_date }
+ let(:expires_2_day_from_now) { 2.days.from_now.to_date }
+
+ before do
+ create(:personal_access_token, user: access_token_user, name: "Token1", expires_at: expires_1_day_from_now)
+ create(:personal_access_token, user: access_token_user, name: "Token2", expires_at: expires_2_day_from_now)
+ end
+
+ it "orders token list ascending on expires_at" do
+ get_access_tokens
+
+ first_token = assigns(:active_access_tokens).first.as_json
+ expect(first_token['name']).to eq("Token1")
+ expect(first_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+ end
+
+ it "orders tokens on id in case token has same expires_at" do
+ create(:personal_access_token, user: access_token_user, name: "Token3", expires_at: expires_1_day_from_now)
+
+ get_access_tokens
+
+ first_token = assigns(:active_access_tokens).first.as_json
+ expect(first_token['name']).to eq("Token3")
+ expect(first_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+
+ second_token = assigns(:active_access_tokens).second.as_json
+ expect(second_token['name']).to eq("Token1")
+ expect(second_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+ end
+ end
+
+ def expect_header(header_name, header_val)
+ expect(response.headers[header_name]).to eq(header_val)
+ end
end
RSpec.shared_examples 'POST resource access tokens available' do
@@ -83,7 +160,7 @@ end
RSpec.shared_examples 'PUT resource access tokens available' do
it 'calls delete user worker' do
- expect(DeleteUserWorker).to receive(:perform_async).with(user.id, bot_user.id, skip_authorization: true)
+ expect(DeleteUserWorker).to receive(:perform_async).with(user.id, access_token_user.id, skip_authorization: true)
subject
end
@@ -91,34 +168,12 @@ RSpec.shared_examples 'PUT resource access tokens available' do
it 'removes membership of bot user' do
subject
- expect(resource.reload.bots).not_to include(bot_user)
+ expect(resource.reload.bots).not_to include(access_token_user)
end
- context 'when user_destroy_with_limited_execution_time_worker is enabled' do
- it 'creates GhostUserMigration records to handle migration in a worker' do
- expect { subject }.to(
- change { Users::GhostUserMigration.count }.from(0).to(1))
- end
- end
-
- context 'when user_destroy_with_limited_execution_time_worker is disabled' do
- before do
- stub_feature_flags(user_destroy_with_limited_execution_time_worker: false)
- end
-
- it 'converts issuables of the bot user to ghost user' do
- issue = create(:issue, author: bot_user)
-
- subject
-
- expect(issue.reload.author.ghost?).to be true
- end
-
- it 'deletes project bot user' do
- subject
-
- expect(User.exists?(bot_user.id)).to be_falsy
- end
+ it 'creates GhostUserMigration records to handle migration in a worker' do
+ expect { subject }.to(
+ change { Users::GhostUserMigration.count }.from(0).to(1))
end
context 'when unsuccessful' do
diff --git a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
index 32562aef8d2..f577e2ad323 100644
--- a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
@@ -15,7 +15,7 @@ RSpec.shared_examples 'with cross-reference system notes' do
new_merge_request.project.add_developer(user)
hidden_merge_request = create(:merge_request)
- new_cross_reference = "test commit #{hidden_merge_request.project.commit}"
+ new_cross_reference = "test commit #{hidden_merge_request.project.commit.to_reference(project)}"
new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference)
create(:system_note_metadata, note: new_note, action: 'cross_reference')
end
diff --git a/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb
index 22805cf7aed..bb492425fd7 100644
--- a/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb
@@ -1,13 +1,15 @@
# frozen_string_literal: true
# Requires `query(params)` , `user`, `issuable_data` and `issuable` bindings
-RSpec.shared_examples 'query with a search term' do
+RSpec.shared_examples 'query with a search term' do |fields = [:DESCRIPTION]|
+ let(:search_term) { 'bar' }
+ let(:ids) { graphql_dig_at(issuable_data, :node, :id) }
+
it 'returns only matching issuables' do
- filter_params = { search: 'bar', in: [:DESCRIPTION] }
+ filter_params = { search: search_term, in: fields }
graphql_query = query(filter_params)
post_graphql(graphql_query, current_user: user)
- ids = graphql_dig_at(issuable_data, :node, :id)
expect(ids).to contain_exactly(issuable.to_global_id.to_s)
end
diff --git a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
new file mode 100644
index 00000000000..5469fd80a4f
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
@@ -0,0 +1,170 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'graphql issue list request spec' do
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_query
+ end
+ end
+
+ describe 'filters' do
+ context 'when filtering by assignees' do
+ context 'when both assignee_username filters are provided' do
+ let(:issue_filter_params) do
+ { assignee_username: current_user.username, assignee_usernames: [current_user.username] }
+ end
+
+ it 'returns a mutually exclusive param error' do
+ post_query
+
+ expect_graphql_errors_to_include(
+ 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.'
+ )
+ end
+ end
+
+ context 'when filtering by a negated argument' do
+ let(:issue_filter_params) { { not: { assignee_usernames: [current_user.username] } } }
+
+ it 'returns correctly filtered issues' do
+ post_query
+
+ expect(issue_ids).to match_array(expected_negated_assignee_issues.map { |i| i.to_gid.to_s })
+ end
+ end
+ end
+
+ context 'when filtering by unioned arguments' do
+ let(:issue_filter_params) { { or: { assignee_usernames: [current_user.username, another_user.username] } } }
+
+ it 'returns correctly filtered issues' do
+ post_query
+
+ expect(issue_ids).to match_array(expected_unioned_assignee_issues.map { |i| i.to_gid.to_s })
+ end
+
+ context 'when argument is blank' do
+ let(:issue_filter_params) { { or: {} } }
+
+ it 'does not raise an error' do
+ post_query
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ context 'when feature flag is disabled' do
+ it 'returns an error' do
+ stub_feature_flags(or_issuable_queries: false)
+
+ post_query
+
+ expect_graphql_errors_to_include(
+ "'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled."
+ )
+ end
+ end
+ end
+
+ context 'when filtering by a blank negated argument' do
+ let(:issue_filter_params) { { not: {} } }
+
+ it 'does not raise an error' do
+ post_query
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ context 'when filtering by reaction emoji' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:value, :issue_list) do
+ 'thumbsup' | lazy { voted_issues }
+ 'ANY' | lazy { voted_issues }
+ 'any' | lazy { voted_issues }
+ 'AnY' | lazy { voted_issues }
+ 'NONE' | lazy { no_award_issues }
+ 'thumbsdown' | lazy { [] }
+ end
+
+ with_them do
+ let(:issue_filter_params) { { my_reaction_emoji: value } }
+ let(:gids) { to_gid_list(issue_list) }
+
+ it 'returns correctly filtered issues' do
+ post_query
+
+ expect(issue_ids).to match_array(gids)
+ end
+ end
+ end
+
+ context 'when filtering by search' do
+ it_behaves_like 'query with a search term', [:TITLE] do
+ let(:search_term) { search_title_term }
+ let(:issuable_data) { issues_data }
+ let(:user) { current_user }
+ let(:issuable) { title_search_issue }
+ let(:ids) { issue_ids }
+ end
+ end
+ end
+
+ describe 'sorting and pagination' do
+ context 'when sorting by severity' do
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :SEVERITY_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_severity_sorted_asc) }
+ end
+ end
+
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :SEVERITY_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_severity_sorted_asc.reverse) }
+ end
+ end
+ end
+
+ context 'when sorting by priority' do
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :PRIORITY_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_priority_sorted_asc) }
+ end
+ end
+
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :PRIORITY_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_priority_sorted_desc) }
+ end
+ end
+ end
+ end
+
+ it 'includes a web_url' do
+ post_query
+
+ expect(issues_data[0]['webUrl']).to be_present
+ end
+
+ it 'includes discussion locked' do
+ post_query
+
+ expect(issues_data).to contain_exactly(
+ *locked_discussion_issues.map { |i| hash_including('id' => i.to_gid.to_s, 'discussionLocked' => true) },
+ *unlocked_discussion_issues.map { |i| hash_including('id' => i.to_gid.to_s, 'discussionLocked' => false) }
+ )
+ end
+
+ def to_gid_list(instance_list)
+ instance_list.map { |instance| instance.to_gid.to_s }
+ end
+end
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 1b609915f32..fb4aacfd7a9 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
@@ -114,7 +114,7 @@ RSpec.shared_examples 'group and project packages query' do
end
[:CREATED_ASC, :NAME_ASC, :VERSION_ASC, :TYPE_ASC, :CREATED_DESC, :NAME_DESC, :VERSION_DESC, :TYPE_DESC].each do |order|
- context "#{order}" do
+ context order.to_s do
let(:sorted_packages) { packages_order_map.fetch(order) }
it_behaves_like 'sorted paginated query' do
diff --git a/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb
new file mode 100644
index 00000000000..54cc13fac94
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'perform graphql requests for AccessLevel type objects' do |access_level_kind|
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user, maintainer_projects: [project]) }
+ let_it_be(:variables) { { path: project.full_path } }
+
+ let(:fields) { all_graphql_fields_for("#{access_level_kind.to_s.classify}AccessLevel", max_depth: 2) }
+ let(:access_levels) { protected_branch.public_send("#{access_level_kind}_access_levels") }
+ let(:access_levels_count) { access_levels.size }
+ let(:maintainer_access_level) { access_levels.for_role.first }
+ let(:maintainer_access_level_data) { access_levels_data.first }
+ let(:access_levels_data) do
+ graphql_data_at('project',
+ 'branchRules',
+ 'nodes',
+ 0,
+ 'branchProtection',
+ "#{access_level_kind.to_s.camelize(:lower)}AccessLevels",
+ 'nodes')
+ end
+
+ let(:query) do
+ <<~GQL
+ query($path: ID!) {
+ project(fullPath: $path) {
+ branchRules(first: 1) {
+ nodes {
+ branchProtection {
+ #{access_level_kind.to_s.camelize(:lower)}AccessLevels {
+ nodes {
+ #{fields}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ context 'when request AccessLevel type objects as a guest user' do
+ let_it_be(:protected_branch) { create(:protected_branch, project: project) }
+
+ before do
+ project.add_guest(current_user)
+
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(access_levels_data).not_to be_present }
+ end
+
+ context 'when request AccessLevel type objects as a maintainer' do
+ let_it_be(:protected_branch) do
+ create(:protected_branch, "maintainers_can_#{access_level_kind}", project: project)
+ end
+
+ before do
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns all the access level attributes' do
+ expect(maintainer_access_level_data['accessLevel']).to eq(maintainer_access_level.access_level)
+ expect(maintainer_access_level_data['accessLevelDescription']).to eq(maintainer_access_level.humanize)
+ expect(maintainer_access_level_data.dig('group', 'name')).to be_nil
+ expect(maintainer_access_level_data.dig('user', 'name')).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/issues_shared_examples.rb b/spec/support/shared_examples/requests/api/issues_shared_examples.rb
index 991dbced02d..6328fb9cd8a 100644
--- a/spec/support/shared_examples/requests/api/issues_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/issues_shared_examples.rb
@@ -37,7 +37,7 @@ RSpec.shared_examples 'labeled issues with labels and label_name params' do
context 'negation' do
context 'array of labeled issues when all labels match with negation' do
- let(:params) { { labels: "#{label.title},#{label_b.title}", not: { labels: "#{label_c.title}" } } }
+ let(:params) { { labels: "#{label.title},#{label_b.title}", not: { labels: label_c.title.to_s } } }
it_behaves_like 'returns negated label names'
end
diff --git a/spec/support/shared_examples/requests/api/members_shared_examples.rb b/spec/support/shared_examples/requests/api/members_shared_examples.rb
index fce75c29971..9136f60eb93 100644
--- a/spec/support/shared_examples/requests/api/members_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/members_shared_examples.rb
@@ -11,3 +11,11 @@ RSpec.shared_examples 'a 404 response when source is private' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+RSpec.shared_examples 'a 403 response when user does not have rights to manage members of a specific access level' do
+ it 'returns 403' do
+ route
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
index fa111ca5811..d749479544d 100644
--- a/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
@@ -5,6 +5,7 @@ RSpec.shared_examples 'multiple and scoped issue boards' do |route_definition|
context 'multiple issue boards' do
before do
+ stub_feature_flags(apollo_boards: false)
board_parent.add_reporter(user)
stub_licensed_features(multiple_group_issue_boards: true)
end
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index 8479493911b..11f9565989f 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -179,7 +179,8 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end
end
- if parent_type == 'projects'
+ case parent_type
+ when 'projects'
context 'by a project owner' do
let(:user) { project.first_owner }
@@ -211,7 +212,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
expect(Time.parse(json_response['updated_at'])).to be_like_time(creation_time)
end
end
- elsif parent_type == 'groups'
+ when 'groups'
context 'by a group owner' do
it 'sets the creation time on the new note' do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params
@@ -288,7 +289,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end
it 'allows user in allow-list to create notes' do
- stub_application_setting(notes_create_limit_allowlist: ["#{user.username}"])
+ stub_application_setting(notes_create_limit_allowlist: [user.username.to_s])
subject
expect(response).to have_gitlab_http_status(:created)
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 11e19d8d067..a9b44015206 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
@@ -221,6 +221,27 @@ RSpec.shared_examples 'rejects PyPI access with unknown group id' do
end
end
+RSpec.shared_examples 'allow access for everyone with public package_registry_access_level' do
+ context 'with private project but public access to package registry' do
+ before do
+ project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+ project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC)
+ end
+
+ context 'as non-member user' do
+ let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'as anonymous' do
+ let(:headers) { {} }
+
+ it_behaves_like 'returning response status', :success
+ end
+ end
+end
+
RSpec.shared_examples 'pypi simple API endpoint' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
index 544a0ed8fdd..bdff2c65691 100644
--- a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
@@ -63,9 +63,9 @@ RSpec.shared_examples 'redirects to version download' do |user_type, status, add
it 'returns a valid response' do
subject
- expect(request.url).to include 'module-1/system/download'
+ expect(request.url).to include "#{package.name}/download"
expect(response.headers).to include 'Location'
- expect(response.headers['Location']).to include 'module-1/system/1.0.1/download'
+ expect(response.headers['Location']).to include "#{package.name}/1.0.1/download"
end
end
end
diff --git a/spec/support/shared_examples/services/alert_management_shared_examples.rb b/spec/support/shared_examples/services/alert_management_shared_examples.rb
index 571cb7dc03d..b46ace1824a 100644
--- a/spec/support/shared_examples/services/alert_management_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb
@@ -72,8 +72,8 @@ RSpec.shared_examples 'processes one firing and one resolved prometheus alerts'
.and change(Note, :count).by(1)
expect(subject).to be_success
- expect(subject.payload[:alerts]).to all(be_a_kind_of(AlertManagement::Alert))
- expect(subject.payload[:alerts].size).to eq(1)
+ expect(subject.payload).to eq({})
+ expect(subject.http_status).to eq(:created)
end
it_behaves_like 'processes incident issues'
diff --git a/spec/support/shared_examples/services/base_rpm_service_shared_examples.rb b/spec/support/shared_examples/services/base_rpm_service_shared_examples.rb
deleted file mode 100644
index c9520852a5b..00000000000
--- a/spec/support/shared_examples/services/base_rpm_service_shared_examples.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'handling rpm xml file' do
- include_context 'with rpm package data'
-
- let(:xml) { nil }
- let(:data) { {} }
-
- context 'when generate empty xml' do
- it 'generate expected xml' do
- expect(subject).to eq(empty_xml)
- end
- end
-
- context 'when updating existing xml' do
- let(:xml) { empty_xml }
- let(:data) { xml_update_params }
-
- shared_examples 'changing root tag attribute' do
- it "increment previous 'packages' value by 1" do
- previous_value = Nokogiri::XML(xml).at(described_class::ROOT_TAG).attributes["packages"].value.to_i
- new_value = Nokogiri::XML(subject).at(described_class::ROOT_TAG).attributes["packages"].value.to_i
-
- expect(previous_value + 1).to eq(new_value)
- end
- end
-
- it 'generate valid xml add expected xml node to existing xml' do
- # Have one root attribute
- result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
- expect(result.children.count).to eq(1)
-
- # Root node has 1 child with generated node
- expect(result.xpath("//#{described_class::ROOT_TAG}/package").count).to eq(1)
- end
-
- context 'when empty xml' do
- it_behaves_like 'changing root tag attribute'
- end
-
- context 'when xml has children' do
- let(:xml) { described_class.new(xml: empty_xml, data: data).execute }
-
- it 'has children nodes' do
- result = Nokogiri::XML::Document.parse(xml).remove_namespaces!
- expect(result.children.count).to be > 0
- end
-
- it_behaves_like 'changing root tag attribute'
- end
- end
-end
diff --git a/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb b/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb
new file mode 100644
index 00000000000..c38ca6a3bf0
--- /dev/null
+++ b/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'listing issuable discussions' do |user_role, internal_discussion_count, total_discussions_count|
+ before_all do
+ create_notes(issuable, "some user comment")
+ end
+
+ context 'when user cannot read issue' do
+ it "returns no notes" do
+ expect(discussions_service.execute).to be_empty
+ end
+ end
+
+ context 'when user can read issuable' do
+ before do
+ group.add_developer(current_user)
+ end
+
+ context 'with paginated results' do
+ let(:finder_params_for_issuable) { { per_page: 2 } }
+ let(:next_page_cursor) { { cursor: discussions_service.paginator.cursor_for_next_page } }
+
+ it "returns next page notes" do
+ next_page_discussions_service = described_class.new(current_user, issuable,
+ finder_params_for_issuable.merge(next_page_cursor))
+ discussions = next_page_discussions_service.execute
+
+ expect(discussions.count).to eq(2)
+ expect(discussions.first.notes.map(&:note)).to match_array(["added #{label.to_reference} label"])
+ expect(discussions.second.notes.map(&:note)).to match_array(["removed #{label.to_reference} label"])
+ end
+ end
+
+ # confidential notes are currently available only on issues and epics
+ context 'and cannot read confidential notes' do
+ before do
+ group.add_member(current_user, user_role)
+ end
+
+ it "returns non confidential notes" do
+ discussions = discussions_service.execute
+
+ non_conf_discussion_count = total_discussions_count - internal_discussion_count
+ expect(discussions.count).to eq(non_conf_discussion_count)
+ expect(discussions.count { |disc| disc.notes.any?(&:confidential) }).to eq(0)
+ expect(discussions.count { |disc| !disc.notes.any?(&:confidential) }).to eq(non_conf_discussion_count)
+ end
+ end
+
+ # confidential notes are currently available only on issues and epics
+ context 'and can read confidential notes' do
+ it "returns all notes" do
+ discussions = discussions_service.execute
+
+ expect(discussions.count).to eq(total_discussions_count)
+ expect(discussions.count { |disc| disc.notes.any?(&:confidential) }).to eq(internal_discussion_count)
+ non_conf_discussion_count = total_discussions_count - internal_discussion_count
+ expect(discussions.count { |disc| !disc.notes.any?(&:confidential) }).to eq(non_conf_discussion_count)
+ end
+ end
+
+ context 'and system notes only' do
+ let(:finder_params_for_issuable) { { notes_filter: UserPreference::NOTES_FILTERS[:only_activity] } }
+
+ it "returns system notes" do
+ discussions = discussions_service.execute
+
+ expect(discussions.count { |disc| disc.notes.any?(&:system) }).to be > 0
+ expect(discussions.count { |disc| !disc.notes.any?(&:system) }).to eq(0)
+ end
+ end
+
+ context 'and user comments only' do
+ let(:finder_params_for_issuable) { { notes_filter: UserPreference::NOTES_FILTERS[:only_comments] } }
+
+ it "returns user comments" do
+ discussions = discussions_service.execute
+
+ expect(discussions.count { |disc| disc.notes.any?(&:system) }).to eq(0)
+ expect(discussions.count { |disc| !disc.notes.any?(&:system) }).to be > 0
+ end
+ end
+ end
+end
+
+def create_notes(issuable, note_body)
+ assoc_name = issuable.to_ability_name
+
+ create(:note, system: true, project: issuable.project, noteable: issuable)
+
+ first_discussion = create(:discussion_note_on_issue, noteable: issuable, project: issuable.project, note: note_body)
+ create(:note,
+ discussion_id: first_discussion.discussion_id, noteable: issuable,
+ project: issuable.project, note: "reply on #{note_body}")
+
+ create(:resource_label_event, user: current_user, "#{assoc_name}": issuable, label: label, action: 'add')
+ create(:resource_label_event, user: current_user, "#{assoc_name}": issuable, label: label, action: 'remove')
+
+ unless issuable.is_a?(Epic)
+ create(:resource_milestone_event, "#{assoc_name}": issuable, milestone: milestone, action: 'add')
+ create(:resource_milestone_event, "#{assoc_name}": issuable, milestone: milestone, action: 'remove')
+ end
+
+ # confidential notes are currently available only on issues and epics
+ return unless issuable.is_a?(Issue) || issuable.is_a?(Epic)
+
+ first_internal_discussion = create(:discussion_note_on_issue, :confidential,
+ noteable: issuable, project: issuable.project, note: "confidential #{note_body}")
+ create(:note, :confidential,
+ discussion_id: first_internal_discussion.discussion_id, noteable: issuable,
+ project: issuable.project, note: "reply on confidential #{note_body}")
+end
diff --git a/spec/support/shared_examples/services/merge_status_updated_trigger_shared_examples.rb b/spec/support/shared_examples/services/merge_status_updated_trigger_shared_examples.rb
new file mode 100644
index 00000000000..97e3b0a44a7
--- /dev/null
+++ b/spec/support/shared_examples/services/merge_status_updated_trigger_shared_examples.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
+ specify do
+ expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(merge_request)
+
+ action
+ end
+end
+
+RSpec.shared_examples 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
+ specify do
+ expect(GraphqlTriggers).not_to receive(:merge_request_merge_status_updated)
+
+ action
+ end
+end
diff --git a/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb b/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb
index 09820593cdb..46a1f4b6598 100644
--- a/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb
@@ -20,7 +20,7 @@ RSpec.shared_examples_for 'dismissing user callout' do |model|
old_time = 1.day.ago
new_time = Time.current
attributes = params.merge(dismissed_at: old_time, user: user)
- existing_callout = create("#{model.name.split('::').last.underscore}".to_sym, attributes)
+ existing_callout = create(model.name.split('::').last.underscore.to_s.to_sym, attributes)
expect { execute }.to change { existing_callout.reload.dismissed_at }.from(old_time).to(new_time)
end
diff --git a/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb b/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb
new file mode 100644
index 00000000000..ac17915c15a
--- /dev/null
+++ b/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples "setting work item's milestone" do
+ context "when 'milestone' param does not exist" do
+ let(:params) { {} }
+
+ it "does not set the work item's milestone" do
+ expect { execute_callback }.to not_change(work_item, :milestone)
+ end
+ end
+
+ context "when 'milestone' is not in the work item's project's hierarchy" do
+ let(:another_group_milestone) { create(:milestone, group: create(:group)) }
+ let(:params) { { milestone_id: another_group_milestone.id } }
+
+ it "does not set the work item's milestone" do
+ expect { execute_callback }.to not_change(work_item, :milestone)
+ end
+ end
+
+ context 'when assigning a group milestone' do
+ let(:params) { { milestone_id: group_milestone.id } }
+
+ it "sets the work item's milestone" do
+ expect { execute_callback }
+ .to change(work_item, :milestone)
+ .from(nil)
+ .to(group_milestone)
+ end
+ end
+
+ context 'when assigning a project milestone' do
+ let(:params) { { milestone_id: project_milestone.id } }
+
+ it "sets the work item's milestone" do
+ expect { execute_callback }
+ .to change(work_item, :milestone)
+ .from(nil)
+ .to(project_milestone)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
index 3c977e62a10..af56f8ffac7 100644
--- a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
@@ -26,9 +26,10 @@ RSpec.shared_examples "migrates" do |to_store:, from_store: nil|
expect(subject).to be_an(CarrierWave::Uploader::Base)
expect(subject).to be_a(ObjectStorage::Concern)
- if from == described_class::Store::REMOTE
+ case from
+ when described_class::Store::REMOTE
expect(subject.file).to be_a(CarrierWave::Storage::Fog::File)
- elsif from == described_class::Store::LOCAL
+ when described_class::Store::LOCAL
expect(subject.file).to be_a(CarrierWave::SanitizedFile)
else
raise 'Unexpected file type'
diff --git a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
index 3ba5f080a01..0be55fd2a3e 100644
--- a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
+++ b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
@@ -137,8 +137,12 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
let(:lease_timeout) { 15.minutes }
let(:lease_key) { described_class.name.demodulize.underscore }
let(:interval_variance) { described_class::INTERVAL_VARIANCE }
+ let(:migration_id) { 123 }
let(:migration) do
- build(:batched_background_migration, :active, interval: job_interval, table_name: table_name)
+ build(
+ :batched_background_migration, :active,
+ id: migration_id, interval: job_interval, table_name: table_name
+ )
end
before do
@@ -150,45 +154,6 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
allow(migration).to receive(:reload)
end
- context 'when the reloaded migration is no longer active' do
- it 'does not run the migration' do
- expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout)
-
- expect(migration).to receive(:reload)
- expect(migration).to receive(:active?).and_return(false)
-
- expect(worker).not_to receive(:run_active_migration)
-
- worker.perform
- end
- end
-
- context 'when the interval has not elapsed' do
- it 'does not run the migration' do
- expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout)
-
- expect(migration).to receive(:interval_elapsed?).with(variance: interval_variance).and_return(false)
-
- expect(worker).not_to receive(:run_active_migration)
-
- worker.perform
- end
- end
-
- context 'when the reloaded migration is still active and the interval has elapsed' do
- it 'runs the migration' do
- expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout)
-
- expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |instance|
- expect(instance).to receive(:run_migration_job).with(migration)
- end
-
- expect(worker).to receive(:run_active_migration).and_call_original
-
- worker.perform
- end
- end
-
context 'when the calculated timeout is less than the minimum allowed' do
let(:minimum_timeout) { described_class::MINIMUM_LEASE_TIMEOUT }
let(:job_interval) { 2.minutes }
@@ -196,8 +161,8 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
it 'sets the lease timeout to the minimum value' do
expect_to_obtain_exclusive_lease(lease_key, timeout: minimum_timeout)
- expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |instance|
- expect(instance).to receive(:run_migration_job).with(migration)
+ expect_next_instance_of(Database::BatchedBackgroundMigration::ExecutionWorker) do |worker|
+ expect(worker).to receive(:perform).with(tracking_database, migration_id)
end
expect(worker).to receive(:run_active_migration).and_call_original
@@ -217,10 +182,13 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
expect { worker.perform }.to raise_error(RuntimeError, 'I broke')
end
- it 'receives the correct connection' do
+ it 'delegetes the execution to ExecutionWorker' do
base_model = Gitlab::Database.database_base_models[tracking_database]
expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(base_model.connection).and_yield
+ expect_next_instance_of(Database::BatchedBackgroundMigration::ExecutionWorker) do |worker|
+ expect(worker).to receive(:perform).with(tracking_database, migration_id)
+ end
worker.perform
end
@@ -236,10 +204,10 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
let(:migration_class) do
Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
job_arguments :matching_status
+ operation_name :update_all
def perform
each_sub_batch(
- operation_name: :update_all,
batching_scope: -> (relation) { relation.where(status: matching_status) }
) do |sub_batch|
sub_batch.update_all(some_column: 0)
diff --git a/spec/support/sidekiq_middleware.rb b/spec/support/sidekiq_middleware.rb
index cbd6163d46b..73f43487d7c 100644
--- a/spec/support/sidekiq_middleware.rb
+++ b/spec/support/sidekiq_middleware.rb
@@ -6,6 +6,15 @@ require 'sidekiq/testing'
module SidekiqMiddleware
def with_sidekiq_server_middleware(&block)
Sidekiq::Testing.server_middleware.clear
+
+ if Gem::Version.new(Sidekiq::VERSION) != Gem::Version.new('6.5.7')
+ raise 'New version of sidekiq detected, please remove this line'
+ end
+
+ # This line is a workaround for a Sidekiq bug that is already fixed in v7.0.0
+ # https://github.com/mperham/sidekiq/commit/1b83a152786ed382f07fff12d2608534f1e3c922
+ Sidekiq::Testing.server_middleware.instance_variable_set(:@config, Sidekiq)
+
Sidekiq::Testing.server_middleware(&block)
ensure
Sidekiq::Testing.server_middleware.clear
diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb
index f952f7f0985..b9bd3f82f65 100644
--- a/spec/support/webmock.rb
+++ b/spec/support/webmock.rb
@@ -15,6 +15,13 @@ def webmock_allowed_hosts
end.compact.uniq
end
+def with_net_connect_allowed
+ WebMock.allow_net_connect!
+ yield
+ensure
+ webmock_enable!
+end
+
# This prevents Selenium/WebMock from spawning thousands of connections
# while waiting for an element to appear via Capybara's find:
# https://github.com/teamcapybara/capybara/issues/2322#issuecomment-619321520