summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/helpers/board_helpers.rb9
-rw-r--r--spec/support/helpers/ci_artifact_metadata_generator.rb2
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb8
-rw-r--r--spec/support/helpers/graphql_helpers.rb16
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb2
-rw-r--r--spec/support/helpers/jira_service_helper.rb2
-rw-r--r--spec/support/helpers/key_generator_helper.rb3
-rw-r--r--spec/support/helpers/navbar_structure_helper.rb12
-rw-r--r--spec/support/helpers/next_instance_of.rb15
-rw-r--r--spec/support/helpers/query_recorder.rb40
-rw-r--r--spec/support/helpers/reload_helpers.rb12
-rw-r--r--spec/support/helpers/rubygems_helpers.rb11
-rw-r--r--spec/support/helpers/seed_repo.rb60
-rw-r--r--spec/support/helpers/stub_env.rb2
-rw-r--r--spec/support/helpers/stub_requests.rb2
-rw-r--r--spec/support/helpers/test_env.rb11
-rw-r--r--spec/support/helpers/usage_data_helpers.rb24
-rw-r--r--spec/support/import_export/project_tree_expectations.rb4
-rw-r--r--spec/support/matchers/exceed_query_limit.rb5
-rw-r--r--spec/support/matchers/graphql_matchers.rb14
-rw-r--r--spec/support/matchers/track_self_describing_event_matcher.rb12
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb11
-rw-r--r--spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb87
-rw-r--r--spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb92
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb5
-rw-r--r--spec/support/shared_contexts/project_service_jira_context.rb5
-rw-r--r--spec/support/shared_contexts/project_service_shared_context.rb5
-rw-r--r--spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb7
-rw-r--r--spec/support/shared_contexts/requests/api/go_modules_shared_context.rb14
-rw-r--r--spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb2
-rw-r--r--spec/support/shared_examples/boards/destroy_service_shared_examples.rb30
-rw-r--r--spec/support/shared_examples/boards/lists/update_service_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/controllers/snippet_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/controllers/trackable_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/controllers/unique_visits_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/features/cascading_settings_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb4
-rw-r--r--spec/support/shared_examples/features/error_tracking_shared_example.rb2
-rw-r--r--spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/features/project_upload_files_shared_examples.rb73
-rw-r--r--spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/features/search_settings_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/features/sidebar_shared_examples.rb165
-rw-r--r--spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_empty_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb6
-rw-r--r--spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/graphql/spam_protection_shared_examples.rb85
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/helpers/groups_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/api/internal_base_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/lib/gitlab/sidekiq_middleware/metrics_middleware_with_worker_attribution_shared_examples.rb132
-rw-r--r--spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/mailers/notify_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/models/boards/listable_shared_examples.rb25
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/models/cluster_application_version_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/models/clusters/prometheus_client_shared.rb86
-rw-r--r--spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/packages/debian/component_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb95
-rw-r--r--spec/support/shared_examples/namespaces/namespace_traversal_examples.rb (renamed from spec/support/shared_examples/namespaces/recursive_traversal_examples.rb)48
-rw-r--r--spec/support/shared_examples/nav_sidebar_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/policies/resource_access_token_shared_examples.rb76
-rw-r--r--spec/support/shared_examples/querying_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb49
-rw-r--r--spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/requests/clusters/integrations_controller_shared_examples.rb46
-rw-r--r--spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb38
-rw-r--r--spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/groups_count_service_shared_examples.rb55
-rw-r--r--spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb31
-rw-r--r--spec/support/shared_examples/services/merge_request_shared_examples.rb90
-rw-r--r--spec/support/shared_examples/services/notification_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/snippets_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/workers/in_product_marketing_email_shared_example.rb15
-rw-r--r--spec/support/shared_examples/workers/worker_with_data_consistency_shared_example.rb27
-rw-r--r--spec/support/sidekiq_middleware.rb18
98 files changed, 2005 insertions, 370 deletions
diff --git a/spec/support/helpers/board_helpers.rb b/spec/support/helpers/board_helpers.rb
index 683ee3e4bf2..6e145fed733 100644
--- a/spec/support/helpers/board_helpers.rb
+++ b/spec/support/helpers/board_helpers.rb
@@ -5,14 +5,5 @@ module BoardHelpers
within card do
first('.board-card-number').click
end
-
- wait_for_sidebar
- end
-
- def wait_for_sidebar
- # loop until the CSS transition is complete
- Timeout.timeout(0.5) do
- loop until evaluate_script('$(".right-sidebar").outerWidth()') == 290
- end
end
end
diff --git a/spec/support/helpers/ci_artifact_metadata_generator.rb b/spec/support/helpers/ci_artifact_metadata_generator.rb
index e02501565a9..ae821d6582b 100644
--- a/spec/support/helpers/ci_artifact_metadata_generator.rb
+++ b/spec/support/helpers/ci_artifact_metadata_generator.rb
@@ -7,7 +7,7 @@
class CiArtifactMetadataGenerator
attr_accessor :entries, :output
- ARTIFACT_METADATA = "GitLab Build Artifacts Metadata 0.0.2\n".freeze
+ ARTIFACT_METADATA = "GitLab Build Artifacts Metadata 0.0.2\n"
def initialize(stream)
@entries = {}
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index 14041ad0ac6..9e62eef14de 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -3,15 +3,15 @@
module CycleAnalyticsHelpers
include GitHelpers
- def wait_for_stages_to_load
- expect(page).to have_selector '.js-stage-table'
+ def wait_for_stages_to_load(selector = '.js-path-navigation')
+ expect(page).to have_selector selector
wait_for_requests
end
- def select_group(target_group)
+ def select_group(target_group, ready_selector = '.js-path-navigation')
visit group_analytics_cycle_analytics_path(target_group)
- wait_for_stages_to_load
+ wait_for_stages_to_load(ready_selector)
end
def toggle_dropdown(field)
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 75d9508f470..d714f04fbba 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -222,9 +222,12 @@ module GraphqlHelpers
lazy_vals.is_a?(Array) ? lazy_vals.map { |val| sync(val) } : sync(lazy_vals)
end
- def graphql_query_for(name, args = {}, selection = nil)
+ def graphql_query_for(name, args = {}, selection = nil, operation_name = nil)
type = GitlabSchema.types['Query'].fields[GraphqlHelpers.fieldnamerize(name)]&.type
- wrap_query(query_graphql_field(name, args, selection, type))
+ query = wrap_query(query_graphql_field(name, args, selection, type))
+ query = "query #{operation_name}#{query}" if operation_name
+
+ query
end
def wrap_query(query)
@@ -274,11 +277,11 @@ module GraphqlHelpers
# prepare_input_for_mutation({ 'my_key' => 1 })
# => { 'myKey' => 1}
def prepare_input_for_mutation(input)
- input.map do |name, value|
+ input.to_h do |name, value|
value = prepare_input_for_mutation(value) if value.is_a?(Hash)
[GraphqlHelpers.fieldnamerize(name), value]
- end.to_h
+ end
end
def input_variable_name_for_mutation(mutation_name)
@@ -304,7 +307,10 @@ module GraphqlHelpers
def query_graphql_field(name, attributes = {}, fields = nil, type = nil)
type ||= name.to_s.classify
- attributes, fields = [nil, attributes] if fields.nil? && !attributes.is_a?(Hash)
+ if fields.nil? && !attributes.is_a?(Hash)
+ fields = attributes
+ attributes = nil
+ end
field = field_with_params(name, attributes)
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index 09425c3742a..28375c1d51e 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -31,7 +31,7 @@ module JavaScriptFixturesHelpers
#
def clean_frontend_fixtures(directory_name)
full_directory_name = File.expand_path(directory_name, fixture_root_path)
- Dir[File.expand_path('*.html', full_directory_name)].each do |file_name|
+ Dir[File.expand_path('*.{html,json,md}', full_directory_name)].each do |file_name|
FileUtils.rm(file_name)
end
end
diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb
index 698490c8c92..ce908d53f88 100644
--- a/spec/support/helpers/jira_service_helper.rb
+++ b/spec/support/helpers/jira_service_helper.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module JiraServiceHelper
- JIRA_URL = "http://jira.example.net".freeze
+ JIRA_URL = "http://jira.example.net"
JIRA_API = JIRA_URL + "/rest/api/2"
def jira_service_settings
diff --git a/spec/support/helpers/key_generator_helper.rb b/spec/support/helpers/key_generator_helper.rb
index 59c8eeb3692..58bde80a31f 100644
--- a/spec/support/helpers/key_generator_helper.rb
+++ b/spec/support/helpers/key_generator_helper.rb
@@ -27,7 +27,8 @@ module Spec
# Encodes an openssh-mpi-encoded integer.
def encode_mpi(n) # rubocop:disable Naming/UncommunicativeMethodParamName
- chars, n = [], n.to_i
+ chars = []
+ n = n.to_i
chars << (n & 0xff) && n >>= 8 while n != 0
chars << 0 if chars.empty? || chars.last >= 0x80
chars.reverse.pack('C*')
diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb
index e18a708e41c..826108a63a5 100644
--- a/spec/support/helpers/navbar_structure_helper.rb
+++ b/spec/support/helpers/navbar_structure_helper.rb
@@ -29,7 +29,7 @@ module NavbarStructureHelper
)
end
- def insert_container_nav(within)
+ def insert_container_nav
insert_after_sub_nav_item(
_('Package Registry'),
within: _('Packages & Registries'),
@@ -37,11 +37,19 @@ module NavbarStructureHelper
)
end
- def insert_dependency_proxy_nav(within)
+ def insert_dependency_proxy_nav
insert_after_sub_nav_item(
_('Package Registry'),
within: _('Packages & Registries'),
new_sub_nav_item_name: _('Dependency Proxy')
)
end
+
+ def insert_infrastructure_registry_nav
+ insert_after_sub_nav_item(
+ _('Package Registry'),
+ within: _('Packages & Registries'),
+ new_sub_nav_item_name: _('Infrastructure Registry')
+ )
+ end
end
diff --git a/spec/support/helpers/next_instance_of.rb b/spec/support/helpers/next_instance_of.rb
index a8e9ab2bafe..95d8936588c 100644
--- a/spec/support/helpers/next_instance_of.rb
+++ b/spec/support/helpers/next_instance_of.rb
@@ -2,25 +2,26 @@
module NextInstanceOf
def expect_next_instance_of(klass, *new_args, &blk)
- stub_new(expect(klass), nil, *new_args, &blk)
+ stub_new(expect(klass), nil, false, *new_args, &blk)
end
- def expect_next_instances_of(klass, number, *new_args, &blk)
- stub_new(expect(klass), number, *new_args, &blk)
+ def expect_next_instances_of(klass, number, ordered = false, *new_args, &blk)
+ stub_new(expect(klass), number, ordered, *new_args, &blk)
end
def allow_next_instance_of(klass, *new_args, &blk)
- stub_new(allow(klass), nil, *new_args, &blk)
+ stub_new(allow(klass), nil, false, *new_args, &blk)
end
- def allow_next_instances_of(klass, number, *new_args, &blk)
- stub_new(allow(klass), number, *new_args, &blk)
+ def allow_next_instances_of(klass, number, ordered = false, *new_args, &blk)
+ stub_new(allow(klass), number, ordered, *new_args, &blk)
end
private
- def stub_new(target, number, *new_args, &blk)
+ def stub_new(target, number, ordered = false, *new_args, &blk)
receive_new = receive(:new)
+ receive_new.ordered if ordered
receive_new.exactly(number).times if number
receive_new.with(*new_args) if new_args.any?
diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index 61634813a1c..2d880c7a8fe 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -3,37 +3,53 @@
module ActiveRecord
class QueryRecorder
attr_reader :log, :skip_cached, :cached, :data
- UNKNOWN = %w(unknown unknown).freeze
- def initialize(skip_cached: true, query_recorder_debug: false, &block)
- @data = Hash.new { |h, k| h[k] = { count: 0, occurrences: [], backtrace: [] } }
+ UNKNOWN = %w[unknown unknown].freeze
+
+ def initialize(skip_cached: true, log_file: nil, query_recorder_debug: false, &block)
+ @data = Hash.new { |h, k| h[k] = { count: 0, occurrences: [], backtrace: [], durations: [] } }
@log = []
@cached = []
@skip_cached = skip_cached
- @query_recorder_debug = query_recorder_debug
+ @query_recorder_debug = ENV['QUERY_RECORDER_DEBUG'] || query_recorder_debug
+ @log_file = log_file
# force replacement of bind parameters to give tests the ability to check for ids
ActiveRecord::Base.connection.unprepared_statement do
ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block)
end
end
- def show_backtrace(values)
- Rails.logger.debug("QueryRecorder SQL: #{values[:sql]}")
+ def show_backtrace(values, duration)
+ values[:sql].lines.each do |line|
+ print_to_log(:SQL, line)
+ end
+ print_to_log(:DURATION, duration)
Gitlab::BacktraceCleaner.clean_backtrace(caller).each do |line|
- Rails.logger.debug("QueryRecorder backtrace: --> #{line}")
+ print_to_log(:backtrace, line)
+ end
+ end
+
+ def print_to_log(label, line)
+ msg = "QueryRecorder #{label}: --> #{line}"
+
+ if @log_file
+ @log_file.puts(msg)
+ else
+ Rails.logger.debug(msg)
end
end
def get_sql_source(sql)
- matches = sql.match(/,line:(?<line>.*):in\s+`(?<method>.*)'\*\//)
+ matches = sql.match(%r{,line:(?<line>.*):in\s+`(?<method>.*)'\*/})
matches ? [matches[:line], matches[:method]] : UNKNOWN
end
- def store_sql_by_source(values: {}, backtrace: nil)
+ def store_sql_by_source(values: {}, duration: nil, backtrace: nil)
full_name = get_sql_source(values[:sql]).join(':')
@data[full_name][:count] += 1
@data[full_name][:occurrences] << values[:sql]
@data[full_name][:backtrace] << backtrace
+ @data[full_name][:durations] << duration
end
def find_query(query_regexp, limit, first_only: false)
@@ -55,14 +71,14 @@ module ActiveRecord
end
def callback(name, start, finish, message_id, values)
- store_backtrace = ENV['QUERY_RECORDER_DEBUG'] || @query_recorder_debug
- backtrace = store_backtrace ? show_backtrace(values) : nil
+ duration = finish - start
if values[:cached] && skip_cached
@cached << values[:sql]
elsif !values[:name]&.include?("SCHEMA")
+ backtrace = @query_recorder_debug ? show_backtrace(values, duration) : nil
@log << values[:sql]
- store_sql_by_source(values: values, backtrace: backtrace)
+ store_sql_by_source(values: values, duration: duration, backtrace: backtrace)
end
end
diff --git a/spec/support/helpers/reload_helpers.rb b/spec/support/helpers/reload_helpers.rb
new file mode 100644
index 00000000000..60811e4604f
--- /dev/null
+++ b/spec/support/helpers/reload_helpers.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module ReloadHelpers
+ def reload_models(*models)
+ models.map(&:reload)
+ end
+
+ def subject_and_reload(*models)
+ subject
+ reload_models(*models)
+ end
+end
diff --git a/spec/support/helpers/rubygems_helpers.rb b/spec/support/helpers/rubygems_helpers.rb
new file mode 100644
index 00000000000..6a808f52e97
--- /dev/null
+++ b/spec/support/helpers/rubygems_helpers.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module RubygemsHelpers
+ def gem_from_file(file)
+ full_path = File.expand_path(
+ Rails.root.join('spec', 'fixtures', 'packages', 'rubygems', file.filename)
+ )
+
+ Gem::Package.new(File.open(full_path))
+ end
+end
diff --git a/spec/support/helpers/seed_repo.rb b/spec/support/helpers/seed_repo.rb
index 20738b45129..74ac529a3de 100644
--- a/spec/support/helpers/seed_repo.rb
+++ b/spec/support/helpers/seed_repo.rb
@@ -31,64 +31,64 @@
module SeedRepo
module BigCommit
- ID = "913c66a37b4a45b9769037c55c2d238bd0942d2e".freeze
- PARENT_ID = "cfe32cf61b73a0d5e9f13e774abde7ff789b1660".freeze
- MESSAGE = "Files, encoding and much more".freeze
- AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ ID = "913c66a37b4a45b9769037c55c2d238bd0942d2e"
+ PARENT_ID = "cfe32cf61b73a0d5e9f13e774abde7ff789b1660"
+ MESSAGE = "Files, encoding and much more"
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
FILES_COUNT = 2
end
module Commit
- ID = "570e7b2abdd848b95f2f578043fc23bd6f6fd24d".freeze
- PARENT_ID = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9".freeze
- MESSAGE = "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n".freeze
- AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ ID = "570e7b2abdd848b95f2f578043fc23bd6f6fd24d"
+ PARENT_ID = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
+ MESSAGE = "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n"
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
FILES = ["files/ruby/popen.rb", "files/ruby/regex.rb"].freeze
FILES_COUNT = 2
- C_FILE_PATH = "files/ruby".freeze
+ C_FILE_PATH = "files/ruby"
C_FILES = ["popen.rb", "regex.rb", "version_info.rb"].freeze
- BLOB_FILE = %{%h3= @key.title\n%hr\n%pre= @key.key\n.actions\n = link_to 'Remove', @key, :confirm => 'Are you sure?', :method => :delete, :class => \"btn danger delete-key\"\n\n\n}.freeze
- BLOB_FILE_PATH = "app/views/keys/show.html.haml".freeze
+ BLOB_FILE = %{%h3= @key.title\n%hr\n%pre= @key.key\n.actions\n = link_to 'Remove', @key, :confirm => 'Are you sure?', :method => :delete, :class => \"btn danger delete-key\"\n\n\n}
+ BLOB_FILE_PATH = "app/views/keys/show.html.haml"
end
module EmptyCommit
- ID = "b0e52af38d7ea43cf41d8a6f2471351ac036d6c9".freeze
- PARENT_ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d".freeze
- MESSAGE = "Empty commit".freeze
- AUTHOR_FULL_NAME = "Rémy Coutable".freeze
+ ID = "b0e52af38d7ea43cf41d8a6f2471351ac036d6c9"
+ PARENT_ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d"
+ MESSAGE = "Empty commit"
+ AUTHOR_FULL_NAME = "Rémy Coutable"
FILES = [].freeze
FILES_COUNT = FILES.count
end
module EncodingCommit
- ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d".freeze
- PARENT_ID = "66028349a123e695b589e09a36634d976edcc5e8".freeze
- MESSAGE = "Add ISO-8859-encoded file".freeze
- AUTHOR_FULL_NAME = "Stan Hu".freeze
+ ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d"
+ PARENT_ID = "66028349a123e695b589e09a36634d976edcc5e8"
+ MESSAGE = "Add ISO-8859-encoded file"
+ AUTHOR_FULL_NAME = "Stan Hu"
FILES = ["encoding/iso8859.txt"].freeze
FILES_COUNT = FILES.count
end
module FirstCommit
- ID = "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863".freeze
+ ID = "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863"
PARENT_ID = nil
- MESSAGE = "Initial commit".freeze
- AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ MESSAGE = "Initial commit"
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
FILES = ["LICENSE", ".gitignore", "README.md"].freeze
FILES_COUNT = 3
end
module LastCommit
- ID = "4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6".freeze
- PARENT_ID = "0e1b353b348f8477bdbec1ef47087171c5032cd9".freeze
- MESSAGE = "Merge branch 'master' into 'master'".freeze
- AUTHOR_FULL_NAME = "Stan Hu".freeze
+ ID = "4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6"
+ PARENT_ID = "0e1b353b348f8477bdbec1ef47087171c5032cd9"
+ MESSAGE = "Merge branch 'master' into 'master'"
+ AUTHOR_FULL_NAME = "Stan Hu"
FILES = ["bin/executable"].freeze
FILES_COUNT = FILES.count
end
module Repo
- HEAD = "master".freeze
+ HEAD = "master"
BRANCHES = %w[
feature
fix
@@ -111,9 +111,9 @@ module SeedRepo
end
module RubyBlob
- ID = "7e3e39ebb9b2bf433b4ad17313770fbe4051649c".freeze
- NAME = "popen.rb".freeze
- CONTENT = <<-eos.freeze
+ ID = "7e3e39ebb9b2bf433b4ad17313770fbe4051649c"
+ NAME = "popen.rb"
+ CONTENT = <<-eos
require 'fileutils'
require 'open3'
diff --git a/spec/support/helpers/stub_env.rb b/spec/support/helpers/stub_env.rb
index 8107ffc939f..5f344f8fb52 100644
--- a/spec/support/helpers/stub_env.rb
+++ b/spec/support/helpers/stub_env.rb
@@ -14,7 +14,7 @@ module StubENV
private
- STUBBED_KEY = '__STUBBED__'.freeze
+ STUBBED_KEY = '__STUBBED__'
def add_stubbed_value(key, value)
allow(ENV).to receive(:[]).with(key).and_return(value)
diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb
index 473f07dd413..a3810323fee 100644
--- a/spec/support/helpers/stub_requests.rb
+++ b/spec/support/helpers/stub_requests.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module StubRequests
- IP_ADDRESS_STUB = '8.8.8.9'.freeze
+ IP_ADDRESS_STUB = '8.8.8.9'
# Fully stubs a request using WebMock class. This class also
# stubs the IP address the URL is translated to (DNS lookup).
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 266c0e18ccd..7ba15a9c00b 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -92,7 +92,7 @@ module TestEnv
}.freeze
TMP_TEST_PATH = Rails.root.join('tmp', 'tests').freeze
- REPOS_STORAGE = 'default'.freeze
+ REPOS_STORAGE = 'default'
SECOND_STORAGE_PATH = Rails.root.join('tmp', 'tests', 'second_storage')
# Test environment
@@ -170,7 +170,14 @@ module TestEnv
install_dir: gitaly_dir,
version: Gitlab::GitalyClient.expected_server_version,
task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
- Gitlab::SetupHelper::Gitaly.create_configuration(gitaly_dir, { 'default' => repos_path }, force: true)
+ Gitlab::SetupHelper::Gitaly.create_configuration(
+ gitaly_dir,
+ { 'default' => repos_path },
+ force: true,
+ options: {
+ prometheus_listen_addr: 'localhost:9236'
+ }
+ )
Gitlab::SetupHelper::Gitaly.create_configuration(
gitaly_dir,
{ 'default' => repos_path },
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index df79049123d..d05676a649e 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -174,6 +174,22 @@ module UsageDataHelpers
allow(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(false)
end
+ def stub_prometheus_queries
+ stub_request(:get, %r{^https?://::1:9090/-/ready})
+ .to_return(
+ status: 200,
+ body: [{}].to_json,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+
+ stub_request(:get, %r{^https?://::1:9090/api/v1/query\?query=.*})
+ .to_return(
+ status: 200,
+ body: [{}].to_json,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+ end
+
def clear_memoized_values(values)
values.each { |v| described_class.clear_memoization(v) }
end
@@ -242,4 +258,12 @@ module UsageDataHelpers
end
end
end
+
+ def load_sample_metric_definition(filename: 'sample_metric.yml')
+ load_metric_yaml(fixture_file("lib/generators/gitlab/usage_metric_definition_generator/#{filename}"))
+ end
+
+ def load_metric_yaml(data)
+ ::Gitlab::Config::Loader::Yaml.new(data).load_raw!
+ end
end
diff --git a/spec/support/import_export/project_tree_expectations.rb b/spec/support/import_export/project_tree_expectations.rb
index 966c977e8e9..2423a58a3e6 100644
--- a/spec/support/import_export/project_tree_expectations.rb
+++ b/spec/support/import_export/project_tree_expectations.rb
@@ -97,13 +97,13 @@ module ImportExport
def normalize_elements(elem)
case elem
when Hash
- elem.map do |key, value|
+ elem.to_h do |key, value|
if ignore_key?(key, value)
[key, :ignored]
else
[key, normalize_elements(value)]
end
- end.to_h
+ end
when Array
elem.map { |a| normalize_elements(a) }
else
diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb
index 7a66eff3a41..b48c7f905b2 100644
--- a/spec/support/matchers/exceed_query_limit.rb
+++ b/spec/support/matchers/exceed_query_limit.rb
@@ -20,6 +20,11 @@ module ExceedQueryLimitHelpers
self
end
+ def for_model(model)
+ table = model.table_name if model < ActiveRecord::Base
+ for_query(/(FROM|UPDATE|INSERT INTO|DELETE FROM)\s+"#{table}"/)
+ end
+
def show_common_queries
@show_common_queries = true
self
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index 565c21e0f85..904b7efdd7f 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -30,11 +30,13 @@ RSpec::Matchers.define :have_graphql_fields do |*expected|
end
match do |kls|
- if @allow_extra
- expect(kls.fields.keys).to include(*expected_field_names)
- else
- expect(kls.fields.keys).to contain_exactly(*expected_field_names)
- end
+ keys = kls.fields.keys.to_set
+ fields = expected_field_names.to_set
+
+ next true if fields == keys
+ next true if @allow_extra && fields.proper_subset?(keys)
+
+ false
end
failure_message do |kls|
@@ -108,7 +110,7 @@ RSpec::Matchers.define :have_graphql_arguments do |*expected|
names = expected_names(field).inspect
args = field.arguments.keys.inspect
- "expected that #{field.name} would have the following arguments: #{names}, but it has #{args}."
+ "expected #{field.name} to have the following arguments: #{names}, but it has #{args}."
end
end
diff --git a/spec/support/matchers/track_self_describing_event_matcher.rb b/spec/support/matchers/track_self_describing_event_matcher.rb
deleted file mode 100644
index c3723d2418f..00000000000
--- a/spec/support/matchers/track_self_describing_event_matcher.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-RSpec::Matchers.define :track_self_describing_event do |schema, data|
- match do
- expect(Gitlab::Tracking).to have_received(:self_describing_event)
- .with(schema, data: data)
- end
-
- match_when_negated do
- expect(Gitlab::Tracking).not_to have_received(:self_describing_event)
- end
-end
diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb
index 9dffea7c94e..14c6c85cc43 100644
--- a/spec/support/shared_contexts/email_shared_context.rb
+++ b/spec/support/shared_contexts/email_shared_context.rb
@@ -82,8 +82,8 @@ RSpec.shared_examples :note_handler_shared_examples do |forwardable|
let!(:email_raw) { update_commands_only }
context 'and current user cannot update noteable' do
- it 'raises a CommandsOnlyNoteError' do
- expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
+ it 'does not raise an error' do
+ expect { receiver.execute }.not_to raise_error
end
end
@@ -92,15 +92,11 @@ RSpec.shared_examples :note_handler_shared_examples do |forwardable|
project.add_developer(user)
end
- it 'does not raise an error', unless: forwardable do
+ it 'does not raise an error' do
expect { receiver.execute }.to change { noteable.resource_state_events.count }.by(1)
expect(noteable.reload).to be_closed
end
-
- it 'raises an InvalidNoteError', if: forwardable do
- expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
- end
end
end
end
@@ -189,6 +185,7 @@ RSpec.shared_examples :note_handler_shared_examples do |forwardable|
let(:email_raw) { with_quick_actions }
let!(:sent_notification) do
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).with(project: project).and_return(true)
SentNotification.record_note(note, support_bot.id, mail_key)
end
diff --git a/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
new file mode 100644
index 00000000000..5a72b330707
--- /dev/null
+++ b/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'structured_logger' do
+ let(:timestamp) { Time.iso8601('2018-01-01T12:00:00.000Z') }
+ let(:created_at) { timestamp - 1.second }
+ let(:scheduling_latency_s) { 1.0 }
+
+ let(:job) do
+ {
+ "class" => "TestWorker",
+ "args" => [1234, 'hello', { 'key' => 'value' }],
+ "retry" => false,
+ "queue" => "cronjob:test_queue",
+ "queue_namespace" => "cronjob",
+ "jid" => "da883554ee4fe414012f5f42",
+ "created_at" => created_at.to_f,
+ "enqueued_at" => created_at.to_f,
+ "correlation_id" => 'cid',
+ "error_message" => "wrong number of arguments (2 for 3)",
+ "error_class" => "ArgumentError",
+ "error_backtrace" => []
+ }
+ end
+
+ let(:logger) { double }
+ let(:clock_realtime_start) { 0.222222299 }
+ let(:clock_realtime_end) { 1.333333799 }
+ let(:clock_thread_cputime_start) { 0.222222299 }
+ let(:clock_thread_cputime_end) { 1.333333799 }
+ let(:start_payload) do
+ job.except('error_backtrace', 'error_class', 'error_message').merge(
+ 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: start',
+ 'job_status' => 'start',
+ 'pid' => Process.pid,
+ 'created_at' => created_at.to_f,
+ 'enqueued_at' => created_at.to_f,
+ 'scheduling_latency_s' => scheduling_latency_s,
+ 'job_size_bytes' => be > 0
+ )
+ end
+
+ let(:end_payload) do
+ start_payload.merge(
+ 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: done: 0.0 sec',
+ 'job_status' => 'done',
+ 'duration_s' => 0.0,
+ 'completed_at' => timestamp.to_f,
+ 'cpu_s' => 1.111112,
+ 'db_duration_s' => 0.0,
+ 'db_cached_count' => 0,
+ 'db_count' => 0,
+ 'db_write_count' => 0
+ )
+ end
+
+ let(:exception_payload) do
+ end_payload.merge(
+ 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec',
+ 'job_status' => 'fail',
+ 'error_class' => 'ArgumentError',
+ 'error_message' => 'Something went wrong',
+ 'error_backtrace' => be_a(Array).and(be_present)
+ )
+ end
+
+ before do
+ allow(Sidekiq).to receive(:logger).and_return(logger)
+
+ allow(subject).to receive(:current_time).and_return(timestamp.to_f)
+
+ allow(Process).to receive(:clock_gettime).with(Process::CLOCK_REALTIME, :float_second)
+ .and_return(clock_realtime_start, clock_realtime_end)
+ allow(Process).to receive(:clock_gettime).with(Process::CLOCK_THREAD_CPUTIME_ID, :float_second)
+ .and_return(clock_thread_cputime_start, clock_thread_cputime_end)
+ end
+
+ subject { described_class.new }
+
+ def call_subject(job, queue)
+ # This structured logger strongly depends on execution of `InstrumentationLogger`
+ subject.call(job, queue) do
+ ::Gitlab::SidekiqMiddleware::InstrumentationLogger.new.call('worker', job, queue) do
+ yield
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb
new file mode 100644
index 00000000000..73de631e293
--- /dev/null
+++ b/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'server metrics with mocked prometheus' do
+ let(:concurrency_metric) { double('concurrency metric') }
+
+ let(:queue_duration_seconds) { double('queue duration seconds metric') }
+ let(:completion_seconds_metric) { double('completion seconds metric') }
+ let(:user_execution_seconds_metric) { double('user execution seconds metric') }
+ let(:db_seconds_metric) { double('db seconds metric') }
+ let(:gitaly_seconds_metric) { double('gitaly seconds metric') }
+ let(:failed_total_metric) { double('failed total metric') }
+ let(:retried_total_metric) { double('retried total metric') }
+ let(:redis_requests_total) { double('redis calls total metric') }
+ let(:running_jobs_metric) { double('running jobs metric') }
+ let(:redis_seconds_metric) { double('redis seconds metric') }
+ let(:elasticsearch_seconds_metric) { double('elasticsearch seconds metric') }
+ let(:elasticsearch_requests_total) { double('elasticsearch calls total metric') }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_queue_duration_seconds, anything, anything, anything).and_return(queue_duration_seconds)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything, anything).and_return(completion_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_cpu_seconds, anything, anything, anything).and_return(user_execution_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_db_seconds, anything, anything, anything).and_return(db_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_gitaly_seconds, anything, anything, anything).and_return(gitaly_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_redis_requests_duration_seconds, anything, anything, anything).and_return(redis_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_elasticsearch_requests_duration_seconds, anything, anything, anything).and_return(elasticsearch_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric)
+ allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric)
+ allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_redis_requests_total, anything).and_return(redis_requests_total)
+ allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_elasticsearch_requests_total, anything).and_return(elasticsearch_requests_total)
+ allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :all).and_return(running_jobs_metric)
+ allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_concurrency, anything, {}, :all).and_return(concurrency_metric)
+
+ allow(concurrency_metric).to receive(:set)
+ end
+end
+
+RSpec.shared_context 'server metrics call' do
+ let(:thread_cputime_before) { 1 }
+ let(:thread_cputime_after) { 2 }
+ let(:thread_cputime_duration) { thread_cputime_after - thread_cputime_before }
+
+ let(:monotonic_time_before) { 11 }
+ let(:monotonic_time_after) { 20 }
+ let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before }
+
+ let(:queue_duration_for_job) { 0.01 }
+
+ let(:db_duration) { 3 }
+ let(:gitaly_duration) { 4 }
+
+ let(:redis_calls) { 2 }
+ let(:redis_duration) { 0.01 }
+
+ let(:elasticsearch_calls) { 8 }
+ let(:elasticsearch_duration) { 0.54 }
+ let(:instrumentation) do
+ {
+ gitaly_duration_s: gitaly_duration,
+ redis_calls: redis_calls,
+ redis_duration_s: redis_duration,
+ elasticsearch_calls: elasticsearch_calls,
+ elasticsearch_duration_s: elasticsearch_duration
+ }
+ end
+
+ before do
+ allow(subject).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after)
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after)
+ allow(Gitlab::InstrumentationHelper).to receive(:queue_duration_for_job).with(job).and_return(queue_duration_for_job)
+ allow(ActiveRecord::LogSubscriber).to receive(:runtime).and_return(db_duration * 1000)
+
+ job[:instrumentation] = instrumentation
+ job[:gitaly_duration_s] = gitaly_duration
+ job[:redis_calls] = redis_calls
+ job[:redis_duration_s] = redis_duration
+
+ job[:elasticsearch_calls] = elasticsearch_calls
+ job[:elasticsearch_duration_s] = elasticsearch_duration
+
+ allow(running_jobs_metric).to receive(:increment)
+ allow(redis_requests_total).to receive(:increment)
+ allow(elasticsearch_requests_total).to receive(:increment)
+ allow(queue_duration_seconds).to receive(:observe)
+ allow(user_execution_seconds_metric).to receive(:observe)
+ allow(db_seconds_metric).to receive(:observe)
+ allow(gitaly_seconds_metric).to receive(:observe)
+ allow(completion_seconds_metric).to receive(:observe)
+ allow(redis_seconds_metric).to receive(:observe)
+ allow(elasticsearch_seconds_metric).to receive(:observe)
+ end
+end
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 671c0cdf79c..78d14ecb880 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -59,7 +59,7 @@ RSpec.shared_context 'project navbar structure' do
]
},
{
- nav_item: _('Merge Requests'),
+ nav_item: _('Merge requests'),
nav_sub_items: []
},
{
@@ -139,6 +139,7 @@ RSpec.shared_context 'group navbar structure' do
_('Projects'),
_('Repository'),
_('CI/CD'),
+ _('Applications'),
_('Packages & Registries'),
_('Webhooks')
]
@@ -189,7 +190,7 @@ RSpec.shared_context 'group navbar structure' do
]
},
{
- nav_item: _('Merge Requests'),
+ nav_item: _('Merge requests'),
nav_sub_items: []
},
security_and_compliance_nav_item,
diff --git a/spec/support/shared_contexts/project_service_jira_context.rb b/spec/support/shared_contexts/project_service_jira_context.rb
index 8e01de70846..54bb9fd108e 100644
--- a/spec/support/shared_contexts/project_service_jira_context.rb
+++ b/spec/support/shared_contexts/project_service_jira_context.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
RSpec.shared_context 'project service Jira context' do
- let(:url) { 'http://jira.example.com' }
- let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' }
+ let(:url) { 'https://jira.example.com' }
+ let(:test_url) { 'https://jira.example.com/rest/api/2/serverInfo' }
def fill_form(disable: false)
click_active_checkbox if disable
@@ -10,6 +10,5 @@ RSpec.shared_context 'project service Jira context' do
fill_in 'service_url', with: url
fill_in 'service_username', with: 'username'
fill_in 'service_password', with: 'password'
- fill_in 'service_jira_issue_transition_id', with: '25'
end
end
diff --git a/spec/support/shared_contexts/project_service_shared_context.rb b/spec/support/shared_contexts/project_service_shared_context.rb
index b4b9ab456e0..a8e75c624e8 100644
--- a/spec/support/shared_contexts/project_service_shared_context.rb
+++ b/spec/support/shared_contexts/project_service_shared_context.rb
@@ -15,7 +15,10 @@ RSpec.shared_context 'project service activation' do
def visit_project_integration(name)
visit_project_integrations
- click_link(name)
+
+ within('#content-body') do
+ click_link(name)
+ end
end
def click_active_checkbox
diff --git a/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb
index f3bbb325475..ac53be1a1cb 100644
--- a/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb
+++ b/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb
@@ -41,13 +41,6 @@ RSpec.shared_context 'conan recipe endpoints' do
let(:jwt) { build_jwt(personal_access_token) }
let(:headers) { build_token_auth_header(jwt.encoded) }
let(:conan_package_reference) { '123456789' }
- let(:presenter) { double('::Packages::Conan::PackagePresenter') }
-
- before do
- allow(::Packages::Conan::PackagePresenter).to receive(:new)
- .with(package, user, package.project, any_args)
- .and_return(presenter)
- end
end
RSpec.shared_context 'conan file download endpoints' do
diff --git a/spec/support/shared_contexts/requests/api/go_modules_shared_context.rb b/spec/support/shared_contexts/requests/api/go_modules_shared_context.rb
new file mode 100644
index 00000000000..5a90c3076b1
--- /dev/null
+++ b/spec/support/shared_contexts/requests/api/go_modules_shared_context.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'basic Go module' do
+ let_it_be(:user) { create :user }
+ let_it_be(:project) { create :project_empty_repo, creator: user, path: 'my-go-lib' }
+
+ let_it_be(:commit_v1_0_0) { create :go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'README.md' => 'Hi' } }
+ let_it_be(:commit_v1_0_1) { create :go_module_commit, :module, project: project, tag: 'v1.0.1' }
+ let_it_be(:commit_v1_0_2) { create :go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg' }
+ let_it_be(:commit_v1_0_3) { create :go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod' }
+ let_it_be(:commit_file_y) { create :go_module_commit, :files, project: project, files: { 'y.go' => "package a\n" } }
+ let_it_be(:commit_mod_v2) { create :go_module_commit, :module, project: project, name: 'v2' }
+ let_it_be(:commit_v2_0_0) { create :go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" } }
+end
diff --git a/spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb b/spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb
index bcc98cf6416..80f011f622b 100644
--- a/spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb
+++ b/spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb
@@ -23,7 +23,7 @@ RSpec.shared_context 'container repository delete tags service shared context' d
end
def stub_delete_reference_requests(tags)
- tags = Hash[Array.wrap(tags).map { |tag| [tag, 200] }] unless tags.is_a?(Hash)
+ tags = Array.wrap(tags).to_h { |tag| [tag, 200] } unless tags.is_a?(Hash)
tags.each do |tag, status|
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/#{tag}")
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index 3322c6ef01a..f250632ff51 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -28,6 +28,8 @@ Service.available_services_names.each do |service|
hash.merge!(k => 1234)
elsif service == 'jira' && k == :jira_issue_transition_id
hash.merge!(k => '1,2,3')
+ elsif service == 'emails_on_push' && k == :recipients
+ hash.merge!(k => 'foo@bar.com')
else
hash.merge!(k => "someword")
end
diff --git a/spec/support/shared_examples/boards/destroy_service_shared_examples.rb b/spec/support/shared_examples/boards/destroy_service_shared_examples.rb
new file mode 100644
index 00000000000..33bae3da44b
--- /dev/null
+++ b/spec/support/shared_examples/boards/destroy_service_shared_examples.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'board destroy service' do
+ describe '#execute' do
+ let(:parent_type) { parent.is_a?(Project) ? :project : :group }
+ let!(:board) { create(board_factory, parent_type => parent) }
+
+ subject(:service) { described_class.new(parent, double) }
+
+ context 'when there is more than one board' do
+ let!(:board2) { create(board_factory, parent_type => parent) }
+
+ it 'destroys the board' do
+ create(board_factory, parent_type => parent)
+
+ expect do
+ expect(service.execute(board)).to be_success
+ end.to change(boards, :count).by(-1)
+ end
+ end
+
+ context 'when there is only one board' do
+ it 'does not remove board' do
+ expect do
+ expect(service.execute(board)).to be_error
+ end.not_to change(boards, :count)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/boards/lists/update_service_shared_examples.rb b/spec/support/shared_examples/boards/lists/update_service_shared_examples.rb
new file mode 100644
index 00000000000..d8a74f2582d
--- /dev/null
+++ b/spec/support/shared_examples/boards/lists/update_service_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'moving list' do
+ context 'when user can admin list' do
+ it 'calls Lists::MoveService to update list position' do
+ board.resource_parent.add_developer(user)
+
+ expect_next_instance_of(Boards::Lists::MoveService, board.resource_parent, user, params) do |move_service|
+ expect(move_service).to receive(:execute).with(list).and_call_original
+ end
+
+ service.execute(list)
+ end
+ end
+
+ context 'when user cannot admin list' do
+ it 'does not call Lists::MoveService to update list position' do
+ expect(Boards::Lists::MoveService).not_to receive(:new)
+
+ service.execute(list)
+ end
+ end
+end
+
+RSpec.shared_examples 'updating list preferences' do
+ context 'when user can read list' do
+ it 'updates list preference for user' do
+ board.resource_parent.add_guest(user)
+
+ service.execute(list)
+
+ expect(list.preferences_for(user).collapsed).to eq(true)
+ end
+ end
+
+ context 'when user cannot read list' do
+ it 'does not update list preference for user' do
+ service.execute(list)
+
+ expect(list.preferences_for(user).collapsed).to be_falsy
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb b/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
index 62aaec85162..c939c306d93 100644
--- a/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
@@ -36,16 +36,6 @@ RSpec.shared_examples 'raw snippet blob' do
expect(response.header['Content-Disposition']).to match "attachment; filename=\"#{filepath}\""
end
-
- context 'when the feature flag attachment_with_filename is disabled' do
- it 'returns just attachment in the disposition header' do
- stub_feature_flags(attachment_with_filename: false)
-
- subject
-
- expect(response.header['Content-Disposition']).to eq 'attachment'
- end
- end
end
end
diff --git a/spec/support/shared_examples/controllers/snippet_shared_examples.rb b/spec/support/shared_examples/controllers/snippet_shared_examples.rb
new file mode 100644
index 00000000000..f49cc979368
--- /dev/null
+++ b/spec/support/shared_examples/controllers/snippet_shared_examples.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'snippets views' do
+ let(:params) { {} }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when rendered' do
+ render_views
+
+ it 'avoids N+1 database queries' do
+ # Warming call to load everything non snippet related
+ get(:index, params: params)
+
+ project = create(:project, namespace: user.namespace)
+ create(:project_snippet, project: project, author: user)
+
+ control_count = ActiveRecord::QueryRecorder.new { get(:index, params: params) }.count
+
+ project = create(:project, namespace: user.namespace)
+ create(:project_snippet, project: project, author: user)
+
+ expect { get(:index, params: params) }.not_to exceed_query_limit(control_count)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/trackable_shared_examples.rb b/spec/support/shared_examples/controllers/trackable_shared_examples.rb
deleted file mode 100644
index dac7d8c94ff..00000000000
--- a/spec/support/shared_examples/controllers/trackable_shared_examples.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'a Trackable Controller' do
- describe '#track_event', :snowplow do
- before do
- sign_in user
- end
-
- context 'with no params' do
- controller(described_class) do
- def index
- track_event
- head :ok
- end
- end
-
- it 'tracks the action name', :snowplow do
- get :index
-
- expect_snowplow_event(category: 'AnonymousController', action: 'index')
- end
- end
-
- context 'with params' do
- controller(described_class) do
- def index
- track_event('some_event', category: 'SomeCategory', label: 'errorlabel')
- head :ok
- end
- end
-
- it 'tracks with the specified param' do
- get :index
-
- expect_snowplow_event(category: 'SomeCategory', action: 'some_event', label: 'errorlabel')
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
index 428389a9a01..3f97c031e27 100644
--- a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
@@ -4,27 +4,30 @@ RSpec.shared_examples 'tracking unique visits' do |method|
let(:request_params) { {} }
it 'tracks unique visit if the format is HTML' do
- expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit).with(instance_of(String), target_id)
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:track_event).with(target_id, values: kind_of(String))
get method, params: request_params, format: :html
end
it 'tracks unique visit if DNT is not enabled' do
- expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit).with(instance_of(String), target_id)
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:track_event).with(target_id, values: kind_of(String))
+
request.headers['DNT'] = '0'
get method, params: request_params, format: :html
end
it 'does not track unique visit if DNT is enabled' do
- expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
request.headers['DNT'] = '1'
get method, params: request_params, format: :html
end
it 'does not track unique visit if the format is JSON' do
- expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
get method, params: request_params, format: :json
end
diff --git a/spec/support/shared_examples/features/cascading_settings_shared_examples.rb b/spec/support/shared_examples/features/cascading_settings_shared_examples.rb
new file mode 100644
index 00000000000..29ef3da9a85
--- /dev/null
+++ b/spec/support/shared_examples/features/cascading_settings_shared_examples.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a cascading setting' do
+ context 'when setting is enforced by an ancestor group' do
+ before do
+ visit group_path
+
+ page.within form_group_selector do
+ find(setting_field_selector).check
+ find('[data-testid="enforce-for-all-subgroups-checkbox"]').check
+ end
+
+ click_save_button
+ end
+
+ it 'disables setting in subgroups' do
+ visit subgroup_path
+
+ expect(find("#{setting_field_selector}[disabled]")).to be_checked
+ end
+
+ it 'does not show enforcement checkbox in subgroups' do
+ visit subgroup_path
+
+ expect(page).not_to have_selector '[data-testid="enforce-for-all-subgroups-checkbox"]'
+ end
+
+ it 'displays lock icon with popover', :js do
+ visit subgroup_path
+
+ page.within form_group_selector do
+ find('[data-testid="cascading-settings-lock-icon"]').click
+ end
+
+ page.within '[data-testid="cascading-settings-lock-popover"]' do
+ expect(page).to have_text 'This setting has been enforced by an owner of Foo bar.'
+ expect(page).to have_link 'Foo bar', href: setting_path
+ end
+ 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 da966fd2200..1c816ee4b0a 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
@@ -43,7 +43,7 @@ RSpec.shared_examples 'a creatable merge request' do
expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
- click_button 'Submit merge request'
+ click_button 'Create merge request'
page.within '.issuable-sidebar' do
page.within '.assignee' do
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index 86ba2821c78..808e0be6be2 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -304,7 +304,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
let(:reply_id) { find("#{comments_selector} .note:last-of-type", match: :first)['data-note-id'] }
it 'can be replied to after resolving' do
- click_button "Resolve thread"
+ find('button[data-qa-selector="resolve_discussion_button"]').click
wait_for_requests
refresh
@@ -316,7 +316,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
it 'shows resolved thread when toggled' do
submit_reply('a')
- click_button "Resolve thread"
+ find('button[data-qa-selector="resolve_discussion_button"]').click
wait_for_requests
expect(page).to have_selector(".note-row-#{note_id}", visible: true)
diff --git a/spec/support/shared_examples/features/error_tracking_shared_example.rb b/spec/support/shared_examples/features/error_tracking_shared_example.rb
index 92fc54ce0b0..1bdc5355408 100644
--- a/spec/support/shared_examples/features/error_tracking_shared_example.rb
+++ b/spec/support/shared_examples/features/error_tracking_shared_example.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'error tracking index page' do
it 'renders the error index page', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
- within('div.js-title-container') do
+ within('[data-testid="breadcrumb-links"]') do
expect(page).to have_content(project.namespace.name)
expect(page).to have_content(project.name)
end
diff --git a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
index 7a32f61d4fa..49c3674277d 100644
--- a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
+++ b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'issuable invite members experiments' do
context 'when a privileged user can invite' do
- it 'shows a link for inviting members and follows through to the members page' do
+ it 'shows a link for inviting members and launches invite modal' do
project.add_maintainer(user)
visit issuable_path
@@ -11,14 +11,14 @@ RSpec.shared_examples 'issuable invite members experiments' do
wait_for_requests
page.within '.dropdown-menu-user' do
- expect(page).to have_link('Invite Members', href: project_project_members_path(project))
+ expect(page).to have_link('Invite Members')
expect(page).to have_selector('[data-track-event="click_invite_members"]')
expect(page).to have_selector('[data-track-label="edit_assignee"]')
end
click_link 'Invite Members'
- expect(current_path).to eq project_project_members_path(project)
+ expect(page).to have_content("You're inviting members to the")
end
end
diff --git a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
index 00d3bd08218..7adf303bde4 100644
--- a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-RSpec.shared_examples 'it uploads and commit a new text file' do
- it 'uploads and commit a new text file', :js do
+RSpec.shared_examples 'it uploads and commits a new text file' do
+ it 'uploads and commits a new text file', :js do
find('.add-to-tree').click
page.within('.dropdown-menu') do
@@ -10,7 +10,7 @@ RSpec.shared_examples 'it uploads and commit a new text file' do
wait_for_requests
end
- drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
@@ -32,8 +32,8 @@ RSpec.shared_examples 'it uploads and commit a new text file' do
end
end
-RSpec.shared_examples 'it uploads and commit a new image file' do
- it 'uploads and commit a new image file', :js do
+RSpec.shared_examples 'it uploads and commits a new image file' do
+ it 'uploads and commits a new image file', :js do
find('.add-to-tree').click
page.within('.dropdown-menu') do
@@ -42,7 +42,7 @@ RSpec.shared_examples 'it uploads and commit a new image file' do
wait_for_requests
end
- drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'))
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'), make_visible: true)
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
@@ -58,21 +58,49 @@ RSpec.shared_examples 'it uploads and commit a new image file' do
end
end
-RSpec.shared_examples 'it uploads and commit a new file to a forked project' do
+RSpec.shared_examples 'it uploads and commits a new pdf file' do
+ it 'uploads and commits a new pdf file', :js do
+ find('.add-to-tree').click
+
+ page.within('.dropdown-menu') do
+ click_link('Upload file')
+
+ wait_for_requests
+ end
+
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'), make_visible: true)
+
+ page.within('#modal-upload-blob') do
+ fill_in(:commit_message, with: 'New commit message')
+ fill_in(:branch_name, with: 'upload_image', visible: true)
+ click_button('Upload file')
+ end
+
+ wait_for_all_requests
+
+ visit(project_blob_path(project, 'upload_image/git-cheat-sheet.pdf'))
+
+ expect(page).to have_css('.js-pdf-viewer')
+ end
+end
+
+RSpec.shared_examples 'it uploads and commits a new file to a forked project' do
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
- it 'uploads and commit a new file to a forked project', :js, :sidekiq_might_not_need_inline do
+ it 'uploads and commits a new file to a forked project', :js, :sidekiq_might_not_need_inline do
find('.add-to-tree').click
click_link('Upload file')
expect(page).to have_content(fork_message)
+ wait_for_all_requests
+
find('.add-to-tree').click
click_link('Upload file')
- drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
@@ -95,6 +123,33 @@ RSpec.shared_examples 'it uploads and commit a new file to a forked project' do
end
end
+RSpec.shared_examples 'it uploads a file to a sub-directory' do
+ it 'uploads a file to a sub-directory', :js do
+ click_link 'files'
+
+ page.within('.repo-breadcrumb') do
+ expect(page).to have_content('files')
+ end
+
+ find('.add-to-tree').click
+ click_link('Upload file')
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
+
+ page.within('#modal-upload-blob') do
+ fill_in(:commit_message, with: 'New commit message')
+ end
+
+ click_button('Upload file')
+
+ expect(page).to have_content('New commit message')
+
+ page.within('.repo-breadcrumb') do
+ expect(page).to have_content('files')
+ expect(page).to have_content('doc_sample.txt')
+ end
+ end
+end
+
RSpec.shared_examples 'uploads and commits a new text file via "upload file" button' do
it 'uploads and commits a new text file via "upload file" button', :js do
find('[data-testid="upload-file-button"]').click
diff --git a/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb b/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb
index 06127f2ed8c..6d44a6fde85 100644
--- a/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb
+++ b/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb
@@ -14,11 +14,11 @@ RSpec.shared_examples 'creating an issue for a thread' do
end
it 'can create a new issue for the project' do
- expect { click_button 'Submit issue' }.to change { project.issues.reload.size }.by(1)
+ expect { click_button 'Create issue' }.to change { project.issues.reload.size }.by(1)
end
it 'resolves the discussion in the merge request' do
- click_button 'Submit issue'
+ click_button 'Create issue'
discussion.first_note.reload
@@ -26,7 +26,7 @@ RSpec.shared_examples 'creating an issue for a thread' do
end
it 'shows a flash messaage after resolving a discussion' do
- click_button 'Submit issue'
+ click_button 'Create issue'
page.within '.flash-notice' do
# Only check for the word 'Resolved' since the spec might have resolved
diff --git a/spec/support/shared_examples/features/search_settings_shared_examples.rb b/spec/support/shared_examples/features/search_settings_shared_examples.rb
index 6a507c4be56..dda780690b2 100644
--- a/spec/support/shared_examples/features/search_settings_shared_examples.rb
+++ b/spec/support/shared_examples/features/search_settings_shared_examples.rb
@@ -7,9 +7,7 @@ RSpec.shared_examples 'cannot search settings' do
end
RSpec.shared_examples 'can search settings' do |search_term, non_match_section|
- it 'has search settings field' do
- expect(page).to have_field(placeholder: SearchHelpers::INPUT_PLACEHOLDER)
- end
+ it_behaves_like 'can highlight results', search_term
it 'hides unmatching sections on search' do
expect(page).to have_content(non_match_section)
@@ -21,22 +19,19 @@ RSpec.shared_examples 'can search settings' do |search_term, non_match_section|
end
end
-RSpec.shared_examples 'can search settings with feature flag check' do |search_term, non_match_section|
- let(:flag) { true }
-
- before do
- stub_feature_flags(search_settings_in_page: flag)
-
- visit(visit_path)
+RSpec.shared_examples 'can highlight results' do |search_term|
+ it 'has search settings field' do
+ expect(page).to have_field(placeholder: SearchHelpers::INPUT_PLACEHOLDER)
end
- context 'with feature flag on' do
- it_behaves_like 'can search settings', search_term, non_match_section
- end
+ it 'highlights the search terms' do
+ selector = '.gl-bg-orange-100'
+ fill_in SearchHelpers::INPUT_PLACEHOLDER, with: search_term
- context 'with feature flag off' do
- let(:flag) { false }
+ expect(page).to have_css(selector)
- it_behaves_like 'cannot search settings'
+ page.find_all(selector) do |element|
+ expect(element).to have_content(search_term)
+ end
end
end
diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb
new file mode 100644
index 00000000000..429efbe6ba0
--- /dev/null
+++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb
@@ -0,0 +1,165 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'issue boards sidebar' do
+ include MobileHelpers
+
+ before do
+ first_card.click
+ end
+
+ it 'shows sidebar when clicking issue' do
+ expect(page).to have_selector('[data-testid="issue-boards-sidebar"]')
+ end
+
+ it 'closes sidebar when clicking issue' do
+ expect(page).to have_selector('[data-testid="issue-boards-sidebar"]')
+
+ first_card.click
+
+ expect(page).not_to have_selector('[data-testid="issue-boards-sidebar"]')
+ end
+
+ it 'shows issue details when sidebar is open', :aggregate_failures do
+ page.within('[data-testid="issue-boards-sidebar"]') do
+ expect(page).to have_content(issue.title)
+ expect(page).to have_content(issue.to_reference)
+ end
+ end
+
+ context 'when clicking close button' do
+ before do
+ find('[data-testid="issue-boards-sidebar"] .gl-drawer-close-button').click
+ end
+
+ it 'unhighlights the active issue card' do
+ expect(first_card[:class]).not_to include('is-active')
+ expect(first_card[:class]).not_to include('multi-select')
+ end
+
+ it 'closes sidebar when clicking close button' do
+ expect(page).not_to have_selector('[data-testid="issue-boards-sidebar"]')
+ end
+ end
+
+ context 'in notifications subscription' do
+ it 'displays notifications toggle', :aggregate_failures do
+ page.within('[data-testid="sidebar-notifications"]') do
+ expect(page).to have_selector('[data-testid="notification-subscribe-toggle"]')
+ expect(page).to have_content('Notifications')
+ expect(page).not_to have_content('Notifications have been disabled by the project or group owner')
+ end
+ end
+
+ it 'shows toggle as on then as off as user toggles to subscribe and unsubscribe', :aggregate_failures do
+ toggle = find('[data-testid="notification-subscribe-toggle"]')
+
+ toggle.click
+
+ expect(toggle).to have_css("button.is-checked")
+
+ toggle.click
+
+ expect(toggle).not_to have_css("button.is-checked")
+ end
+
+ context 'when notifications have been disabled' do
+ before do
+ project.update_attribute(:emails_disabled, true)
+
+ refresh_and_click_first_card
+ end
+
+ it 'displays a message that notifications have been disabled' do
+ page.within('[data-testid="sidebar-notifications"]') do
+ expect(page).not_to have_selector('[data-testid="notification-subscribe-toggle"]')
+ expect(page).to have_content('Notifications have been disabled by the project or group owner')
+ end
+ end
+ end
+ end
+
+ context 'in time tracking' do
+ it 'displays time tracking feature with default message' do
+ page.within('[data-testid="time-tracker"]') do
+ expect(page).to have_content('Time tracking')
+ expect(page).to have_content('No estimate or time spent')
+ end
+ end
+
+ context 'when only spent time is recorded' do
+ before do
+ issue.timelogs.create!(time_spent: 3600, user: user)
+
+ refresh_and_click_first_card
+ end
+
+ it 'shows the total time spent only' do
+ page.within('[data-testid="time-tracker"]') do
+ expect(page).to have_content('Spent: 1h')
+ expect(page).not_to have_content('Estimated')
+ end
+ end
+ end
+
+ context 'when only estimated time is recorded' do
+ before do
+ issue.update!(time_estimate: 3600)
+
+ refresh_and_click_first_card
+ end
+
+ it 'shows the estimated time only', :aggregate_failures do
+ page.within('[data-testid="time-tracker"]') do
+ expect(page).to have_content('Estimated: 1h')
+ expect(page).not_to have_content('Spent')
+ end
+ end
+ end
+
+ context 'when estimated and spent times are available' do
+ before do
+ issue.timelogs.create!(time_spent: 1800, user: user)
+ issue.update!(time_estimate: 3600)
+
+ refresh_and_click_first_card
+ end
+
+ it 'shows time tracking progress bar' do
+ page.within('[data-testid="time-tracker"]') do
+ expect(page).to have_selector('[data-testid="timeTrackingComparisonPane"]')
+ end
+ end
+
+ it 'shows both estimated and spent time text', :aggregate_failures do
+ page.within('[data-testid="time-tracker"]') do
+ expect(page).to have_content('Spent 30m')
+ expect(page).to have_content('Est 1h')
+ end
+ end
+ end
+
+ context 'when limitedToHours instance option is turned on' do
+ before do
+ # 3600+3600*24 = 1d 1h or 25h
+ issue.timelogs.create!(time_spent: 3600 + 3600 * 24, user: user)
+ stub_application_setting(time_tracking_limit_to_hours: true)
+
+ refresh_and_click_first_card
+ end
+
+ it 'shows the total time spent only' do
+ page.within('[data-testid="time-tracker"]') do
+ expect(page).to have_content('Spent: 25h')
+ end
+ end
+ end
+ end
+
+ def refresh_and_click_first_card
+ page.refresh
+
+ wait_for_requests
+
+ first_card.click
+ end
+end
diff --git a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
index 2f8ebd0d264..8a6d5d88ca6 100644
--- a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
@@ -240,7 +240,7 @@ RSpec.shared_examples 'User creates wiki page' do
end
end
- it "shows the emoji autocompletion dropdown" do
+ it "shows the emoji autocompletion dropdown", :js do
click_link("New page")
page.within(".wiki-form") do
diff --git a/spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb
index d3d2a36147d..4fea450bd64 100644
--- a/spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb
@@ -13,7 +13,7 @@ RSpec.shared_examples 'User views Git access wiki page' do
expect(page).to have_text("Clone repository #{wiki.full_path}")
- within('.git-clone-holder') do
+ within('.js-git-clone-holder') do
expect(page).to have_css('#clone-dropdown', text: 'HTTP')
expect(page).to have_field('clone_url', with: wiki.http_url_to_repo)
diff --git a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
index a22d98f20c4..1a981f42086 100644
--- a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
@@ -38,19 +38,19 @@ RSpec.shared_examples 'User previews wiki changes' do
end
end
- context "when there are no spaces or hyphens in the page name" do
+ context "when there are no spaces or hyphens in the page name", :js do
let(:wiki_page) { build(:wiki_page, wiki: wiki, title: 'a/b/c/d', content: page_content) }
it_behaves_like 'rewrites relative links'
end
- context "when there are spaces in the page name" do
+ context "when there are spaces in the page name", :js do
let(:wiki_page) { build(:wiki_page, wiki: wiki, title: 'a page/b page/c page/d page', content: page_content) }
it_behaves_like 'rewrites relative links'
end
- context "when there are hyphens in the page name" do
+ context "when there are hyphens in the page name", :js do
let(:wiki_page) { build(:wiki_page, wiki: wiki, title: 'a-page/b-page/c-page/d-page', content: page_content) }
it_behaves_like 'rewrites relative links'
diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
index 1e325535e81..d185e9dd81c 100644
--- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
@@ -11,7 +11,7 @@ RSpec.shared_examples 'User updates wiki page' do
sign_in(user)
end
- context 'when wiki is empty' do
+ context 'when wiki is empty', :js do
before do |example|
visit(wiki_path(wiki))
@@ -57,7 +57,7 @@ RSpec.shared_examples 'User updates wiki page' do
it_behaves_like 'wiki file attachments'
end
- context 'when wiki is not empty' do
+ context 'when wiki is not empty', :js do
let!(:wiki_page) { create(:wiki_page, wiki: wiki, title: 'home', content: 'Home page') }
before do
@@ -147,7 +147,7 @@ RSpec.shared_examples 'User updates wiki page' do
it_behaves_like 'wiki file attachments'
end
- context 'when the page is in a subdir' do
+ context 'when the page is in a subdir', :js do
let(:page_name) { 'page_name' }
let(:page_dir) { "foo/bar/#{page_name}" }
let!(:wiki_page) { create(:wiki_page, wiki: wiki, title: page_dir, content: 'Home page') }
diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_empty_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_empty_shared_examples.rb
index 14180d503df..3514ce286d6 100644
--- a/spec/support/shared_examples/features/wiki/user_views_wiki_empty_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_wiki_empty_shared_examples.rb
@@ -20,11 +20,11 @@ RSpec.shared_examples 'User views empty wiki' do
end
end
- shared_examples 'empty wiki message' do |writable: false, issuable: false, confluence: false|
+ shared_examples 'empty wiki message' do |writable: false, issuable: false, confluence: false, expect_button: true|
# This mirrors the logic in:
# - app/views/shared/empty_states/_wikis.html.haml
# - WikiHelper#wiki_empty_state_messages
- it 'shows the empty state message with the expected elements' do
+ it 'shows the empty state message with the expected elements', :js do
visit wiki_path(wiki)
if writable
@@ -37,7 +37,7 @@ RSpec.shared_examples 'User views empty wiki' do
if issuable && !writable
expect(element).to have_content("improve the wiki for this #{container_name}")
expect(element).to have_link("issue tracker", href: project_issues_path(project))
- expect(element).to have_link("Suggest wiki improvement", href: new_project_issue_path(project))
+ expect(element.has_link?("Suggest wiki improvement", href: new_project_issue_path(project))).to be(expect_button)
else
expect(element).not_to have_content("improve the wiki for this #{container_name}")
expect(element).not_to have_link("issue tracker")
diff --git a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb
index bb4270d7db6..fc795012ce7 100644
--- a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb
@@ -21,13 +21,13 @@ RSpec.shared_examples 'a mutation which can mutate a spammable' do
end
end
- describe "#with_spam_action_response_fields" do
+ describe "#spam_action_response_fields" do
it 'resolves with spam action fields' do
subject
# NOTE: We do not need to assert on the specific values of spam action fields here, we only need
- # to verify that #with_spam_action_response_fields was invoked and that the fields are present in the
- # response. The specific behavior of #with_spam_action_response_fields is covered in the
+ # to verify that #spam_action_response_fields was invoked and that the fields are present in the
+ # response. The specific behavior of #spam_action_response_fields is covered in the
# HasSpamActionResponseFields unit tests.
expect(mutation_response.keys)
.to include('spam', 'spamLogId', 'needsCaptchaResponse', 'captchaSiteKey')
diff --git a/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb
index cfa12171b7e..022e2308517 100644
--- a/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb
@@ -10,22 +10,40 @@ RSpec.shared_examples 'an assignable resource' do
describe '#resolve' do
let_it_be(:assignee) { create(:user) }
let_it_be(:assignee2) { create(:user) }
+
let(:assignee_usernames) { [assignee.username] }
let(:mutated_resource) { subject[resource.class.name.underscore.to_sym] }
+ let(:mode) { described_class.arguments['operationMode'].default_value }
- subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, assignee_usernames: assignee_usernames) }
-
- before do
- resource.project.add_developer(assignee)
- resource.project.add_developer(assignee2)
+ subject do
+ mutation.resolve(project_path: resource.project.full_path,
+ iid: resource.iid,
+ operation_mode: mode,
+ assignee_usernames: assignee_usernames)
end
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
+ it 'does not change assignees if the resource is not accessible to the assignees' do
+ resource.project.add_developer(user)
+
+ expect { subject }.not_to change { resource.reload.assignee_ids }
+ end
+
+ it 'returns an operational error if the resource is not accessible to the assignees' do
+ resource.project.add_developer(user)
+
+ result = subject
+
+ expect(result[:errors]).to include a_string_matching(/Cannot assign/)
+ end
+
context 'when the user can update the resource' do
before do
+ resource.project.add_developer(assignee)
+ resource.project.add_developer(assignee2)
resource.project.add_developer(user)
end
diff --git a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
index f78ea364147..eaeb5faee3b 100644
--- a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
@@ -44,7 +44,7 @@
# end
# end
#
-RSpec.shared_examples 'sorted paginated query' do
+RSpec.shared_examples 'sorted paginated query' do |conditions = {}|
# Provided as a convenience when constructing queries using string concatenation
let(:page_info) { 'pageInfo { startCursor endCursor }' }
# Convenience for using default implementation of pagination_results_data
@@ -123,6 +123,16 @@ RSpec.shared_examples 'sorted paginated query' do
expect(results).to eq first_page
end
end
+
+ context 'when last and sort params are present', if: conditions[:is_reversible] do
+ let(:params) { sort_argument.merge(last: 1) }
+
+ it 'fetches last elements without error' do
+ post_graphql(pagination_query(params), current_user: current_user)
+
+ expect(results.first).to eq(expected_results.last)
+ end
+ end
end
end
end
diff --git a/spec/support/shared_examples/graphql/spam_protection_shared_examples.rb b/spec/support/shared_examples/graphql/spam_protection_shared_examples.rb
new file mode 100644
index 00000000000..8fb89a4f80e
--- /dev/null
+++ b/spec/support/shared_examples/graphql/spam_protection_shared_examples.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples 'has spam protection' do
+ include AfterNextHelpers
+
+ describe '#check_spam_action_response!' do
+ let(:variables) { nil }
+ let(:headers) { {} }
+ let(:spam_log_id) { 123 }
+ let(:captcha_site_key) { 'abc123' }
+
+ def send_request
+ post_graphql_mutation(mutation, current_user: current_user)
+ end
+
+ before do
+ allow_next(mutation_class).to receive(:spam_action_response_fields).and_return(
+ spam: spam,
+ needs_captcha_response: render_captcha,
+ spam_log_id: spam_log_id,
+ captcha_site_key: captcha_site_key
+ )
+ end
+
+ context 'when the object is spam (DISALLOW)' do
+ shared_examples 'disallow response' do
+ it 'informs the client that the request was denied as spam' do
+ send_request
+
+ expect(graphql_errors)
+ .to contain_exactly a_hash_including('message' => ::Mutations::SpamProtection::SPAM_DISALLOWED_MESSAGE)
+ expect(graphql_errors)
+ .to contain_exactly a_hash_including('extensions' => { "spam" => true })
+ end
+ end
+
+ let(:spam) { true }
+
+ context 'and no CAPTCHA is available' do
+ let(:render_captcha) { false }
+
+ it_behaves_like 'disallow response'
+ end
+
+ context 'and a CAPTCHA is required' do
+ let(:render_captcha) { true }
+
+ it_behaves_like 'disallow response'
+ end
+ end
+
+ context 'when the object is not spam (CONDITIONAL ALLOW)' do
+ let(:spam) { false }
+
+ context 'and no CAPTCHA is required' do
+ let(:render_captcha) { false }
+
+ it 'does not return a to-level error' do
+ send_request
+
+ expect(graphql_errors).to be_blank
+ end
+ end
+
+ context 'and a CAPTCHA is required' do
+ let(:render_captcha) { true }
+
+ it 'informs the client that the request may be retried after solving the CAPTCHA' do
+ send_request
+
+ expect(graphql_errors)
+ .to contain_exactly a_hash_including('message' => ::Mutations::SpamProtection::NEEDS_CAPTCHA_RESPONSE_MESSAGE)
+ expect(graphql_errors)
+ .to contain_exactly a_hash_including('extensions' => {
+ "captcha_site_key" => captcha_site_key,
+ "needs_captcha_response" => true,
+ "spam_log_id" => spam_log_id
+ })
+ end
+ end
+ end
+ end
+end
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 bc091a678e2..efb2c466f70 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
@@ -13,18 +13,18 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
it 'raises an error if a required property is missing', :aggregate_failures do
expect { subject(deprecated: { milestone: '1.10' }) }.to raise_error(
ArgumentError,
- 'Please provide a `reason` within `deprecated`'
+ include("Reason can't be blank")
)
expect { subject(deprecated: { reason: 'Deprecation reason' }) }.to raise_error(
ArgumentError,
- 'Please provide a `milestone` within `deprecated`'
+ include("Milestone can't be blank")
)
end
it 'raises an error if milestone is not a String', :aggregate_failures do
expect { subject(deprecated: { milestone: 1.10, reason: 'Deprecation reason' }) }.to raise_error(
ArgumentError,
- '`milestone` must be a `String`'
+ include("Milestone must be a string")
)
end
end
@@ -49,4 +49,22 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
expect(deprecable.description).to be_nil
end
+
+ it 'adds information about the replacement if provided' do
+ deprecable = subject(deprecated: { milestone: '1.10', reason: :renamed, replacement: 'Foo.bar' })
+
+ expect(deprecable.deprecation_reason).to include 'Please use `Foo.bar`'
+ end
+
+ it 'supports named reasons: renamed' do
+ deprecable = subject(deprecated: { milestone: '1.10', reason: :renamed })
+
+ expect(deprecable.deprecation_reason).to include 'This was renamed.'
+ end
+
+ it 'supports named reasons: discouraged' do
+ deprecable = subject(deprecated: { milestone: '1.10', reason: :discouraged })
+
+ expect(deprecable.deprecation_reason).to include 'Use of this is not recommended.'
+ end
end
diff --git a/spec/support/shared_examples/helpers/groups_shared_examples.rb b/spec/support/shared_examples/helpers/groups_shared_examples.rb
new file mode 100644
index 00000000000..9c74d25b31f
--- /dev/null
+++ b/spec/support/shared_examples/helpers/groups_shared_examples.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+# This shared_example requires the following variables:
+# - current_user
+# - group
+# - type, the issuable type (ie :issues, :merge_requests)
+# - count_service, the Service used by the specified issuable type
+
+RSpec.shared_examples 'cached issuables count' do
+ subject { helper.cached_issuables_count(group, type: type) }
+
+ before do
+ allow(helper).to receive(:current_user) { current_user }
+ allow(count_service).to receive(:new).and_call_original
+ end
+
+ it 'calls the correct service class' do
+ subject
+ expect(count_service).to have_received(:new).with(group, current_user)
+ end
+
+ it 'returns all digits for count value under 1000' do
+ allow_next_instance_of(count_service) do |service|
+ allow(service).to receive(:count).and_return(999)
+ end
+
+ expect(subject).to eq('999')
+ end
+
+ it 'returns truncated digits for count value over 1000' do
+ allow_next_instance_of(count_service) do |service|
+ allow(service).to receive(:count).and_return(2300)
+ end
+
+ expect(subject).to eq('2.3k')
+ end
+
+ it 'returns truncated digits for count value over 10000' do
+ allow_next_instance_of(count_service) do |service|
+ allow(service).to receive(:count).and_return(12560)
+ end
+
+ expect(subject).to eq('12.6k')
+ end
+
+ it 'returns truncated digits for count value over 100000' do
+ allow_next_instance_of(count_service) do |service|
+ allow(service).to receive(:count).and_return(112560)
+ end
+
+ expect(subject).to eq('112.6k')
+ end
+end
diff --git a/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb b/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb
index bdb0316bf5a..d5ebda28f0a 100644
--- a/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb
+++ b/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb
@@ -8,7 +8,7 @@ RSpec.shared_examples 'API::CI::Runner application context metadata' do |api_rou
send_request
- Labkit::Context.with_context do |context|
+ Gitlab::ApplicationContext.with_raw_context do |context|
expected_context = {
'meta.caller_id' => api_route,
'meta.user' => job.user.username,
diff --git a/spec/support/shared_examples/lib/api/internal_base_shared_examples.rb b/spec/support/shared_examples/lib/api/internal_base_shared_examples.rb
index dfa1388e0bb..ef08537dfe9 100644
--- a/spec/support/shared_examples/lib/api/internal_base_shared_examples.rb
+++ b/spec/support/shared_examples/lib/api/internal_base_shared_examples.rb
@@ -1,17 +1,6 @@
# frozen_string_literal: true
RSpec.shared_examples 'actor key validations' do
- context 'key id is not provided' do
- let(:key_id) { nil }
-
- it 'returns an error message' do
- subject
-
- expect(json_response['success']).to be_falsey
- expect(json_response['message']).to eq('Could not find a user without a key')
- end
- end
-
context 'key does not exist' do
let(:key_id) { non_existing_record_id }
diff --git a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
index 0df1af3b10a..9c95d1ff9d9 100644
--- a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
@@ -843,6 +843,17 @@ RSpec.shared_examples 'trace with enabled live trace feature' do
expect { subject }.to raise_error(Gitlab::Ci::Trace::AlreadyArchivedError)
expect(build.job_artifacts_trace.file.exists?).to be_truthy
end
+
+ context 'when live trace chunks still exist' do
+ before do
+ create(:ci_build_trace_chunk, build: build)
+ end
+
+ it 'removes the traces' do
+ expect { subject }.to raise_error(Gitlab::Ci::Trace::AlreadyArchivedError)
+ expect(build.trace_chunks).to be_empty
+ end
+ end
end
context 'when job is not finished yet' do
diff --git a/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
new file mode 100644
index 00000000000..88e6ffd15a8
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'CTE with MATERIALIZED keyword examples' do
+ describe 'adding MATERIALIZE to the CTE' do
+ let(:options) { {} }
+
+ before do
+ # Clear the cached value before the test
+ Gitlab::Database::AsWithMaterialized.clear_memoization(:materialized_supported)
+ end
+
+ context 'when PG version is <12' do
+ it 'does not add MATERIALIZE keyword' do
+ allow(Gitlab::Database).to receive(:version).and_return('11.1')
+
+ expect(query).to include(expected_query_block_without_materialized)
+ end
+ end
+
+ context 'when PG version is >=12' do
+ it 'adds MATERIALIZE keyword' do
+ allow(Gitlab::Database).to receive(:version).and_return('12.1')
+
+ expect(query).to include(expected_query_block_with_materialized)
+ end
+
+ context 'when version is higher than 12' do
+ it 'adds MATERIALIZE keyword' do
+ allow(Gitlab::Database).to receive(:version).and_return('15.1')
+
+ expect(query).to include(expected_query_block_with_materialized)
+ end
+ end
+
+ context 'when materialized is disabled' do
+ let(:options) { { materialized: false } }
+
+ it 'does not add MATERIALIZE keyword' do
+ expect(query).to include(expected_query_block_without_materialized)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/metrics_middleware_with_worker_attribution_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/metrics_middleware_with_worker_attribution_shared_examples.rb
new file mode 100644
index 00000000000..48dc47e8e9b
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/metrics_middleware_with_worker_attribution_shared_examples.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'metrics middleware with worker attribution' do
+ subject { described_class.new }
+
+ let(:queue) { :test }
+ let(:worker_class) { worker.class }
+ let(:job) { {} }
+ let(:default_labels) do
+ { queue: queue.to_s,
+ worker: worker_class.to_s,
+ boundary: "",
+ external_dependencies: "no",
+ feature_category: "",
+ urgency: "low" }
+ end
+
+ context "when workers are not attributed" do
+ before do
+ stub_const('TestNonAttributedWorker', Class.new)
+ TestNonAttributedWorker.class_eval do
+ include Sidekiq::Worker
+ end
+ end
+
+ it_behaves_like "a metrics middleware" do
+ let(:worker) { TestNonAttributedWorker.new }
+ let(:labels) { default_labels.merge(urgency: "") }
+ end
+ end
+
+ context "when a worker is wrapped into ActiveJob" do
+ before do
+ stub_const('TestWrappedWorker', Class.new)
+ TestWrappedWorker.class_eval do
+ include Sidekiq::Worker
+ end
+ end
+
+ it_behaves_like "a metrics middleware" do
+ let(:job) do
+ {
+ "class" => ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper,
+ "wrapped" => TestWrappedWorker
+ }
+ end
+
+ let(:worker) { TestWrappedWorker.new }
+ let(:labels) { default_labels.merge(urgency: "") }
+ end
+ end
+
+ context "when workers are attributed" do
+ def create_attributed_worker_class(urgency, external_dependencies, resource_boundary, category)
+ klass = Class.new do
+ include Sidekiq::Worker
+ include WorkerAttributes
+
+ urgency urgency if urgency
+ worker_has_external_dependencies! if external_dependencies
+ worker_resource_boundary resource_boundary unless resource_boundary == :unknown
+ feature_category category unless category.nil?
+ end
+ stub_const("TestAttributedWorker", klass)
+ end
+
+ let(:urgency) { nil }
+ let(:external_dependencies) { false }
+ let(:resource_boundary) { :unknown }
+ let(:feature_category) { nil }
+ let(:worker_class) { create_attributed_worker_class(urgency, external_dependencies, resource_boundary, feature_category) }
+ let(:worker) { worker_class.new }
+
+ context "high urgency" do
+ it_behaves_like "a metrics middleware" do
+ let(:urgency) { :high }
+ let(:labels) { default_labels.merge(urgency: "high") }
+ end
+ end
+
+ context "no urgency" do
+ it_behaves_like "a metrics middleware" do
+ let(:urgency) { :throttled }
+ let(:labels) { default_labels.merge(urgency: "throttled") }
+ end
+ end
+
+ context "external dependencies" do
+ it_behaves_like "a metrics middleware" do
+ let(:external_dependencies) { true }
+ let(:labels) { default_labels.merge(external_dependencies: "yes") }
+ end
+ end
+
+ context "cpu boundary" do
+ it_behaves_like "a metrics middleware" do
+ let(:resource_boundary) { :cpu }
+ let(:labels) { default_labels.merge(boundary: "cpu") }
+ end
+ end
+
+ context "memory boundary" do
+ it_behaves_like "a metrics middleware" do
+ let(:resource_boundary) { :memory }
+ let(:labels) { default_labels.merge(boundary: "memory") }
+ end
+ end
+
+ context "feature category" do
+ it_behaves_like "a metrics middleware" do
+ let(:feature_category) { :authentication }
+ let(:labels) { default_labels.merge(feature_category: "authentication") }
+ end
+ end
+
+ context "combined" do
+ it_behaves_like "a metrics middleware" do
+ let(:urgency) { :high }
+ let(:external_dependencies) { true }
+ let(:resource_boundary) { :cpu }
+ let(:feature_category) { :authentication }
+ let(:labels) do
+ default_labels.merge(
+ urgency: "high",
+ external_dependencies: "yes",
+ boundary: "cpu",
+ feature_category: "authentication")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb
index 73beef06855..aa6a51c3646 100644
--- a/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb
@@ -43,4 +43,33 @@ RSpec.shared_examples 'SQL set operator' do |operator_keyword|
expect(set_operator.to_sql).to eq('NULL')
end
end
+
+ describe 'remove_order parameter' do
+ let(:scopes) do
+ [
+ User.where(id: 1).order(id: :desc).limit(1),
+ User.where(id: 2).order(id: :asc).limit(1)
+ ]
+ end
+
+ subject(:union_query) { described_class.new(scopes, remove_order: remove_order).to_sql }
+
+ context 'when remove_order: true' do
+ let(:remove_order) { true }
+
+ it 'removes the ORDER BY from the query' do
+ expect(union_query).not_to include('ORDER BY "users"."id" DESC')
+ expect(union_query).not_to include('ORDER BY "users"."id" ASC')
+ end
+ end
+
+ context 'when remove_order: false' do
+ let(:remove_order) { false }
+
+ it 'does not remove the ORDER BY from the query' do
+ expect(union_query).to include('ORDER BY "users"."id" DESC')
+ expect(union_query).to include('ORDER BY "users"."id" ASC')
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
index aa6e64a3820..4b956c2b566 100644
--- a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
@@ -14,10 +14,6 @@ RSpec.shared_examples 'a daily tracked issuable event' do
expect(track_action(author: user1)).to be_truthy
expect(track_action(author: user1)).to be_truthy
expect(track_action(author: user2)).to be_truthy
- expect(track_action(author: user3, time: time - 3.days)).to be_truthy
-
- expect(count_unique(date_from: time, date_to: time)).to eq(2)
- expect(count_unique(date_from: time - 5.days, date_to: 1.day.since(time))).to eq(3)
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 0143bf693c7..b10ebb4d2a3 100644
--- a/spec/support/shared_examples/mailers/notify_shared_examples.rb
+++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb
@@ -225,7 +225,7 @@ RSpec.shared_examples 'a note email' do
sender = subject.header[:from].addrs[0]
aggregate_failures do
- expect(sender.display_name).to eq(note_author.name)
+ expect(sender.display_name).to eq("#{note_author.name} (@#{note_author.username})")
expect(sender.address).to eq(gitlab_sender)
expect(subject).to deliver_to(recipient.notification_email)
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 7bf2456c548..1b110ab02b5 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
@@ -16,7 +16,9 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_primary_duration_s: record_query ? 0.002 : 0,
db_replica_cached_count: 0,
db_replica_count: 0,
- db_replica_duration_s: 0.0
+ db_replica_duration_s: 0.0,
+ db_primary_wal_count: record_wal_query ? 1 : 0,
+ db_replica_wal_count: 0
)
elsif db_role == :replica
expect(described_class.db_counter_payload).to eq(
@@ -28,7 +30,9 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_primary_duration_s: 0.0,
db_replica_cached_count: record_cached_query ? 1 : 0,
db_replica_count: record_query ? 1 : 0,
- db_replica_duration_s: record_query ? 0.002 : 0
+ db_replica_duration_s: record_query ? 0.002 : 0,
+ db_replica_wal_count: record_wal_query ? 1 : 0,
+ db_primary_wal_count: 0
)
else
expect(described_class.db_counter_payload).to eq(
@@ -66,6 +70,12 @@ RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do
expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_cached_count_total".to_sym, 1) if db_role
end
+ if record_wal_query
+ expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1) if db_role
+ else
+ expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1) if db_role
+ end
+
subscriber.sql(event)
end
diff --git a/spec/support/shared_examples/models/boards/listable_shared_examples.rb b/spec/support/shared_examples/models/boards/listable_shared_examples.rb
index e733a5488fb..250a4c1b1bd 100644
--- a/spec/support/shared_examples/models/boards/listable_shared_examples.rb
+++ b/spec/support/shared_examples/models/boards/listable_shared_examples.rb
@@ -16,18 +16,23 @@ RSpec.shared_examples 'boards listable model' do |list_factory|
end
describe 'scopes' do
+ let_it_be(:list1) { create(list_factory, list_type: :backlog) }
+ let_it_be(:list2) { create(list_factory, list_type: :closed) }
+ let_it_be(:list3) { create(list_factory, position: 1) }
+ let_it_be(:list4) { create(list_factory, position: 2) }
+
describe '.ordered' do
it 'returns lists ordered by type and position' do
- # rubocop:disable Rails/SaveBang
- lists = [
- create(list_factory, list_type: :backlog),
- create(list_factory, list_type: :closed),
- create(list_factory, position: 1),
- create(list_factory, position: 2)
- ]
- # rubocop:enable Rails/SaveBang
-
- expect(described_class.where(id: lists).ordered).to eq([lists[0], lists[2], lists[3], lists[1]])
+ expect(described_class.where(id: [list1, list2, list3, list4]).ordered)
+ .to eq([list1, list3, list4, list2])
+ end
+ end
+
+ describe '.without_types' do
+ it 'excludes lists of given types' do
+ lists = described_class.without_types([:label, :closed])
+
+ expect(lists).to match_array([list1])
end
end
end
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index 7603787a54e..d3f3e15d299 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -138,7 +138,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do
subject.make_externally_installed
- expect(subject).to be_installed
+ expect(subject).to be_externally_installed
end
context 'helm record does not exist' do
@@ -170,7 +170,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do
subject.make_externally_installed
- expect(subject).to be_installed
+ expect(subject).to be_externally_installed
end
end
@@ -180,7 +180,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do
subject.make_externally_installed
- expect(subject).to be_installed
+ expect(subject).to be_externally_installed
end
it 'clears #status_reason' do
@@ -317,6 +317,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
:uninstall_errored | false
:uninstalled | false
:timed_out | false
+ :externally_installed | true
end
with_them do
diff --git a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
index ed2e4fee2de..3acc43eb0da 100644
--- a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
@@ -47,4 +47,15 @@ RSpec.shared_examples 'cluster application version specs' do |application_name|
end
end
end
+
+ describe '#make_externally_installed' do
+ subject { build(application_name) }
+
+ it 'sets to a special version' do
+ subject.make_externally_installed!
+
+ expect(subject).to be_persisted
+ expect(subject.version).to eq('EXTERNALLY_INSTALLED')
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/clusters/prometheus_client_shared.rb b/spec/support/shared_examples/models/clusters/prometheus_client_shared.rb
new file mode 100644
index 00000000000..8d6dcfef925
--- /dev/null
+++ b/spec/support/shared_examples/models/clusters/prometheus_client_shared.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+# Input
+# - factory: [:clusters_applications_prometheus, :clusters_integrations_prometheus]
+RSpec.shared_examples '#prometheus_client shared' do
+ shared_examples 'exception caught for prometheus client' do
+ before do
+ allow(kube_client).to receive(:proxy_url).and_raise(exception)
+ end
+
+ it 'returns nil' do
+ expect(subject.prometheus_client).to be_nil
+ end
+ end
+
+ context 'cluster is nil' do
+ it 'returns nil' do
+ expect(subject.cluster).to be_nil
+ expect(subject.prometheus_client).to be_nil
+ end
+ end
+
+ context "cluster doesn't have kubeclient" do
+ let(:cluster) { create(:cluster) }
+
+ subject { create(factory, cluster: cluster) }
+
+ it 'returns nil' do
+ expect(subject.prometheus_client).to be_nil
+ end
+ end
+
+ context 'cluster has kubeclient' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url }
+ let(:kube_client) { subject.cluster.kubeclient.core_client }
+
+ subject { create(factory, cluster: cluster) }
+
+ before do
+ subject.cluster.platform_kubernetes.namespace = 'a-namespace'
+ stub_kubeclient_discover(cluster.platform_kubernetes.api_url)
+
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ cluster_project: cluster.cluster_project,
+ project: cluster.cluster_project.project)
+ end
+
+ it 'creates proxy prometheus_client' do
+ expect(subject.prometheus_client).to be_instance_of(Gitlab::PrometheusClient)
+ end
+
+ it 'merges proxy_url, options and headers from kube client with prometheus_client options' do
+ expect(Gitlab::PrometheusClient)
+ .to(receive(:new))
+ .with(a_valid_url, kube_client.rest_client.options.merge({
+ headers: kube_client.headers,
+ timeout: PrometheusAdapter::DEFAULT_PROMETHEUS_REQUEST_TIMEOUT_SEC
+ }))
+ subject.prometheus_client
+ end
+
+ context 'when cluster is not reachable' do
+ it_behaves_like 'exception caught for prometheus client' do
+ let(:exception) { Kubeclient::HttpError.new(401, 'Unauthorized', nil) }
+ end
+ end
+
+ context 'when there is a socket error while contacting cluster' do
+ it_behaves_like 'exception caught for prometheus client' do
+ let(:exception) { Errno::ECONNREFUSED }
+ end
+
+ it_behaves_like 'exception caught for prometheus client' do
+ let(:exception) { Errno::ECONNRESET }
+ end
+ end
+
+ context 'when the network is unreachable' do
+ it_behaves_like 'exception caught for prometheus client' do
+ let(:exception) { Errno::ENETUNREACH }
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb
index b73ff516670..fbb94b4f5c1 100644
--- a/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb
+++ b/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb
@@ -34,7 +34,7 @@ RSpec.shared_examples 'Debian Distribution Architecture' do |factory, container,
subject { described_class.with_distribution(architecture.distribution) }
it 'does not return other distributions' do
- expect(subject.to_a).to eq([architecture, architecture_same_distribution])
+ expect(subject.to_a).to match_array([architecture, architecture_same_distribution])
end
end
@@ -42,7 +42,7 @@ RSpec.shared_examples 'Debian Distribution Architecture' do |factory, container,
subject { described_class.with_name(architecture.name) }
it 'does not return other distributions' do
- expect(subject.to_a).to eq([architecture, architecture_same_name])
+ expect(subject.to_a).to match_array([architecture, architecture_same_name])
end
end
end
diff --git a/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb
index bf6fc23116c..23e76d32fb0 100644
--- a/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb
+++ b/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb
@@ -36,7 +36,7 @@ RSpec.shared_examples 'Debian Distribution Component' do |factory, container, ca
subject { described_class.with_distribution(component.distribution) }
it 'does not return other distributions' do
- expect(subject.to_a).to eq([component, component_same_distribution])
+ expect(subject.to_a).to match_array([component, component_same_distribution])
end
end
@@ -44,7 +44,7 @@ RSpec.shared_examples 'Debian Distribution Component' do |factory, container, ca
subject { described_class.with_name(component.name) }
it 'does not return other distributions' do
- expect(subject.to_a).to eq([component, component_same_name])
+ expect(subject.to_a).to match_array([component, component_same_name])
end
end
end
diff --git a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb
index b4ec146df14..9eacacf725f 100644
--- a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb
+++ b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb
@@ -179,7 +179,7 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze|
subject { described_class.with_codename_or_suite(distribution_with_suite.codename) }
it 'does not return other distributions' do
- expect(subject.to_a).to eq([distribution_with_suite, distribution_with_same_codename, distribution_with_codename_and_suite_flipped])
+ expect(subject.to_a).to contain_exactly(distribution_with_suite, distribution_with_same_codename, distribution_with_codename_and_suite_flipped)
end
end
@@ -187,7 +187,7 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze|
subject { described_class.with_codename_or_suite(distribution_with_suite.suite) }
it 'does not return other distributions' do
- expect(subject.to_a).to eq([distribution_with_suite, distribution_with_same_suite, distribution_with_codename_and_suite_flipped])
+ expect(subject.to_a).to contain_exactly(distribution_with_suite, distribution_with_same_suite, distribution_with_codename_and_suite_flipped)
end
end
end
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index abc6e3ecce8..6b243aef3e6 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -354,33 +354,29 @@ RSpec.shared_examples 'wiki model' do
subject.repository.create_file(user, 'image.png', image, branch_name: subject.default_branch, message: 'add image')
end
- shared_examples 'find_file results' do
- it 'returns the latest version of the file if it exists' do
- file = subject.find_file('image.png')
+ it 'returns the latest version of the file if it exists' do
+ file = subject.find_file('image.png')
- expect(file.mime_type).to eq('image/png')
- end
+ expect(file.mime_type).to eq('image/png')
+ end
- it 'returns nil if the page does not exist' do
- expect(subject.find_file('non-existent')).to eq(nil)
- end
+ it 'returns nil if the page does not exist' do
+ expect(subject.find_file('non-existent')).to eq(nil)
+ end
- it 'returns a Gitlab::Git::WikiFile instance' do
- file = subject.find_file('image.png')
+ it 'returns a Gitlab::Git::WikiFile instance' do
+ file = subject.find_file('image.png')
- expect(file).to be_a Gitlab::Git::WikiFile
- end
+ expect(file).to be_a Gitlab::Git::WikiFile
+ end
- it 'returns the whole file' do
- file = subject.find_file('image.png')
- image.rewind
+ it 'returns the whole file' do
+ file = subject.find_file('image.png')
+ image.rewind
- expect(file.raw_data.b).to eq(image.read.b)
- end
+ expect(file.raw_data.b).to eq(image.read.b)
end
- it_behaves_like 'find_file results'
-
context 'when load_content is disabled' do
it 'includes the file data in the Gitlab::Git::WikiFile' do
file = subject.find_file('image.png', load_content: false)
@@ -388,14 +384,6 @@ RSpec.shared_examples 'wiki model' do
expect(file.raw_data).to be_empty
end
end
-
- context 'when feature flag :gitaly_find_file is disabled' do
- before do
- stub_feature_flags(gitaly_find_file: false)
- end
-
- it_behaves_like 'find_file results'
- end
end
describe '#create_page' do
@@ -481,28 +469,53 @@ RSpec.shared_examples 'wiki model' do
end
describe '#delete_page' do
- let(:page) { create(:wiki_page, wiki: wiki) }
+ shared_examples 'delete_page operations' do
+ let(:page) { create(:wiki_page, wiki: wiki) }
- it 'deletes the page' do
- subject.delete_page(page)
+ it 'deletes the page' do
+ subject.delete_page(page)
- expect(subject.list_pages.count).to eq(0)
- end
+ expect(subject.list_pages.count).to eq(0)
+ end
- it 'sets the correct commit email' do
- subject.delete_page(page)
+ it 'sets the correct commit email' do
+ subject.delete_page(page)
- expect(user.commit_email).not_to eq(user.email)
- expect(commit.author_email).to eq(user.commit_email)
- expect(commit.committer_email).to eq(user.commit_email)
+ expect(user.commit_email).not_to eq(user.email)
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ end
+
+ it 'runs after_wiki_activity callbacks' do
+ page
+
+ expect(subject).to receive(:after_wiki_activity)
+
+ subject.delete_page(page)
+ end
end
- it 'runs after_wiki_activity callbacks' do
- page
+ it_behaves_like 'delete_page operations'
- expect(subject).to receive(:after_wiki_activity)
+ context 'when an error is raised' do
+ it 'logs the error and returns false' do
+ page = build(:wiki_page, wiki: wiki)
+ exception = Gitlab::Git::Index::IndexError.new('foo')
+
+ allow(subject.repository).to receive(:delete_file).and_raise(exception)
+
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(exception, action: :deleted, wiki_id: wiki.id)
+
+ expect(subject.delete_page(page)).to be_falsey
+ end
+ end
+
+ context 'when feature flag :gitaly_replace_wiki_delete_page is disabled' do
+ before do
+ stub_feature_flags(gitaly_replace_wiki_delete_page: false)
+ end
- subject.delete_page(page)
+ it_behaves_like 'delete_page operations'
end
end
diff --git a/spec/support/shared_examples/namespaces/recursive_traversal_examples.rb b/spec/support/shared_examples/namespaces/namespace_traversal_examples.rb
index 2c94be61bc1..36e5808fa28 100644
--- a/spec/support/shared_examples/namespaces/recursive_traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/namespace_traversal_examples.rb
@@ -1,6 +1,22 @@
# frozen_string_literal: true
-RSpec.shared_examples 'recursive namespace traversal' do
+RSpec.shared_examples 'namespace traversal' do
+ shared_examples 'recursive version' do |method|
+ let(:recursive_method) { "recursive_#{method}" }
+
+ it "is equivalent to ##{method}" do
+ groups.each do |group|
+ expect(group.public_send(method)).to match_array group.public_send(recursive_method)
+ end
+ end
+
+ it "makes a recursive query" do
+ groups.each do |group|
+ expect { group.public_send(recursive_method).load }.to make_queries_matching(/WITH RECURSIVE/)
+ end
+ end
+ end
+
describe '#self_and_hierarchy' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
@@ -14,6 +30,12 @@ RSpec.shared_examples 'recursive namespace traversal' do
expect(nested_group.self_and_hierarchy).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group)
expect(very_deep_nested_group.self_and_hierarchy).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group)
end
+
+ describe '#recursive_self_and_hierarchy' do
+ let(:groups) { [group, nested_group, very_deep_nested_group] }
+
+ it_behaves_like 'recursive version', :self_and_hierarchy
+ end
end
describe '#ancestors' do
@@ -28,6 +50,12 @@ RSpec.shared_examples 'recursive namespace traversal' do
expect(nested_group.ancestors).to include(group)
expect(group.ancestors).to eq([])
end
+
+ describe '#recursive_ancestors' do
+ let(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
+
+ it_behaves_like 'recursive version', :ancestors
+ end
end
describe '#self_and_ancestors' do
@@ -42,6 +70,12 @@ RSpec.shared_examples 'recursive namespace traversal' do
expect(nested_group.self_and_ancestors).to contain_exactly(group, nested_group)
expect(group.self_and_ancestors).to contain_exactly(group)
end
+
+ describe '#recursive_self_and_ancestors' do
+ let(:groups) { [nested_group, deep_nested_group, very_deep_nested_group] }
+
+ it_behaves_like 'recursive version', :self_and_ancestors
+ end
end
describe '#descendants' do
@@ -58,6 +92,12 @@ RSpec.shared_examples 'recursive namespace traversal' do
expect(nested_group.descendants.to_a).to include(deep_nested_group, very_deep_nested_group)
expect(group.descendants.to_a).to include(nested_group, deep_nested_group, very_deep_nested_group)
end
+
+ describe '#recursive_descendants' do
+ let(:groups) { [group, nested_group, deep_nested_group, very_deep_nested_group] }
+
+ it_behaves_like 'recursive version', :descendants
+ end
end
describe '#self_and_descendants' do
@@ -74,5 +114,11 @@ RSpec.shared_examples 'recursive namespace traversal' do
expect(nested_group.self_and_descendants).to contain_exactly(nested_group, deep_nested_group, very_deep_nested_group)
expect(group.self_and_descendants).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group)
end
+
+ describe '#recursive_self_and_descendants' do
+ let(:groups) { [group, nested_group, deep_nested_group, very_deep_nested_group] }
+
+ it_behaves_like 'recursive version', :self_and_descendants
+ end
end
end
diff --git a/spec/support/shared_examples/nav_sidebar_shared_examples.rb b/spec/support/shared_examples/nav_sidebar_shared_examples.rb
index e084a957785..3e500683712 100644
--- a/spec/support/shared_examples/nav_sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/nav_sidebar_shared_examples.rb
@@ -24,3 +24,13 @@ RSpec.shared_examples 'page has active sub tab' do |title|
.to have_content(title)
end
end
+
+RSpec.shared_examples 'sidebar includes snowplow attributes' do |track_action, track_label, track_property|
+ specify do
+ allow(view).to receive(:tracking_enabled?).and_return(true)
+
+ render
+
+ expect(rendered).to have_css(".nav-sidebar[data-track-action=\"#{track_action}\"][data-track-label=\"#{track_label}\"][data-track-property=\"#{track_property}\"]")
+ end
+end
diff --git a/spec/support/shared_examples/policies/resource_access_token_shared_examples.rb b/spec/support/shared_examples/policies/resource_access_token_shared_examples.rb
index 7710e756e5b..337ad024fc0 100644
--- a/spec/support/shared_examples/policies/resource_access_token_shared_examples.rb
+++ b/spec/support/shared_examples/policies/resource_access_token_shared_examples.rb
@@ -5,16 +5,70 @@ RSpec.shared_examples 'Self-managed Core resource access tokens' do
allow(::Gitlab).to receive(:com?).and_return(false)
end
- context 'with owner' do
+ context 'with owner access' do
let(:current_user) { owner }
- it { is_expected.to be_allowed(:admin_resource_access_tokens) }
+ context 'create resource access tokens' do
+ it { is_expected.to be_allowed(:create_resource_access_tokens) }
+
+ context 'when resource access token creation is not allowed' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ before do
+ group.namespace_settings.update_column(:resource_access_token_creation_allowed, false)
+ end
+
+ it { is_expected.not_to be_allowed(:create_resource_access_tokens) }
+ end
+
+ context 'when parent group has project access token creation disabled' do
+ let(:parent) { create(:group) }
+ let(:group) { create(:group, parent: parent) }
+ let(:project) { create(:project, group: group) }
+
+ before do
+ parent.namespace_settings.update_column(:resource_access_token_creation_allowed, false)
+ end
+
+ it { is_expected.not_to be_allowed(:create_resource_access_tokens) }
+ end
+
+ context 'with a personal namespace project' do
+ let(:namespace) { create(:namespace) }
+ let(:project) { create(:project, namespace: namespace) }
+
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ it { is_expected.to be_allowed(:create_resource_access_tokens) }
+ end
+ end
+
+ context 'read resource access tokens' do
+ it { is_expected.to be_allowed(:read_resource_access_tokens) }
+ end
+
+ context 'destroy resource access tokens' do
+ it { is_expected.to be_allowed(:destroy_resource_access_tokens) }
+ end
end
- context 'with developer' do
+ context 'with developer access' do
let(:current_user) { developer }
- it { is_expected.not_to be_allowed(:admin_resource_access_tokens) }
+ context 'create resource access tokens' do
+ it { is_expected.not_to be_allowed(:create_resource_access_tokens) }
+ end
+
+ context 'read resource access tokens' do
+ it { is_expected.not_to be_allowed(:read_resource_access_tokens) }
+ end
+
+ context 'destroy resource access tokens' do
+ it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) }
+ end
end
end
@@ -24,9 +78,19 @@ RSpec.shared_examples 'GitLab.com Core resource access tokens' do
stub_ee_application_setting(should_check_namespace_plan: true)
end
- context 'with owner' do
+ context 'with owner access' do
let(:current_user) { owner }
- it { is_expected.not_to be_allowed(:admin_resource_access_tokens) }
+ context 'create resource access tokens' do
+ it { is_expected.not_to be_allowed(:create_resource_access_tokens) }
+ end
+
+ context 'read resource access tokens' do
+ it { is_expected.not_to be_allowed(:read_resource_access_tokens) }
+ end
+
+ context 'destroy resource access tokens' do
+ it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) }
+ end
end
end
diff --git a/spec/support/shared_examples/querying_shared_examples.rb b/spec/support/shared_examples/querying_shared_examples.rb
new file mode 100644
index 00000000000..1f554ddb441
--- /dev/null
+++ b/spec/support/shared_examples/querying_shared_examples.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+def update_column_regex(column)
+ /UPDATE.+SET.+#{column}[^=*]=.+FROM.*/m
+end
+
+RSpec.shared_examples 'update on column' do |column|
+ it "#{column} column updated" do
+ qr = ActiveRecord::QueryRecorder.new do
+ subject
+ end
+ expect(qr.log).to include a_string_matching update_column_regex(column)
+ end
+end
+
+RSpec.shared_examples 'no update on column' do |column|
+ it "#{column} column is not updated" do
+ qr = ActiveRecord::QueryRecorder.new do
+ subject
+ end
+ expect(qr.log).not_to include a_string_matching update_column_regex(column)
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
index 4fde68efd60..ca6536444fd 100644
--- a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
@@ -23,7 +23,7 @@ RSpec.shared_examples 'close quick action' do |issuable_type|
it "creates the #{issuable_type} and interprets close quick action accordingly" do
fill_in "#{issuable_type}_title", with: 'bug 345'
fill_in "#{issuable_type}_description", with: "bug description\n/close"
- click_button "Submit #{issuable_type}".humanize
+ click_button "Create #{issuable_type}".humanize
issuable = project.public_send(issuable_type.to_s.pluralize).first
diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
index 54ea876bed2..87aaac673c1 100644
--- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
@@ -205,6 +205,14 @@ RSpec.shared_examples 'empty recipe for not found package' do
'aa/bb/%{project}/ccc' % { project: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path) }
end
+ let(:presenter) { double('::Packages::Conan::PackagePresenter') }
+
+ before do
+ allow(::Packages::Conan::PackagePresenter).to receive(:new)
+ .with(package, user, package.project, any_args)
+ .and_return(presenter)
+ end
+
it 'returns not found' do
allow(::Packages::Conan::PackagePresenter).to receive(:new)
.with(
@@ -248,8 +256,6 @@ RSpec.shared_examples 'recipe download_urls' do
'conanmanifest.txt' => "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt"
}
- allow(presenter).to receive(:recipe_urls) { expected_response }
-
subject
expect(json_response).to eq(expected_response)
@@ -268,8 +274,6 @@ RSpec.shared_examples 'package download_urls' do
'conan_package.tgz' => "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conan_package.tgz"
}
- allow(presenter).to receive(:package_urls) { expected_response }
-
subject
expect(json_response).to eq(expected_response)
@@ -309,13 +313,14 @@ RSpec.shared_examples 'recipe snapshot endpoint' do
context 'with existing package' do
it 'returns a hash of files with their md5 hashes' do
+ conan_file_file = package.package_files.find_by(file_name: 'conanfile.py')
+ conan_manifest_file = package.package_files.find_by(file_name: 'conanmanifest.txt')
+
expected_response = {
- 'conanfile.py' => 'md5hash1',
- 'conanmanifest.txt' => 'md5hash2'
+ 'conanfile.py' => conan_file_file.file_md5,
+ 'conanmanifest.txt' => conan_manifest_file.file_md5
}
- allow(presenter).to receive(:recipe_snapshot) { expected_response }
-
subject
expect(json_response).to eq(expected_response)
@@ -333,13 +338,11 @@ RSpec.shared_examples 'package snapshot endpoint' do
context 'with existing package' do
it 'returns a hash of md5 values for the files' do
expected_response = {
- 'conaninfo.txt' => "md5hash1",
- 'conanmanifest.txt' => "md5hash2",
- 'conan_package.tgz' => "md5hash3"
+ 'conaninfo.txt' => "12345abcde",
+ 'conanmanifest.txt' => "12345abcde",
+ 'conan_package.tgz' => "12345abcde"
}
- allow(presenter).to receive(:package_snapshot) { expected_response }
-
subject
expect(json_response).to eq(expected_response)
diff --git a/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb
new file mode 100644
index 00000000000..c134f7d1839
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'GraphQL query with several integrations requested' do |graphql_query_name:|
+ context 'when several HTTP integrations requested' do
+ let(:params_ai) { { id: global_id_of(active_http_integration) } }
+ let(:params_ii) { { id: global_id_of(inactive_http_integration) } }
+ let(:fields) { "nodes { id name }" }
+
+ let(:single_selection_query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ <<~QUERY
+ ai: #{query_graphql_field(graphql_query_name, params_ai, fields)}
+ QUERY
+ )
+ end
+
+ let(:multi_selection_query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ <<~QUERY
+ ai: #{query_graphql_field(graphql_query_name, params_ai, fields)}
+ ii: #{query_graphql_field(graphql_query_name, params_ii, fields)}
+ QUERY
+ )
+ end
+
+ it 'returns the correct properties of the integrations', :aggregate_failures do
+ post_graphql(multi_selection_query, current_user: current_user)
+
+ expect(graphql_data.dig('project', 'ai', 'nodes')).to include(
+ 'id' => global_id_of(active_http_integration),
+ 'name' => active_http_integration.name
+ )
+
+ expect(graphql_data.dig('project', 'ii', 'nodes')).to include(
+ 'id' => global_id_of(inactive_http_integration),
+ 'name' => inactive_http_integration.name
+ )
+ end
+
+ it 'batches queries' do
+ expect { post_graphql(multi_selection_query, current_user: current_user) }
+ .to issue_same_number_of_queries_as { post_graphql(single_selection_query, current_user: current_user) }.ignoring_cached_queries
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb
index 4a71b696d57..cb06c9fa596 100644
--- a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb
@@ -1,21 +1,13 @@
# frozen_string_literal: true
RSpec.shared_examples 'storing arguments in the application context' do
- around do |example|
- Labkit::Context.with_context { example.run }
- end
-
it 'places the expected params in the application context' do
# Stub the clearing of the context so we can validate it later
- # The `around` block above makes sure we do clean it up later
allow(Labkit::Context).to receive(:pop)
subject
- Labkit::Context.with_context do |context|
- expect(context.to_h)
- .to include(log_hash(expected_params))
- end
+ expect(Gitlab::ApplicationContext.current).to include(log_hash(expected_params))
end
def log_hash(hash)
diff --git a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb
index 7b7d2a33e8c..db70bc75c63 100644
--- a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb
@@ -156,7 +156,7 @@ RSpec.shared_examples 'handling nuget metadata requests with package name and pa
include_context 'with expected presenters dependency groups'
let_it_be(:package_name) { 'Dummy.Package' }
- let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) }
+ let_it_be(:package) { create(:nuget_package, :with_metadatum, name: package_name, project: project) }
let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
subject { get api(url) }
@@ -225,7 +225,7 @@ RSpec.shared_examples 'handling nuget search requests' do |anonymous_requests_ex
let(:take) { 26 }
let(:skip) { 0 }
let(:include_prereleases) { true }
- let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } }
+ let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases }.compact }
subject { get api(url) }
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
index 15976eed021..eb86b7c37d5 100644
--- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -100,7 +100,7 @@ RSpec.shared_examples 'job token for package GET requests' do
end
end
-RSpec.shared_examples 'job token for package uploads' do
+RSpec.shared_examples 'job token for package uploads' do |authorize_endpoint: false|
context 'with job token headers' do
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token).merge(workhorse_headers) }
@@ -111,6 +111,17 @@ RSpec.shared_examples 'job token for package uploads' do
context 'valid token' do
it_behaves_like 'returning response status', :success
+
+ unless authorize_endpoint
+ it 'creates a package with build info' do
+ expect { subject }.to change { Packages::Package.count }.by(1)
+
+ pkg = ::Packages::Package.order_created
+ .last
+
+ expect(pkg.build_infos).to be
+ end
+ end
end
context 'invalid token' do
diff --git a/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb
index 15fb6611b90..abdb468353a 100644
--- a/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb
@@ -43,6 +43,8 @@ end
RSpec.shared_examples 'process rubygems upload' do |user_type, status, add_member = true|
RSpec.shared_examples 'creates rubygems package files' do
it 'creates package files', :aggregate_failures do
+ expect(::Packages::Rubygems::ExtractionWorker).to receive(:perform_async).once
+
expect { subject }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
@@ -51,6 +53,17 @@ RSpec.shared_examples 'process rubygems upload' do |user_type, status, add_membe
package_file = project.packages.last.package_files.reload.last
expect(package_file.file_name).to eq('package.gem')
end
+
+ it 'returns bad request if package creation fails' do
+ file_service = double('file_service', execute: nil)
+
+ expect(::Packages::CreatePackageFileService).to receive(:new).and_return(file_service)
+ expect(::Packages::Rubygems::ExtractionWorker).not_to receive(:perform_async)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
end
context "for user type #{user_type}" do
diff --git a/spec/support/shared_examples/requests/clusters/integrations_controller_shared_examples.rb b/spec/support/shared_examples/requests/clusters/integrations_controller_shared_examples.rb
new file mode 100644
index 00000000000..490c7d12115
--- /dev/null
+++ b/spec/support/shared_examples/requests/clusters/integrations_controller_shared_examples.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples '#create_or_update action' do
+ let(:params) do
+ { integration: { application_type: Clusters::Applications::Prometheus.application_name, enabled: true } }
+ end
+
+ let(:path) { raise NotImplementedError }
+ let(:redirect_path) { raise NotImplementedError }
+
+ describe 'authorization' do
+ subject do
+ post path, params: params
+ end
+
+ it_behaves_like 'a secure endpoint'
+ end
+
+ describe 'functionality' do
+ before do
+ sign_in(user)
+ end
+
+ it 'redirects on success' do
+ post path, params: params
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ expect(response).to redirect_to(redirect_path)
+ expect(flash[:notice]).to be_present
+ end
+
+ it 'redirects on error' do
+ error = ServiceResponse.error(message: 'failed')
+
+ expect_next_instance_of(Clusters::Integrations::CreateService) do |service|
+ expect(service).to receive(:execute).and_return(error)
+ end
+
+ post path, params: params
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ expect(response).to redirect_to(redirect_path)
+ expect(flash[:alert]).to eq(error.message)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
new file mode 100644
index 00000000000..00146335ef7
--- /dev/null
+++ b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+RSpec.shared_examples 'avoid N+1 on environments serialization' do
+ it 'avoids N+1 database queries with grouping', :request_store do
+ create_environment_with_associations(project)
+
+ control = ActiveRecord::QueryRecorder.new { serialize(grouping: true) }
+
+ create_environment_with_associations(project)
+
+ expect { serialize(grouping: true) }.not_to exceed_query_limit(control.count)
+ end
+
+ it 'avoids N+1 database queries without grouping', :request_store do
+ create_environment_with_associations(project)
+
+ control = ActiveRecord::QueryRecorder.new { serialize(grouping: false) }
+
+ create_environment_with_associations(project)
+
+ expect { serialize(grouping: false) }.not_to exceed_query_limit(control.count)
+ end
+
+ def serialize(grouping:)
+ EnvironmentSerializer.new(current_user: user, project: project).yield_self do |serializer|
+ serializer.within_folders if grouping
+ serializer.represent(Environment.where(project: project))
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb b/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb
index 6a4f284ec54..94da405e491 100644
--- a/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb
@@ -13,7 +13,7 @@ RSpec.shared_examples 'lists destroy service' do
development = create(:list, board: board, position: 0)
review = create(:list, board: board, position: 1)
staging = create(:list, board: board, position: 2)
- closed = board.closed_list
+ closed = board.lists.closed.first
described_class.new(parent, user).execute(development)
@@ -24,7 +24,7 @@ RSpec.shared_examples 'lists destroy service' do
end
it 'does not remove list from board when list type is closed' do
- list = board.closed_list
+ list = board.lists.closed.first
service = described_class.new(parent, user)
expect { service.execute(list) }.not_to change(board.lists, :count)
diff --git a/spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb b/spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb
index 41fd286682e..e1143562661 100644
--- a/spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb
@@ -2,14 +2,34 @@
RSpec.shared_examples 'lists list service' do
context 'when the board has a backlog list' do
- let!(:backlog_list) { create(:backlog_list, board: board) }
+ let!(:backlog_list) { create_backlog_list(board) }
it 'does not create a backlog list' do
expect { service.execute(board) }.not_to change(board.lists, :count)
end
it "returns board's lists" do
- expect(service.execute(board)).to eq [backlog_list, list, board.closed_list]
+ expect(service.execute(board)).to eq [backlog_list, list, board.lists.closed.first]
+ end
+
+ context 'when hide_backlog_list is true' do
+ before do
+ board.update_column(:hide_backlog_list, true)
+ end
+
+ it 'hides backlog list' do
+ expect(service.execute(board)).to match_array([board.lists.closed.first, list])
+ end
+ end
+
+ context 'when hide_closed_list is true' do
+ before do
+ board.update_column(:hide_closed_list, true)
+ end
+
+ it 'hides closed list' do
+ expect(service.execute(board)).to match_array([backlog_list, list])
+ end
end
end
@@ -23,25 +43,21 @@ RSpec.shared_examples 'lists list service' do
end
it "returns board's lists" do
- expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list]
+ expect(service.execute(board)).to eq [board.lists.backlog.first, list, board.lists.closed.first]
end
end
context 'when wanting a specific list' do
- let!(:list1) { create(:list, board: board) }
-
it 'returns list specified by id' do
- service = described_class.new(parent, user, list_id: list1.id)
+ service = described_class.new(parent, user, list_id: list.id)
- expect(service.execute(board, create_default_lists: false)).to eq [list1]
+ expect(service.execute(board, create_default_lists: false)).to eq [list]
end
it 'returns empty result when list is not found' do
- external_board = create(:board, resource_parent: create(:project))
- external_list = create(:list, board: external_board)
- service = described_class.new(parent, user, list_id: external_list.id)
+ service = described_class.new(parent, user, list_id: unrelated_list.id)
- expect(service.execute(board, create_default_lists: false)).to eq(List.none)
+ expect(service.execute(board, create_default_lists: false)).to be_empty
end
end
end
diff --git a/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb b/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb
index cbe20928f98..466300017d9 100644
--- a/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb
+++ b/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb
@@ -41,7 +41,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
end.to change(application_class, :count)
expect(cluster_application).to be_persisted
- expect(cluster_application).to be_installed
+ expect(cluster_application).to be_externally_installed
end
end
@@ -53,7 +53,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
it 'marks the application as installed' do
described_class.new(job, user).execute(artifact)
- expect(cluster_application).to be_installed
+ expect(cluster_application).to be_externally_installed
end
end
end
diff --git a/spec/support/shared_examples/services/groups_count_service_shared_examples.rb b/spec/support/shared_examples/services/groups_count_service_shared_examples.rb
new file mode 100644
index 00000000000..84937c3d4d7
--- /dev/null
+++ b/spec/support/shared_examples/services/groups_count_service_shared_examples.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+# The calling spec should use `:use_clean_rails_memory_store_caching`
+# when including this shared example. E.g.:
+#
+# describe MyCountService, :use_clean_rails_memory_store_caching do
+# it_behaves_like 'a counter caching service with threshold'
+# end
+RSpec.shared_examples 'a counter caching service with threshold' do
+ let(:cache_key) { subject.cache_key }
+ let(:under_threshold) { described_class::CACHED_COUNT_THRESHOLD - 1 }
+ let(:over_threshold) { described_class::CACHED_COUNT_THRESHOLD + 1 }
+
+ context 'when cache is empty' do
+ before do
+ Rails.cache.delete(cache_key)
+ end
+
+ it 'refreshes cache if value over threshold' do
+ allow(subject).to receive(:uncached_count).and_return(over_threshold)
+
+ expect(subject.count).to eq(over_threshold)
+ expect(Rails.cache.read(cache_key)).to eq(over_threshold)
+ end
+
+ it 'does not refresh cache if value under threshold' do
+ allow(subject).to receive(:uncached_count).and_return(under_threshold)
+
+ expect(subject.count).to eq(under_threshold)
+ expect(Rails.cache.read(cache_key)).to be_nil
+ end
+ end
+
+ context 'when cached count is under the threshold value' do
+ before do
+ Rails.cache.write(cache_key, under_threshold)
+ end
+
+ it 'does not refresh cache' do
+ expect(Rails.cache).not_to receive(:write)
+ expect(subject.count).to eq(under_threshold)
+ end
+ end
+
+ context 'when cached count is over the threshold value' do
+ before do
+ Rails.cache.write(cache_key, over_threshold)
+ end
+
+ it 'does not refresh cache' do
+ expect(Rails.cache).not_to receive(:write)
+ expect(subject.count).to eq(over_threshold)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
new file mode 100644
index 00000000000..ccc287c10de
--- /dev/null
+++ b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+shared_examples_for 'service deleting todos' do
+ before do
+ stub_feature_flags(destroy_issuable_todos_async: group)
+ end
+
+ it 'destroys associated todos asynchronously' do
+ expect(TodosDestroyer::DestroyedIssuableWorker)
+ .to receive(:perform_async)
+ .with(issuable.id, issuable.class.name)
+
+ subject.execute(issuable)
+ end
+
+ context 'when destroy_issuable_todos_async feature is disabled for group' do
+ before do
+ stub_feature_flags(destroy_issuable_todos_async: false)
+ end
+
+ it 'destroy associated todos synchronously' do
+ expect_next_instance_of(TodosDestroyer::DestroyedIssuableWorker) do |worker|
+ expect(worker)
+ .to receive(:perform)
+ .with(issuable.id, issuable.class.name)
+ end
+
+ subject.execute(issuable)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/merge_request_shared_examples.rb b/spec/support/shared_examples/services/merge_request_shared_examples.rb
index 56179b6cd00..178b6bc47e1 100644
--- a/spec/support/shared_examples/services/merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/services/merge_request_shared_examples.rb
@@ -73,3 +73,93 @@ RSpec.shared_examples 'merge request reviewers cache counters invalidator' do
described_class.new(project, user, {}).execute(merge_request)
end
end
+
+RSpec.shared_examples_for 'a service that can create a merge request' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'creates a merge request with the correct target branch' do
+ branch = push_options[:target] || project.default_branch
+
+ expect { service.execute }.to change { MergeRequest.count }.by(1)
+ expect(last_mr.target_branch).to eq(branch)
+ end
+
+ context 'when project has been forked', :sidekiq_might_not_need_inline do
+ let(:forked_project) { fork_project(project, user1, repository: true) }
+ let(:service) { described_class.new(forked_project, user1, changes, push_options) }
+
+ before do
+ allow(forked_project).to receive(:empty_repo?).and_return(false)
+ end
+
+ it 'sets the correct source and target project' do
+ service.execute
+
+ expect(last_mr.source_project).to eq(forked_project)
+ expect(last_mr.target_project).to eq(project)
+ end
+ end
+end
+
+RSpec.shared_examples_for 'a service that does not create a merge request' do
+ it do
+ expect { service.execute }.not_to change { MergeRequest.count }
+ end
+end
+
+# In the non-foss version of GitLab, there can be many assignees, so
+# there 'count' can be something other than 0 or 1. In the foss
+# version of GitLab, there can be only one assignee though, so 'count'
+# can only be 0 or 1.
+RSpec.shared_examples_for 'a service that can change assignees of a merge request' do |count|
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'changes assignee count' do
+ service.execute
+
+ expect(last_mr.assignees.count).to eq(count)
+ end
+end
+
+RSpec.shared_examples 'with an existing branch that has a merge request open' do |count|
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can change assignees of a merge request', count
+end
+
+RSpec.shared_examples 'when coupled with the `create` push option' do |count|
+ let(:push_options) { { create: true, assign: assigned, unassign: unassigned } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can change assignees of a merge request', count
+end
+
+RSpec.shared_examples 'with a new branch' do |count|
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ service.execute
+
+ expect(service.errors).to include(error_mr_required)
+ end
+
+ it_behaves_like 'when coupled with the `create` push option', count
+end
+
+RSpec.shared_examples 'with an existing branch but no open MR' do |count|
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ service.execute
+
+ expect(service.errors).to include(error_mr_required)
+ end
+
+ it_behaves_like 'when coupled with the `create` push option', count
+end
diff --git a/spec/support/shared_examples/services/notification_service_shared_examples.rb b/spec/support/shared_examples/services/notification_service_shared_examples.rb
index 43fe6789145..cfd674e3c43 100644
--- a/spec/support/shared_examples/services/notification_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/notification_service_shared_examples.rb
@@ -45,7 +45,7 @@ RSpec.shared_examples 'group emails are disabled' do
before do
reset_delivered_emails!
- target_group.clear_memoization(:emails_disabled)
+ target_group.clear_memoization(:emails_disabled_memoized)
end
it 'sends no emails with group emails disabled' do
diff --git a/spec/support/shared_examples/services/snippets_shared_examples.rb b/spec/support/shared_examples/services/snippets_shared_examples.rb
index 10add3a7299..0c4db7ded69 100644
--- a/spec/support/shared_examples/services/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/services/snippets_shared_examples.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
RSpec.shared_examples 'checking spam' do
- let(:request) { double(:request) }
+ let(:request) { double(:request, headers: headers) }
+ let(:headers) { nil }
let(:api) { true }
let(:captcha_response) { 'abc123' }
let(:spam_log_id) { 1 }
@@ -44,6 +45,44 @@ RSpec.shared_examples 'checking spam' do
subject
end
+ context 'when CAPTCHA arguments are passed in the headers' do
+ let(:headers) do
+ {
+ 'X-GitLab-Spam-Log-Id' => spam_log_id,
+ 'X-GitLab-Captcha-Response' => captcha_response
+ }
+ end
+
+ let(:extra_opts) do
+ {
+ request: request,
+ api: api,
+ disable_spam_action_service: disable_spam_action_service
+ }
+ end
+
+ it 'executes the SpamActionService correctly' do
+ spam_params = Spam::SpamParams.new(
+ api: api,
+ captcha_response: captcha_response,
+ spam_log_id: spam_log_id
+ )
+ expect_next_instance_of(
+ Spam::SpamActionService,
+ {
+ spammable: kind_of(Snippet),
+ request: request,
+ user: an_instance_of(User),
+ action: action
+ }
+ ) do |instance|
+ expect(instance).to receive(:execute).with(spam_params: spam_params)
+ end
+
+ subject
+ end
+ end
+
context 'when spam action service is disabled' do
let(:disable_spam_action_service) { true }
diff --git a/spec/support/shared_examples/workers/in_product_marketing_email_shared_example.rb b/spec/support/shared_examples/workers/in_product_marketing_email_shared_example.rb
new file mode 100644
index 00000000000..c4391f61369
--- /dev/null
+++ b/spec/support/shared_examples/workers/in_product_marketing_email_shared_example.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'in-product marketing email' do
+ before do
+ stub_application_setting(in_product_marketing_emails_enabled: in_product_marketing_emails_enabled)
+ stub_experiment(in_product_marketing_emails: experiment_active)
+ allow(::Gitlab).to receive(:com?).and_return(is_gitlab_com)
+ end
+
+ it 'executes the email service service' do
+ expect(Namespaces::InProductMarketingEmailsService).to receive(:send_for_all_tracks_and_intervals).exactly(executes_service).times
+
+ subject.perform
+ end
+end
diff --git a/spec/support/shared_examples/workers/worker_with_data_consistency_shared_example.rb b/spec/support/shared_examples/workers/worker_with_data_consistency_shared_example.rb
new file mode 100644
index 00000000000..ec09c0380f9
--- /dev/null
+++ b/spec/support/shared_examples/workers/worker_with_data_consistency_shared_example.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'worker with data consistency' do |worker_class, data_consistency: :always, feature_flag: nil|
+ describe '.get_data_consistency_feature_flag_enabled?' do
+ it 'returns true' do
+ expect(worker_class.get_data_consistency_feature_flag_enabled?).to be(true)
+ end
+
+ if feature_flag
+ context "when feature flag :#{feature_flag} is disabled" do
+ before do
+ stub_feature_flags(feature_flag => false)
+ end
+
+ it 'returns false' do
+ expect(worker_class.get_data_consistency_feature_flag_enabled?).to be(false)
+ end
+ end
+ end
+ end
+
+ describe '.get_data_consistency' do
+ it 'returns correct data consistency' do
+ expect(worker_class.get_data_consistency).to eq(data_consistency)
+ end
+ end
+end
diff --git a/spec/support/sidekiq_middleware.rb b/spec/support/sidekiq_middleware.rb
index 62f81ef1669..cbd6163d46b 100644
--- a/spec/support/sidekiq_middleware.rb
+++ b/spec/support/sidekiq_middleware.rb
@@ -15,20 +15,14 @@ end
# If Sidekiq::Testing.inline! is used, SQL transactions done inside
# Sidekiq worker are included in the SQL query limit (in a real
-# deployment sidekiq worker is executed separately). To avoid
-# increasing SQL limit counter, the request is marked as whitelisted
-# during Sidekiq block
+# deployment sidekiq worker is executed separately). To avoid increasing
+# SQL limit counter, query limiting is disabled during Sidekiq block
class DisableQueryLimit
def call(worker_instance, msg, queue)
- transaction = Gitlab::QueryLimiting::Transaction.current
-
- if !transaction.respond_to?(:whitelisted) || transaction.whitelisted
- yield
- else
- transaction.whitelisted = true
- yield
- transaction.whitelisted = false
- end
+ ::Gitlab::QueryLimiting.disable!('https://mock-issue')
+ yield
+ ensure
+ ::Gitlab::QueryLimiting.enable!
end
end