summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/capybara.rb2
-rw-r--r--spec/support/capybara_slow_finder.rb32
-rw-r--r--spec/support/cross_database_modification.rb9
-rw-r--r--spec/support/database/multiple_databases.rb48
-rw-r--r--spec/support/database/prevent_cross_database_modification.rb10
-rw-r--r--spec/support/database_cleaner.rb23
-rw-r--r--spec/support/db_cleaner.rb38
-rw-r--r--spec/support/finder_collection_allowlist.yml1
-rw-r--r--spec/support/gitlab_stubs/gitlab_ci.yml5
-rw-r--r--spec/support/helpers/exclusive_lease_helpers.rb19
-rw-r--r--spec/support/helpers/features/web_ide_spec_helpers.rb24
-rw-r--r--spec/support/helpers/git_helpers.rb4
-rw-r--r--spec/support/helpers/graphql_helpers.rb5
-rw-r--r--spec/support/helpers/html_escaped_helpers.rb31
-rw-r--r--spec/support/helpers/ldap_helpers.rb26
-rw-r--r--spec/support/helpers/login_helpers.rb10
-rw-r--r--spec/support/helpers/migrations_helpers/vulnerabilities_helper.rb40
-rw-r--r--spec/support/helpers/project_helpers.rb16
-rw-r--r--spec/support/helpers/seed_helper.rb67
-rw-r--r--spec/support/helpers/stub_configuration.rb2
-rw-r--r--spec/support/helpers/stub_experiments.rb37
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb6
-rw-r--r--spec/support/helpers/stub_object_storage.rb17
-rw-r--r--spec/support/helpers/test_env.rb24
-rw-r--r--spec/support/helpers/usage_data_helpers.rb18
-rw-r--r--spec/support/helpers/user_helpers.rb33
-rw-r--r--spec/support/matchers/event_store.rb61
-rw-r--r--spec/support/migration.rb4
-rw-r--r--spec/support/models/partitionable_check.rb46
-rw-r--r--spec/support/rspec_order_todo.yml10
-rw-r--r--spec/support/services/issuable_update_service_shared_examples.rb6
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb13
-rw-r--r--spec/support/shared_contexts/html_safe_shared_context.rb23
-rw-r--r--spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb5
-rw-r--r--spec/support/shared_contexts/markdown_golden_master_shared_examples.rb2
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/rubocop_default_rspec_language_config_context.rb32
-rw-r--r--spec/support/shared_contexts/services/packages/rpm/xml_shared_context.rb7
-rw-r--r--spec/support/shared_contexts/views/html_safe_render_shared_context.rb39
-rw-r--r--spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb48
-rw-r--r--spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb12
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/features/access_tokens_shared_examples.rb51
-rw-r--r--spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb125
-rw-r--r--spec/support/shared_examples/features/deploy_token_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb4
-rw-r--r--spec/support/shared_examples/features/project_upload_files_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/features/runners_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb33
-rw-r--r--spec/support/shared_examples/graphql/n_plus_one_query_examples.rb16
-rw-r--r--spec/support/shared_examples/lib/cache_helpers_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/lib/gitlab/memory/watchdog/monitor_result_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/lib/gitlab/regex_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/models/boards/listable_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb355
-rw-r--r--spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb104
-rw-r--r--spec/support/shared_examples/models/concerns/has_wiki_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/concerns/participable_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/models/concerns/timebox_shared_examples.rb118
-rw-r--r--spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb298
-rw-r--r--spec/support/shared_examples/policies/wiki_policies_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb62
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb85
-rw-r--r--spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/requests/api/hooks_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_token_examples.rb1
-rw-r--r--spec/support/shared_examples/serializers/issuable_current_user_properties_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/services/base_rpm_service_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/services/merge_request_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/services/reviewers_change_trigger_shared_examples.rb17
-rw-r--r--spec/support/view_component.rb7
86 files changed, 1748 insertions, 814 deletions
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index a5d845f5177..57065400220 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -7,7 +7,7 @@ require 'capybara-screenshot/rspec'
require 'selenium-webdriver'
# Give CI some extra time
-timeout = ENV['CI'] || ENV['CI_SERVER'] ? 60 : 30
+timeout = ENV['CI'] || ENV['CI_SERVER'] ? 30 : 10
# Support running Capybara on a specific port to allow saving commonly used pages
Capybara.server_port = ENV['CAPYBARA_PORT'] if ENV['CAPYBARA_PORT']
diff --git a/spec/support/capybara_slow_finder.rb b/spec/support/capybara_slow_finder.rb
new file mode 100644
index 00000000000..975ddd52c1f
--- /dev/null
+++ b/spec/support/capybara_slow_finder.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Capybara
+ MESSAGE = <<~MSG
+ Timeout (%{timeout}s) reached while running a waiting Capybara finder.
+ Consider using a non-waiting finder.
+
+ See https://www.cloudbees.com/blog/faster-rails-tests
+ MSG
+
+ module Node
+ class Base
+ # Inspired by https://github.com/ngauthier/capybara-slow_finder_errors
+ module SlowFinder
+ def synchronize(seconds = nil, errors: nil)
+ start_time = Gitlab::Metrics::System.monotonic_time
+
+ super
+ rescue Capybara::ElementNotFound => e
+ seconds ||= Capybara.default_max_wait_time
+
+ raise e unless seconds > 0 && Gitlab::Metrics::System.monotonic_time - start_time > seconds
+
+ message = format(MESSAGE, timeout: seconds)
+ raise e, "#{$!}\n\n#{message}", e.backtrace
+ end
+ end
+
+ prepend SlowFinder
+ end
+ end
+end
diff --git a/spec/support/cross_database_modification.rb b/spec/support/cross_database_modification.rb
deleted file mode 100644
index e0d91001c03..00000000000
--- a/spec/support/cross_database_modification.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.configure do |config|
- config.after do |example|
- [::ApplicationRecord, ::Ci::ApplicationRecord].each do |base_class|
- base_class.gitlab_transactions_stack.clear if base_class.respond_to?(:gitlab_transactions_stack)
- end
- end
-end
diff --git a/spec/support/database/multiple_databases.rb b/spec/support/database/multiple_databases.rb
index 05f26e57e9c..25c3b6e74ce 100644
--- a/spec/support/database/multiple_databases.rb
+++ b/spec/support/database/multiple_databases.rb
@@ -2,6 +2,15 @@
module Database
module MultipleDatabases
+ def run_and_cleanup(example)
+ # Each example may call `migrate!`, so we must ensure we are migrated down every time
+ schema_migrate_down!
+
+ example.run
+
+ delete_from_all_tables!(except: deletion_except_tables)
+ end
+
def skip_if_multiple_databases_not_setup
skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
end
@@ -22,6 +31,21 @@ module Database
model.establish_connection(new_db_config)
end
+ def ensure_schema_and_empty_tables
+ # Ensure all schemas for both databases are migrated back
+ Gitlab::Database.database_base_models.each do |_, base_model|
+ with_reestablished_active_record_base do
+ reconfigure_db_connection(
+ model: ActiveRecord::Base,
+ config_model: base_model
+ )
+
+ schema_migrate_up!
+ delete_from_all_tables!(except: deletion_except_tables)
+ end
+ end
+ end
+
# The usage of this method switches temporarily used `connection_handler`
# allowing full manipulation of ActiveRecord::Base connections without
# having side effects like:
@@ -87,6 +111,16 @@ module Database
end
RSpec.configure do |config|
+ # Ensure database versions are memoized to prevent query counts from
+ # being affected by version checks. Note that
+ # Gitlab::Database.check_postgres_version_and_print_warning is called
+ # at startup, but that generates its own
+ # `Gitlab::Database::Reflection` so the result is not memoized by
+ # callers of `ApplicationRecord.database.version`, such as
+ # `Gitlab::Database::AsWithMaterialized.materialized_supported?`.
+ # TODO This can be removed once https://gitlab.com/gitlab-org/gitlab/-/issues/325639 is completed.
+ [ApplicationRecord, ::Ci::ApplicationRecord].each { |record| record.database.version }
+
config.around(:each, :reestablished_active_record_base) do |example|
with_reestablished_active_record_base(reconnect: example.metadata.fetch(:reconnect, true)) do
example.run
@@ -99,7 +133,15 @@ RSpec.configure do |config|
end
end
+ config.append_after(:context, :migration) do
+ break if recreate_databases_and_seed_if_needed
+
+ ensure_schema_and_empty_tables
+ end
+
config.around(:each, :migration) do |example|
+ self.class.use_transactional_tests = false
+
migration_schema = example.metadata[:migration]
migration_schema = :gitlab_main if migration_schema == true
base_model = Gitlab::Database.schemas_to_base_models.fetch(migration_schema).first
@@ -112,11 +154,13 @@ RSpec.configure do |config|
config_model: base_model
)
- example.run
+ run_and_cleanup(example)
end
else
- example.run
+ run_and_cleanup(example)
end
+
+ self.class.use_transactional_tests = true
end
end
diff --git a/spec/support/database/prevent_cross_database_modification.rb b/spec/support/database/prevent_cross_database_modification.rb
index c509aecf9b8..759e8316cc5 100644
--- a/spec/support/database/prevent_cross_database_modification.rb
+++ b/spec/support/database/prevent_cross_database_modification.rb
@@ -14,18 +14,22 @@ RSpec.configure do |config|
# By default allow cross-modifications as we want to observe only transactions
# within a specific block of execution which is defined be `before(:each)` and `after(:each)`
config.before(:all) do
- ::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.suppress = true
+ ::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.suppress_in_rspec = true
end
# Using before and after blocks because the around block causes problems with the let_it_be
# record creations. It makes an extra savepoint which breaks the transaction count logic.
config.before do |example_file|
- ::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.suppress =
+ ::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.suppress_in_rspec =
CROSS_DB_MODIFICATION_ALLOW_LIST.include?(example_file.file_path_rerun_argument)
end
# Reset after execution to preferred state
config.after do |example_file|
- ::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.suppress = true
+ ::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.suppress_in_rspec = true
+
+ [::ApplicationRecord, ::Ci::ApplicationRecord].each do |base_class|
+ base_class.gitlab_transactions_stack.clear if base_class.respond_to?(:gitlab_transactions_stack)
+ end
end
end
diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb
index f8ddf3e66a5..7bd1f0c5dfa 100644
--- a/spec/support/database_cleaner.rb
+++ b/spec/support/database_cleaner.rb
@@ -13,19 +13,6 @@ RSpec.configure do |config|
DatabaseCleaner.clean_with(:deletion)
end
- config.append_after(:context, :migration) do
- delete_from_all_tables!(except: ['work_item_types'])
-
- # Postgres maximum number of columns in a table is 1600 (https://github.com/postgres/postgres/blob/de41869b64d57160f58852eab20a27f248188135/src/include/access/htup_details.h#L23-L47).
- # We drop and recreate the database if any table has more than 1200 columns, just to be safe.
- if any_connection_class_with_more_than_allowed_columns?
- recreate_all_databases!
-
- # Seed required data as recreating DBs will delete it
- TestEnv.seed_db
- end
- end
-
config.around(:each, :delete) do |example|
self.class.use_transactional_tests = false
@@ -35,14 +22,4 @@ RSpec.configure do |config|
self.class.use_transactional_tests = true
end
-
- config.around(:each, :migration) do |example|
- self.class.use_transactional_tests = false
-
- example.run
-
- delete_from_all_tables!(except: ['work_item_types'])
-
- self.class.use_transactional_tests = true
- end
end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index e3a05f17593..24cdbe04fc2 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -78,22 +78,32 @@ module DbCleaner
puts "Databases re-creation done in #{Gitlab::Metrics::System.monotonic_time - start}"
end
+ def recreate_databases_and_seed_if_needed
+ # Postgres maximum number of columns in a table is 1600 (https://github.com/postgres/postgres/blob/de41869b64d57160f58852eab20a27f248188135/src/include/access/htup_details.h#L23-L47).
+ # We drop and recreate the database if any table has more than 1200 columns, just to be safe.
+ return false unless any_connection_class_with_more_than_allowed_columns?
+
+ recreate_all_databases!
+
+ # Seed required data as recreating DBs will delete it
+ TestEnv.seed_db
+
+ true
+ end
+
def force_disconnect_all_connections!
- all_connection_classes.each do |connection_class|
- # We use `connection_pool` to avoid going through
- # Load Balancer since it does retry ops
- pool = connection_class.connection_pool
-
- # Force disconnect https://www.cybertec-postgresql.com/en/terminating-database-connections-in-postgresql/
- pool.connection.execute(<<-SQL)
- SELECT pg_terminate_backend(pid)
- FROM pg_stat_activity
- WHERE datname = #{pool.connection.quote(pool.db_config.database)}
- AND pid != pg_backend_pid();
- SQL
-
- connection_class.connection_pool.disconnect!
+ cmd = <<~SQL
+ SELECT pg_terminate_backend(pg_stat_activity.pid)
+ FROM pg_stat_activity
+ WHERE datname = current_database()
+ AND pid <> pg_backend_pid();
+ SQL
+
+ Gitlab::Database::EachDatabase.each_database_connection(include_shared: false) do |connection|
+ connection.execute(cmd)
end
+
+ ActiveRecord::Base.clear_all_connections! # rubocop:disable Database/MultipleDatabases
end
end
diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml
index 1ac8e49fb45..c8af07905c2 100644
--- a/spec/support/finder_collection_allowlist.yml
+++ b/spec/support/finder_collection_allowlist.yml
@@ -55,6 +55,7 @@
- Security::FindingsFinder
- Security::PipelineVulnerabilitiesFinder
- Security::ScanExecutionPoliciesFinder
+- Security::ScanResultPoliciesFinder
- Security::TrainingProviders::BaseUrlFinder
- Security::TrainingUrlsFinder
- Security::TrainingProviders::KontraUrlFinder
diff --git a/spec/support/gitlab_stubs/gitlab_ci.yml b/spec/support/gitlab_stubs/gitlab_ci.yml
index b6a66cfa2c6..94523591765 100644
--- a/spec/support/gitlab_stubs/gitlab_ci.yml
+++ b/spec/support/gitlab_stubs/gitlab_ci.yml
@@ -7,9 +7,12 @@ before_script:
- bundle exec rake db:create
variables:
+ KEY_VALUE_VAR:
+ value: 'value x'
+ description: 'value of KEY_VALUE_VAR'
DB_NAME: postgres
ENVIRONMENT_VAR:
- value: 'env var value'
+ value: ['env var value', 'env var value2']
description: 'env var description'
stages:
diff --git a/spec/support/helpers/exclusive_lease_helpers.rb b/spec/support/helpers/exclusive_lease_helpers.rb
index 95cfc56c273..06e5ae5427c 100644
--- a/spec/support/helpers/exclusive_lease_helpers.rb
+++ b/spec/support/helpers/exclusive_lease_helpers.rb
@@ -2,6 +2,8 @@
module ExclusiveLeaseHelpers
def stub_exclusive_lease(key = nil, uuid = 'uuid', renew: false, timeout: nil)
+ prepare_exclusive_lease_stub
+
key ||= instance_of(String)
timeout ||= instance_of(Integer)
@@ -37,4 +39,21 @@ module ExclusiveLeaseHelpers
.to receive(:cancel)
.with(key, uuid)
end
+
+ private
+
+ # This prepares the stub to be able to stub specific lease keys
+ # while allowing unstubbed lease keys to behave as original.
+ #
+ # allow(Gitlab::ExclusiveLease).to receive(:new).and_call_original
+ # can only be called once to prevent resetting stubs when
+ # `stub_exclusive_lease` is called multiple times.
+ def prepare_exclusive_lease_stub
+ return if @exclusive_lease_allowed_to_call_original
+
+ allow(Gitlab::ExclusiveLease)
+ .to receive(:new).and_call_original
+
+ @exclusive_lease_allowed_to_call_original = true
+ end
end
diff --git a/spec/support/helpers/features/web_ide_spec_helpers.rb b/spec/support/helpers/features/web_ide_spec_helpers.rb
index 70dedc3ac50..551749a43de 100644
--- a/spec/support/helpers/features/web_ide_spec_helpers.rb
+++ b/spec/support/helpers/features/web_ide_spec_helpers.rb
@@ -8,7 +8,6 @@
# ...
#
# ide_visit(project)
-# ide_create_new_file('path/to/file.txt', content: 'Lorem ipsum')
# ide_commit
#
module WebIdeSpecHelpers
@@ -40,29 +39,6 @@ module WebIdeSpecHelpers
row.matches_css?('.folder.is-open')
end
- # Creates a file in the IDE by expanding directories
- # then using the dropdown next to the parent directory
- #
- # - Throws an error if the parent directory is not found
- def ide_create_new_file(path, content: '')
- parent_path = path.split('/')[0...-1].join('/')
-
- container = ide_traverse_to_file(parent_path)
-
- if container
- click_file_action(container, 'New file')
- else
- ide_tree_actions.click_button('New file')
- end
-
- within '#ide-new-entry' do
- find('input').fill_in(with: path)
- click_button('Create file')
- end
-
- ide_set_editor_value(content)
- end
-
# Deletes a file by traversing to `path`
# then clicking the 'Delete' action.
#
diff --git a/spec/support/helpers/git_helpers.rb b/spec/support/helpers/git_helpers.rb
index 99c5871ba54..72bba419116 100644
--- a/spec/support/helpers/git_helpers.rb
+++ b/spec/support/helpers/git_helpers.rb
@@ -2,7 +2,9 @@
module GitHelpers
def rugged_repo(repository)
- path = File.join(TestEnv.repos_path, repository.disk_path + '.git')
+ path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ File.join(TestEnv.repos_path, repository.disk_path + '.git')
+ end
Rugged::Repository.new(path)
end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 9d745f2cb70..b2fc6ae3286 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -17,6 +17,9 @@ module GraphqlHelpers
# makes an underscored string look like a fieldname
# "merge_request" => "mergeRequest"
def self.fieldnamerize(underscored_field_name)
+ # Skip transformation for a field with leading underscore
+ return underscored_field_name.to_s if underscored_field_name.start_with?('_')
+
underscored_field_name.to_s.camelize(:lower)
end
@@ -717,7 +720,7 @@ module GraphqlHelpers
end
def allow_high_graphql_transaction_threshold
- stub_const("Gitlab::QueryLimiting::Transaction::THRESHOLD", 1000)
+ allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(1000)
end
def allow_high_graphql_query_size
diff --git a/spec/support/helpers/html_escaped_helpers.rb b/spec/support/helpers/html_escaped_helpers.rb
index 7f6825e9598..7cbea7e7428 100644
--- a/spec/support/helpers/html_escaped_helpers.rb
+++ b/spec/support/helpers/html_escaped_helpers.rb
@@ -21,4 +21,35 @@ module HtmlEscapedHelpers
match_data
end
+
+ # Checks if +content+ contains HTML escaped tags and raises an exception
+ # if it does.
+ #
+ # See #match_html_escaped_tags for details.
+ def ensure_no_html_escaped_tags!(content, example)
+ match_data = match_html_escaped_tags(content)
+ return unless match_data
+
+ # Truncate
+ pre_match = match_data.pre_match.last(50)
+ match = match_data[0]
+ post_match = match_data.post_match.first(50)
+
+ string = "#{pre_match}«#{match}»#{post_match}"
+
+ raise <<~MESSAGE
+ The following string contains HTML escaped tags:
+
+ #{string}
+
+ Please consider using `.html_safe`.
+
+ This check can be disabled via:
+
+ it #{example.description.inspect}, :skip_html_escaped_tags_check do
+ ...
+ end
+
+ MESSAGE
+ end
end
diff --git a/spec/support/helpers/ldap_helpers.rb b/spec/support/helpers/ldap_helpers.rb
index 2f5f8be518c..48b593fb3d1 100644
--- a/spec/support/helpers/ldap_helpers.rb
+++ b/spec/support/helpers/ldap_helpers.rb
@@ -69,6 +69,32 @@ module LdapHelpers
allow_any_instance_of(Gitlab::Auth::Ldap::Adapter)
.to receive(:ldap_search).and_raise(Gitlab::Auth::Ldap::LdapConnectionError)
end
+
+ def stub_ldap_access(user, provider, provider_label)
+ ldap_server_config =
+ {
+ 'label' => provider_label,
+ 'provider_name' => provider,
+ 'attributes' => {},
+ 'encryption' => 'plain',
+ 'uid' => 'uid',
+ 'base' => 'dc=example,dc=com'
+ }
+ uid = 'my-uid'
+ allow(::Gitlab::Auth::Ldap::Config).to receive_messages(enabled: true, servers: [ldap_server_config])
+ allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [provider.to_sym])
+
+ Ldap::OmniauthCallbacksController.define_providers!
+ Rails.application.reload_routes!
+
+ mock_auth_hash(provider, uid, user.email)
+ allow(Gitlab::Auth::Ldap::Access).to receive(:allowed?).with(user).and_return(true)
+
+ allow_next_instance_of(ActionDispatch::Routing::RoutesProxy) do |instance|
+ allow(instance).to receive(:"user_#{provider}_omniauth_callback_path")
+ .and_return("/users/auth/#{provider}/callback")
+ end
+ end
end
LdapHelpers.include_mod_with('LdapHelpers')
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 87a1f5459ec..44237b821c3 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -119,6 +119,16 @@ module LoginHelpers
click_button "oauth-login-#{provider}"
end
+ def sign_in_using_ldap!(user, ldap_tab, ldap_name)
+ visit new_user_session_path
+ click_link ldap_tab
+ fill_in 'username', with: user.username
+ fill_in 'password', with: user.password
+ within("##{ldap_name}") do
+ click_button 'Sign in'
+ end
+ end
+
def register_via(provider, uid, email, additional_info: {})
mock_auth_hash(provider, uid, email, additional_info: additional_info)
visit new_user_registration_path
diff --git a/spec/support/helpers/migrations_helpers/vulnerabilities_helper.rb b/spec/support/helpers/migrations_helpers/vulnerabilities_helper.rb
new file mode 100644
index 00000000000..0a86d7abc83
--- /dev/null
+++ b/spec/support/helpers/migrations_helpers/vulnerabilities_helper.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module MigrationHelpers
+ module VulnerabilitiesHelper
+ # rubocop:disable Metrics/ParameterLists
+ def create_finding!(
+ vulnerability_id:, project_id:, scanner_id:, primary_identifier_id:,
+ name: "test", severity: 7, confidence: 7, report_type: 0,
+ project_fingerprint: '123qweasdzxc', location_fingerprint: 'test',
+ metadata_version: 'test', raw_metadata: 'test', uuid: 'b1cee17e-3d7a-11ed-b878-0242ac120002')
+ table(:vulnerability_occurrences).create!(
+ vulnerability_id: vulnerability_id,
+ project_id: project_id,
+ name: name,
+ severity: severity,
+ confidence: confidence,
+ report_type: report_type,
+ project_fingerprint: project_fingerprint,
+ scanner_id: scanner_id,
+ primary_identifier_id: primary_identifier_id,
+ location_fingerprint: location_fingerprint,
+ metadata_version: metadata_version,
+ raw_metadata: raw_metadata,
+ uuid: uuid
+ )
+ end
+ # rubocop:enable Metrics/ParameterLists
+
+ def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
+ table(:vulnerabilities).create!(
+ project_id: project_id,
+ author_id: author_id,
+ title: title,
+ severity: severity,
+ confidence: confidence,
+ report_type: report_type
+ )
+ end
+ end
+end
diff --git a/spec/support/helpers/project_helpers.rb b/spec/support/helpers/project_helpers.rb
index 2427ed2bcc9..daa07e385ea 100644
--- a/spec/support/helpers/project_helpers.rb
+++ b/spec/support/helpers/project_helpers.rb
@@ -1,22 +1,6 @@
# frozen_string_literal: true
module ProjectHelpers
- # @params target [Project] membership target
- # @params membership [Symbol] accepts the membership levels :guest, :reporter...
- # and phony levels :non_member and :anonymous
- def create_user_from_membership(target, membership)
- case membership
- when :anonymous
- nil
- when :non_member
- create(:user, name: membership)
- when :admin
- create(:user, :admin, name: 'admin')
- else
- create(:user, name: membership).tap { |u| target.add_member(u, membership) }
- end
- end
-
def update_feature_access_level(project, access_level, additional_params = {})
features = ProjectFeature::FEATURES.dup
features.delete(:pages)
diff --git a/spec/support/helpers/seed_helper.rb b/spec/support/helpers/seed_helper.rb
deleted file mode 100644
index 9628762d46a..00000000000
--- a/spec/support/helpers/seed_helper.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-
-require_relative 'test_env'
-
-# This file is specific to specs in spec/lib/gitlab/git/
-
-SEED_STORAGE_PATH = Gitlab::GitalyClient::StorageSettings.allow_disk_access { TestEnv.repos_path }
-TEST_REPO_PATH = 'gitlab-git-test.git'
-TEST_NORMAL_REPO_PATH = 'not-bare-repo.git'
-TEST_MUTABLE_REPO_PATH = 'mutable-repo.git'
-TEST_BROKEN_REPO_PATH = 'broken-repo.git'
-
-module SeedHelper
- GITLAB_GIT_TEST_REPO_URL = File.expand_path('../gitlab-git-test.git', __dir__)
-
- def ensure_seeds
- if File.exist?(SEED_STORAGE_PATH)
- FileUtils.rm_r(SEED_STORAGE_PATH)
- end
-
- FileUtils.mkdir_p(SEED_STORAGE_PATH)
-
- create_bare_seeds
- create_normal_seeds
- create_mutable_seeds
- create_broken_seeds
- end
-
- def create_bare_seeds
- system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{GITLAB_GIT_TEST_REPO_URL}),
- chdir: SEED_STORAGE_PATH,
- out: '/dev/null',
- err: '/dev/null')
- end
-
- def create_normal_seeds
- system(git_env, *%W(#{Gitlab.config.git.bin_path} clone #{TEST_REPO_PATH} #{TEST_NORMAL_REPO_PATH}),
- chdir: SEED_STORAGE_PATH,
- out: '/dev/null',
- err: '/dev/null')
- end
-
- def create_mutable_seeds
- system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} #{TEST_MUTABLE_REPO_PATH}),
- chdir: SEED_STORAGE_PATH,
- out: '/dev/null',
- err: '/dev/null')
-
- mutable_repo_full_path = File.join(SEED_STORAGE_PATH, TEST_MUTABLE_REPO_PATH)
- system(git_env, *%W(#{Gitlab.config.git.bin_path} branch -t feature origin/feature),
- chdir: mutable_repo_full_path, out: '/dev/null', err: '/dev/null')
-
- system(git_env, *%W(#{Gitlab.config.git.bin_path} remote add expendable #{GITLAB_GIT_TEST_REPO_URL}),
- chdir: mutable_repo_full_path, out: '/dev/null', err: '/dev/null')
- end
-
- def create_broken_seeds
- system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} #{TEST_BROKEN_REPO_PATH}),
- chdir: SEED_STORAGE_PATH,
- out: '/dev/null',
- err: '/dev/null')
-
- refs_path = File.join(SEED_STORAGE_PATH, TEST_BROKEN_REPO_PATH, 'refs')
-
- FileUtils.rm_r(refs_path)
- end
-end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index c08e35912c3..f41457d2420 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -81,7 +81,7 @@ module StubConfiguration
messages['default'] ||= Gitlab.config.repositories.storages.default
messages.each do |storage_name, storage_hash|
if !storage_hash.key?('path') || storage_hash['path'] == Gitlab::GitalyClient::StorageSettings::Deprecated
- storage_hash['path'] = TestEnv.repos_path
+ storage_hash['path'] = Gitlab::GitalyClient::StorageSettings.allow_disk_access { TestEnv.repos_path }
end
messages[storage_name] = Gitlab::GitalyClient::StorageSettings.new(storage_hash.to_h)
diff --git a/spec/support/helpers/stub_experiments.rb b/spec/support/helpers/stub_experiments.rb
deleted file mode 100644
index 8995b8f5f7b..00000000000
--- a/spec/support/helpers/stub_experiments.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-module StubExperiments
- # Stub Experiment with `key: true/false`
- #
- # @param [Hash] experiment where key is feature name and value is boolean whether active or not.
- #
- # Examples
- # - `stub_experiment(signup_flow: false)` ... Disables `signup_flow` experiment.
- def stub_experiment(experiments)
- allow(Gitlab::Experimentation).to receive(:active?).and_call_original
-
- experiments.each do |experiment_key, enabled|
- allow(Gitlab::Experimentation).to receive(:active?).with(experiment_key) { enabled }
- end
- end
-
- # Stub Experiment for user with `key: true/false`
- #
- # @param [Hash] experiment where key is feature name and value is boolean whether enabled or not.
- #
- # Examples
- # - `stub_experiment_for_subject(signup_flow: false)` ... Disable `signup_flow` experiment for user.
- def stub_experiment_for_subject(experiments)
- allow(Gitlab::Experimentation).to receive(:in_experiment_group?).and_call_original
-
- experiments.each do |experiment_key, enabled|
- allow(Gitlab::Experimentation).to receive(:in_experiment_group?).with(experiment_key, anything) { enabled }
- end
- end
-
- private
-
- def feature_flag_suffix
- Gitlab::Experimentation::Experiment::FEATURE_FLAG_SUFFIX
- end
-end
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index 749554f7786..e5c30769531 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -96,9 +96,9 @@ module StubGitlabCalls
def stub_commonmark_sourcepos_disabled
render_options = Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS
- allow_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark)
- .to receive(:render_options)
- .and_return(render_options)
+ allow_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance|
+ allow(instance).to receive(:render_options).and_return(render_options)
+ end
end
private
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index 661c1c683b0..87e2a71b1cd 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -13,13 +13,16 @@ module StubObjectStorage
enabled: true,
proxy_download: false,
background_upload: false,
- direct_upload: false
+ direct_upload: false,
+ cdn: {}
)
+
new_config = config.to_h.deep_symbolize_keys.merge({
enabled: enabled,
proxy_download: proxy_download,
background_upload: background_upload,
- direct_upload: direct_upload
+ direct_upload: direct_upload,
+ cdn: cdn
})
# Needed for ObjectStorage::Config compatibility
@@ -30,6 +33,10 @@ module StubObjectStorage
allow(config).to receive(:background_upload) { background_upload }
allow(config).to receive(:direct_upload) { direct_upload }
+ uploader_config = Settingslogic.new(new_config.deep_stringify_keys)
+ allow(uploader).to receive(:object_store_options).and_return(uploader_config)
+ allow(uploader.options).to receive(:object_store).and_return(uploader_config)
+
return unless enabled
stub_object_storage(connection_params: uploader.object_store_credentials,
@@ -74,6 +81,12 @@ module StubObjectStorage
**params)
end
+ def stub_rpm_repository_file_object_storage(**params)
+ stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
+ uploader: ::Packages::Rpm::RepositoryFileUploader,
+ **params)
+ end
+
def stub_composer_cache_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
uploader: ::Packages::Composer::CacheUploader,
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 691f978550a..c58353558df 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -295,14 +295,6 @@ module TestEnv
end
end
- def rm_storage_dir(storage, dir)
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- target_repo_refs_path = File.join(GitalySetup.repos_path(storage), dir)
- FileUtils.remove_dir(target_repo_refs_path)
- end
- rescue Errno::ENOENT
- end
-
def storage_dir_exists?(storage, dir)
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
File.exist?(File.join(GitalySetup.repos_path(storage), dir))
@@ -348,6 +340,14 @@ module TestEnv
Capybara.current_session.visit '/'
end
+ def factory_repo_path
+ @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name)
+ end
+
+ def forked_repo_path
+ @forked_repo_path ||= Rails.root.join('tmp', 'tests', forked_repo_name)
+ end
+
def factory_repo_bundle_path
"#{factory_repo_path}.bundle"
end
@@ -377,18 +377,10 @@ module TestEnv
]
end
- def factory_repo_path
- @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name)
- end
-
def factory_repo_name
'gitlab-test'
end
- def forked_repo_path
- @forked_repo_path ||= Rails.root.join('tmp', 'tests', forked_repo_name)
- end
-
def forked_repo_name
'gitlab-test-fork'
end
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index 1aea3545ae0..b4f0cbd8527 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -1,21 +1,6 @@
# frozen_string_literal: true
module UsageDataHelpers
- SMAU_KEYS = %i(
- snippet_create
- snippet_update
- snippet_comment
- merge_request_comment
- commit_comment
- wiki_pages_create
- wiki_pages_update
- wiki_pages_delete
- navbar_searches
- cycle_analytics_views
- productivity_analytics_views
- source_code_pushes
- ).freeze
-
COUNTS_KEYS = %i(
assignee_lists
ci_builds
@@ -83,7 +68,6 @@ module UsageDataHelpers
projects_with_error_tracking_enabled
projects_with_enabled_alert_integrations
projects_with_expiration_policy_enabled
- projects_with_expiration_policy_disabled
projects_with_expiration_policy_enabled_with_keep_n_unset
projects_with_expiration_policy_enabled_with_keep_n_set_to_1
projects_with_expiration_policy_enabled_with_keep_n_set_to_5
@@ -118,7 +102,7 @@ module UsageDataHelpers
uploads
web_hooks
user_preferences_user_gitpod_enabled
- ).push(*SMAU_KEYS)
+ ).freeze
USAGE_DATA_KEYS = %i(
active_user_count
diff --git a/spec/support/helpers/user_helpers.rb b/spec/support/helpers/user_helpers.rb
new file mode 100644
index 00000000000..30fa5b3ad8d
--- /dev/null
+++ b/spec/support/helpers/user_helpers.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module UserHelpers
+ def create_user_from_membership(target, membership)
+ generate_user_from_membership(:create, target, membership)
+ end
+
+ def build_user_from_membership(target, membership)
+ generate_user_from_membership(:build, target, membership)
+ end
+
+ private
+
+ # @param method [Symbol] FactoryBot methods :create, :build, :build_stubbed
+ # @param target [Project, Group] membership target
+ # @param membership [Symbol] accepts the membership levels :guest, :reporter...
+ # and pseudo levels :non_member and :anonymous
+ def generate_user_from_membership(method, target, membership)
+ case membership
+ when :anonymous
+ nil
+ when :non_member
+ FactoryBot.send(method, :user, name: membership)
+ when :admin
+ FactoryBot.send(method, :user, :admin, name: 'admin')
+ else
+ # `.tap` can only be used with `create`, and if we want to `build` a user,
+ # it is more performant than creating a `project_member` or `group_member`
+ # with a built user
+ create(:user, name: membership).tap { |u| target.add_member(u, membership) }
+ end
+ end
+end
diff --git a/spec/support/matchers/event_store.rb b/spec/support/matchers/event_store.rb
index 4ecb924b3ed..582ea202187 100644
--- a/spec/support/matchers/event_store.rb
+++ b/spec/support/matchers/event_store.rb
@@ -6,7 +6,7 @@ RSpec::Matchers.define :publish_event do |expected_event_class|
supports_block_expectations
match do |proc|
- raise ArgumentError, 'This matcher only supports block expectation' unless proc.respond_to?(:call)
+ raise ArgumentError, 'publish_event matcher only supports block expectation' unless proc.respond_to?(:call)
@events ||= []
@@ -22,6 +22,8 @@ RSpec::Matchers.define :publish_event do |expected_event_class|
end
def match_data?(actual, expected)
+ return if actual.blank? || expected.blank?
+
values_match?(actual.keys, expected.keys) &&
actual.keys.all? do |key|
values_match?(expected[key], actual[key])
@@ -33,11 +35,20 @@ RSpec::Matchers.define :publish_event do |expected_event_class|
end
failure_message do
- "expected #{expected_event_class} with #{@expected_data} to be published, but got #{@events}"
+ message = "expected #{expected_event_class} with #{@expected_data || 'no data'} to be published"
+
+ if @events.present?
+ <<~MESSAGE
+ #{message}, but only the following events were published:
+ #{events_list}
+ MESSAGE
+ else
+ "#{message}, but no events were published."
+ end
end
match_when_negated do |proc|
- raise ArgumentError, 'This matcher only supports block expectation' unless proc.respond_to?(:call)
+ raise ArgumentError, 'publish_event matcher only supports block expectation' unless proc.respond_to?(:call)
allow(Gitlab::EventStore).to receive(:publish)
@@ -45,4 +56,48 @@ RSpec::Matchers.define :publish_event do |expected_event_class|
expect(Gitlab::EventStore).not_to have_received(:publish).with(instance_of(expected_event_class))
end
+
+ def events_list
+ @events.map do |event|
+ " - #{event.class.name} with #{event.data}"
+ end.join("\n")
+ end
+end
+
+# not_publish_event enables multiple assertions on a single block, for example:
+# expect { Model.create(invalid: :attribute) }
+# .to not_change(Model, :count)
+# .and not_publish_event(ModelCreated)
+RSpec::Matchers.define :not_publish_event do |expected_event_class|
+ include RSpec::Matchers::Composable
+
+ supports_block_expectations
+
+ match do |proc|
+ raise ArgumentError, 'not_publish_event matcher only supports block expectation' unless proc.respond_to?(:call)
+
+ @events ||= []
+
+ allow(Gitlab::EventStore).to receive(:publish) do |published_event|
+ @events << published_event
+ end
+
+ proc.call
+
+ @events.none? do |event|
+ event.instance_of?(expected_event_class)
+ end
+ end
+
+ failure_message do
+ "expected #{expected_event_class} not to be published"
+ end
+
+ chain :with do |_| # rubocop: disable Lint/UnreachableLoop
+ raise ArgumentError, 'not_publish_event does not permit .with to avoid ambiguity'
+ end
+
+ match_when_negated do |proc|
+ raise ArgumentError, 'not_publish_event matcher does not support negation. Use `expect {}.to publish_event` instead'
+ end
end
diff --git a/spec/support/migration.rb b/spec/support/migration.rb
index 3c359af886d..24e2fc2ff31 100644
--- a/spec/support/migration.rb
+++ b/spec/support/migration.rb
@@ -19,13 +19,9 @@ RSpec.configure do |config|
# Each example may call `migrate!`, so we must ensure we are migrated down every time
config.before(:each, :migration) do
use_fake_application_settings
-
- schema_migrate_down!
end
config.after(:context, :migration) do
- schema_migrate_up!
-
Gitlab::CurrentSettings.clear_in_memory_application_settings!
end
end
diff --git a/spec/support/models/partitionable_check.rb b/spec/support/models/partitionable_check.rb
new file mode 100644
index 00000000000..2c09c1b3408
--- /dev/null
+++ b/spec/support/models/partitionable_check.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module PartitioningTesting
+ module CascadeCheck
+ extend ActiveSupport::Concern
+
+ included do
+ after_create :check_partition_cascade_value
+ end
+
+ def check_partition_cascade_value
+ raise 'Partition value not found' unless partition_scope_value
+ raise 'Default value detected' if partition_id == 100
+
+ return if partition_id == partition_scope_value
+
+ raise "partition_id was expected to equal #{partition_scope_value} but it was #{partition_id}."
+ end
+ end
+
+ module DefaultPartitionValue
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def current_partition_value
+ current = super
+
+ if current == 100
+ 54321
+ else
+ current
+ end
+ end
+ end
+ end
+end
+
+Ci::Partitionable::Testing::PARTITIONABLE_MODELS.each do |klass|
+ model = klass.safe_constantize
+
+ if klass == 'Ci::Pipeline'
+ model.prepend(PartitioningTesting::DefaultPartitionValue)
+ else
+ model.include(PartitioningTesting::CascadeCheck)
+ end
+end
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index b5e3d707d50..c4377e368ee 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -189,7 +189,6 @@
- './ee/spec/controllers/subscriptions_controller_spec.rb'
- './ee/spec/controllers/subscriptions/groups_controller_spec.rb'
- './ee/spec/controllers/trial_registrations_controller_spec.rb'
-- './ee/spec/controllers/trials_controller_spec.rb'
- './ee/spec/controllers/users_controller_spec.rb'
- './ee/spec/db/production/license_spec.rb'
- './ee/spec/elastic_integration/global_search_spec.rb'
@@ -533,7 +532,6 @@
- './ee/spec/features/trial_registrations/company_information_spec.rb'
- './ee/spec/features/trial_registrations/signin_spec.rb'
- './ee/spec/features/trial_registrations/signup_spec.rb'
-- './ee/spec/features/trials/capture_lead_spec.rb'
- './ee/spec/features/trials/select_namespace_spec.rb'
- './ee/spec/features/trials/show_trial_banner_spec.rb'
- './ee/spec/features/users/arkose_labs_csp_spec.rb'
@@ -2017,7 +2015,6 @@
- './ee/spec/models/milestone_spec.rb'
- './ee/spec/models/namespace_limit_spec.rb'
- './ee/spec/models/namespace_setting_spec.rb'
-- './ee/spec/models/namespaces/free_user_cap/preview_spec.rb'
- './ee/spec/models/namespaces/free_user_cap_spec.rb'
- './ee/spec/models/namespaces/free_user_cap/standard_spec.rb'
- './ee/spec/models/namespaces/storage/root_excess_size_spec.rb'
@@ -3266,12 +3263,10 @@
- './ee/spec/services/users_ops_dashboard_projects/destroy_service_spec.rb'
- './ee/spec/services/users/update_highest_member_role_service_spec.rb'
- './ee/spec/services/vulnerabilities/confirm_service_spec.rb'
-- './ee/spec/services/vulnerabilities/create_from_security_finding_service_spec.rb'
- './ee/spec/services/vulnerabilities/create_service_spec.rb'
- './ee/spec/services/vulnerabilities/destroy_dismissal_feedback_service_spec.rb'
- './ee/spec/services/vulnerabilities/dismiss_service_spec.rb'
- './ee/spec/services/vulnerabilities/finding_dismiss_service_spec.rb'
-- './ee/spec/services/vulnerabilities/findings/create_from_security_finding_service_spec.rb'
- './ee/spec/services/vulnerabilities/historical_statistics/adjustment_service_spec.rb'
- './ee/spec/services/vulnerabilities/historical_statistics/deletion_service_spec.rb'
- './ee/spec/services/vulnerabilities/manually_create_service_spec.rb'
@@ -8850,7 +8845,6 @@
- './spec/presenters/award_emoji_presenter_spec.rb'
- './spec/presenters/blob_presenter_spec.rb'
- './spec/presenters/blobs/notebook_presenter_spec.rb'
-- './spec/presenters/blobs/unfold_presenter_spec.rb'
- './spec/presenters/ci/bridge_presenter_spec.rb'
- './spec/presenters/ci/build_presenter_spec.rb'
- './spec/presenters/ci/build_runner_presenter_spec.rb'
@@ -9535,7 +9529,6 @@
- './spec/serializers/deploy_keys/basic_deploy_key_entity_spec.rb'
- './spec/serializers/deploy_keys/deploy_key_entity_spec.rb'
- './spec/serializers/deployment_cluster_entity_spec.rb'
-- './spec/serializers/deployment_entity_spec.rb'
- './spec/serializers/deployment_serializer_spec.rb'
- './spec/serializers/detailed_status_entity_spec.rb'
- './spec/serializers/diff_file_base_entity_spec.rb'
@@ -9552,7 +9545,6 @@
- './spec/serializers/entity_request_spec.rb'
- './spec/serializers/environment_entity_spec.rb'
- './spec/serializers/environment_serializer_spec.rb'
-- './spec/serializers/environment_status_entity_spec.rb'
- './spec/serializers/evidences/evidence_entity_spec.rb'
- './spec/serializers/evidences/evidence_serializer_spec.rb'
- './spec/serializers/evidences/issue_entity_spec.rb'
@@ -9634,7 +9626,6 @@
- './spec/serializers/personal_access_token_entity_spec.rb'
- './spec/serializers/personal_access_token_serializer_spec.rb'
- './spec/serializers/pipeline_details_entity_spec.rb'
-- './spec/serializers/pipeline_serializer_spec.rb'
- './spec/serializers/project_access_token_entity_spec.rb'
- './spec/serializers/project_access_token_serializer_spec.rb'
- './spec/serializers/project_import_entity_spec.rb'
@@ -10017,7 +10008,6 @@
- './spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb'
- './spec/services/incident_management/issuable_escalation_statuses/build_service_spec.rb'
- './spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb'
-- './spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb'
- './spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb'
- './spec/services/incident_management/pager_duty/process_webhook_service_spec.rb'
- './spec/services/incident_management/timeline_events/create_service_spec.rb'
diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb
index c168df7a7d2..94061b140f4 100644
--- a/spec/support/services/issuable_update_service_shared_examples.rb
+++ b/spec/support/services/issuable_update_service_shared_examples.rb
@@ -47,7 +47,7 @@ RSpec.shared_examples 'broadcasting issuable labels updates' do
it 'triggers the GraphQL subscription' do
expect(GraphqlTriggers).to receive(:issuable_labels_updated).with(issuable)
- update_issuable({ add_label_ids: [label_b.id] })
+ update_issuable(add_label_ids: [label_b.id])
end
end
@@ -55,7 +55,7 @@ RSpec.shared_examples 'broadcasting issuable labels updates' do
it 'triggers the GraphQL subscription' do
expect(GraphqlTriggers).to receive(:issuable_labels_updated).with(issuable)
- update_issuable({ remove_label_ids: [label_a.id] })
+ update_issuable(remove_label_ids: [label_a.id])
end
end
@@ -63,7 +63,7 @@ RSpec.shared_examples 'broadcasting issuable labels updates' do
it 'does not trigger the GraphQL subscription' do
expect(GraphqlTriggers).not_to receive(:issuable_labels_updated).with(issuable)
- update_issuable({ label_ids: [label_a.id] })
+ update_issuable(label_ids: [label_a.id])
end
end
end
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
index 91b6baac610..8a64efe9df5 100644
--- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -50,8 +50,8 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
end
- let!(:label) { create(:label, project: project1) }
- let!(:label2) { create(:label, project: project1) }
+ let_it_be(:label) { create(:label, project: project1) }
+ let_it_be(:label2) { create(:label, project: project1) }
let!(:merge_request1) do
create(:merge_request, assignees: [user], author: user, reviewers: [user2],
@@ -87,13 +87,16 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
let!(:label_link) { create(:label_link, label: label, target: merge_request2) }
let!(:label_link2) { create(:label_link, label: label2, target: merge_request3) }
- before do
+ before_all do
project1.add_maintainer(user)
- project2.add_developer(user)
- project3.add_developer(user)
project4.add_developer(user)
project5.add_developer(user)
project6.add_developer(user)
+ end
+
+ before do
+ project2.add_developer(user)
+ project3.add_developer(user)
project2.add_developer(user2)
end
diff --git a/spec/support/shared_contexts/html_safe_shared_context.rb b/spec/support/shared_contexts/html_safe_shared_context.rb
new file mode 100644
index 00000000000..9bdaea9fe64
--- /dev/null
+++ b/spec/support/shared_contexts/html_safe_shared_context.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'when rendered has no HTML escapes' do
+ # Check once per example if `rendered` contains HTML escapes.
+ let(:rendered) do |example|
+ super().tap do |rendered|
+ next if example.metadata[:skip_html_escaped_tags_check]
+
+ HtmlEscapedHelpers.ensure_no_html_escaped_tags!(rendered, example)
+ end
+ end
+end
+
+RSpec.shared_context 'when page has no HTML escapes' do
+ # Check once per example if `page` contains HTML escapes.
+ let(:page) do |example|
+ super().tap do |page|
+ next if example.metadata[:skip_html_escaped_tags_check]
+
+ HtmlEscapedHelpers.ensure_no_html_escaped_tags!(page.native.to_s, example)
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb b/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb
index 7c8b6250d24..1963248142c 100644
--- a/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb
+++ b/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb
@@ -1,6 +1,11 @@
# frozen_string_literal: true
RSpec.shared_context 'dependency proxy helpers context' do
+ def allow_fetch_cascade_application_setting(attribute:, return_value:)
+ allow(Gitlab::CurrentSettings).to receive(:public_send).with(attribute.to_sym).and_return(return_value)
+ allow(Gitlab::CurrentSettings).to receive(:public_send).with("lock_#{attribute}").and_return(false)
+ end
+
def allow_fetch_application_setting(attribute:, return_value:)
attributes = double
allow(::Gitlab::CurrentSettings.current_application_settings).to receive(:attributes).and_return(attributes)
diff --git a/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb b/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb
index 168aef0f174..72e23e6d5fa 100644
--- a/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb
+++ b/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb
@@ -13,6 +13,8 @@ RSpec.shared_context 'API::Markdown Golden Master shared context' do |markdown_y
let_it_be(:project) { create(:project, :public, :repository, group: group) }
let_it_be(:label) { create(:label, project: project, title: 'bug') }
+ let_it_be(:label2) { create(:label, project: project, title: 'UX bug') }
+
let_it_be(:milestone) { create(:milestone, project: project, title: '1.1') }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index 893d3702407..bb1b794c2b6 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -74,6 +74,8 @@ RSpec.shared_context 'GroupPolicy context' do
read_group_runners
admin_group_runners
register_group_runners
+ read_billing
+ edit_billing
]
end
diff --git a/spec/support/shared_contexts/rubocop_default_rspec_language_config_context.rb b/spec/support/shared_contexts/rubocop_default_rspec_language_config_context.rb
new file mode 100644
index 00000000000..a207c6ae9d1
--- /dev/null
+++ b/spec/support/shared_contexts/rubocop_default_rspec_language_config_context.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+# From https://github.com/rubocop/rubocop-rspec/blob/master/spec/shared/default_rspec_language_config_context.rb
+# This can be removed once we have https://github.com/rubocop/rubocop-rspec/pull/1377
+
+RSpec.shared_context 'with default RSpec/Language config' do
+ include_context 'config'
+
+ # Deep duplication is needed to prevent config leakage between examples
+ let(:other_cops) do
+ default_language = RuboCop::ConfigLoader
+ .default_configuration['RSpec']['Language']
+ default_include = RuboCop::ConfigLoader
+ .default_configuration['RSpec']['Include']
+ { 'RSpec' =>
+ {
+ 'Include' => default_include,
+ 'Language' => deep_dup(default_language)
+ } }
+ end
+
+ def deep_dup(object)
+ case object
+ when Array
+ object.map { |item| deep_dup(item) }
+ when Hash
+ object.transform_values { |value| deep_dup(value) }
+ else
+ object # only collections undergo modifications and need duping
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/services/packages/rpm/xml_shared_context.rb b/spec/support/shared_contexts/services/packages/rpm/xml_shared_context.rb
new file mode 100644
index 00000000000..784092d40da
--- /dev/null
+++ b/spec/support/shared_contexts/services/packages/rpm/xml_shared_context.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'with rpm package data' do
+ def xml_update_params
+ Gitlab::Json.parse(fixture_file('packages/rpm/payload.json')).with_indifferent_access
+ end
+end
diff --git a/spec/support/shared_contexts/views/html_safe_render_shared_context.rb b/spec/support/shared_contexts/views/html_safe_render_shared_context.rb
deleted file mode 100644
index 3acca60c901..00000000000
--- a/spec/support/shared_contexts/views/html_safe_render_shared_context.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_context 'when rendered view has no HTML escapes', type: :view do
- # Check once per example if `rendered` contains HTML escapes.
- let(:rendered) do |example|
- super().tap do |rendered|
- next if example.metadata[:skip_html_escaped_tags_check]
-
- ensure_no_html_escaped_tags!(rendered, example)
- end
- end
-
- def ensure_no_html_escaped_tags!(content, example)
- match_data = HtmlEscapedHelpers.match_html_escaped_tags(content)
- return unless match_data
-
- # Truncate
- pre_match = match_data.pre_match.last(50)
- match = match_data[0]
- post_match = match_data.post_match.first(50)
-
- string = "#{pre_match}«#{match}»#{post_match}"
-
- raise <<~MESSAGE
- The following string contains HTML escaped tags:
-
- #{string}
-
- Please consider using `.html_safe`.
-
- This check can be disabled via:
-
- it #{example.description.inspect}, :skip_html_escaped_tags_check do
- ...
- end
-
- MESSAGE
- end
-end
diff --git a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
index 7e7460cd602..cd4432af4ed 100644
--- a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
+++ b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
@@ -57,5 +57,53 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
expect(tracker.entity.failures.first.exception_message).to eq('Only allowed schemes are http, https')
end
end
+
+ context 'when wiki is disabled' do
+ before do
+ allow_next_instance_of(BulkImports::Clients::HTTP) do |client|
+ allow(client)
+ .to receive(:get)
+ .and_raise(
+ BulkImports::NetworkError.new(
+ 'Unsuccessful response 403 from ...',
+ response: response_double
+ )
+ )
+ end
+ end
+
+ describe 'unsuccessful response' do
+ shared_examples 'does not raise an error' do
+ it 'does not raise an error' do
+ expect(parent.wiki).not_to receive(:ensure_repository)
+ expect(parent.wiki.repository).not_to receive(:ensure_repository)
+
+ expect { subject.run }.not_to raise_error
+ end
+ end
+
+ context 'when response is forbidden' do
+ let(:response_double) { instance_double(HTTParty::Response, forbidden?: true, code: 403) }
+
+ include_examples 'does not raise an error'
+ end
+
+ context 'when response is not found' do
+ let(:response_double) { instance_double(HTTParty::Response, forbidden?: false, not_found?: true) }
+
+ include_examples 'does not raise an error'
+ end
+
+ context 'when response is not 403' do
+ let(:response_double) { instance_double(HTTParty::Response, forbidden?: false, not_found?: false, code: 301) }
+
+ it 'marks tracker as failed' do
+ subject.run
+
+ expect(tracker.failed?).to eq(true)
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
index 4af3c0cc6cc..6749ebd471f 100644
--- a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
+++ b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
@@ -32,7 +32,8 @@ RSpec.shared_examples 'Snowplow event tracking' do |overrides: {}|
user: try(:user),
project: try(:project),
label: try(:label),
- property: try(:property)
+ property: try(:property),
+ context: try(:context)
}.merge(overrides).compact.merge(extra)
subject
@@ -40,3 +41,12 @@ RSpec.shared_examples 'Snowplow event tracking' do |overrides: {}|
expect_snowplow_event(**params)
end
end
+
+RSpec.shared_examples 'Snowplow event tracking with RedisHLL context' do |overrides: {}|
+ it_behaves_like 'Snowplow event tracking', overrides: overrides do
+ let(:context) do
+ event = try(:property) || action
+ [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event).to_context.to_json]
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
index 885c0229038..5d77ed5fdfc 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -48,7 +48,7 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when the wiki repository cannot be created' do
before do
expect(Wiki).to receive(:for_container).and_return(wiki)
- expect(wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError }
+ expect(wiki).to receive(:create_wiki_repository) { raise Wiki::CouldNotCreateWikiError }
end
it 'redirects to the wiki container and displays an error message' do
@@ -200,7 +200,7 @@ RSpec.shared_examples 'wiki controller actions' do
context 'the sidebar fails to load' do
before do
allow(Wiki).to receive(:for_container).and_return(wiki)
- wiki.wiki
+ wiki.create_wiki_repository
expect(wiki).to receive(:find_sidebar) do
raise ::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded'
end
@@ -288,7 +288,7 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when page is a file' do
include WikiHelpers
- where(:file_name) { ['dk.png', 'unsanitized.svg', 'git-cheat-sheet.pdf'] }
+ where(:file_name) { ['dk.png', 'unsanitized.svg', 'sample.pdf'] }
with_them do
let(:id) { upload_file_to_wiki(wiki, user, file_name) }
@@ -300,7 +300,7 @@ RSpec.shared_examples 'wiki controller actions' do
expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true')
expect(response.cache_control[:public]).to be(false)
- expect(response.headers['Cache-Control']).to eq('max-age=60, private')
+ expect(response.headers['Cache-Control']).to eq('max-age=60, private, must-revalidate, stale-while-revalidate=60, stale-if-error=300, s-maxage=60')
end
end
end
diff --git a/spec/support/shared_examples/features/access_tokens_shared_examples.rb b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
index 0fc45b154d8..cd255abd7a8 100644
--- a/spec/support/shared_examples/features/access_tokens_shared_examples.rb
+++ b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
@@ -10,11 +10,11 @@ end
RSpec.shared_examples 'resource access tokens creation' do |resource_type|
def active_resource_access_tokens
- find('.table.active-tokens')
+ find("[data-testid='active-tokens']")
end
def created_resource_access_token
- find('#created-personal-access-token').value
+ find_field('new-access-token').value
end
it 'allows creation of an access token', :aggregate_failures do
@@ -106,7 +106,7 @@ end
RSpec.shared_examples 'active resource access tokens' do
def active_resource_access_tokens
- find('.table.active-tokens')
+ find("[data-testid='active-tokens']")
end
it 'shows active access tokens' do
@@ -129,24 +129,22 @@ RSpec.shared_examples 'active resource access tokens' do
end
RSpec.shared_examples 'inactive resource access tokens' do |no_active_tokens_text|
- def no_resource_access_tokens_message
- find('.settings-message')
+ def active_resource_access_tokens
+ find("[data-testid='active-tokens']")
end
it 'allows revocation of an active token' do
visit resource_settings_access_tokens_path
accept_gl_confirm(button_text: 'Revoke') { click_on 'Revoke' }
- expect(page).to have_selector('.settings-message')
- expect(no_resource_access_tokens_message).to have_text(no_active_tokens_text)
+ expect(active_resource_access_tokens).to have_text(no_active_tokens_text)
end
it 'removes expired tokens from active section' do
resource_access_token.update!(expires_at: 5.days.ago)
visit resource_settings_access_tokens_path
- expect(page).to have_selector('.settings-message')
- expect(no_resource_access_tokens_message).to have_text(no_active_tokens_text)
+ expect(active_resource_access_tokens).to have_text(no_active_tokens_text)
end
context 'when resource access token creation is not allowed' do
@@ -158,8 +156,39 @@ RSpec.shared_examples 'inactive resource access tokens' do |no_active_tokens_tex
visit resource_settings_access_tokens_path
accept_gl_confirm(button_text: 'Revoke') { click_on 'Revoke' }
- expect(page).to have_selector('.settings-message')
- expect(no_resource_access_tokens_message).to have_text(no_active_tokens_text)
+ expect(active_resource_access_tokens).to have_text(no_active_tokens_text)
+ end
+ end
+end
+
+RSpec.shared_examples '#create access token' do
+ let(:url) { {} }
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:token_attributes) { attributes_for(:personal_access_token) }
+
+ before do
+ sign_in(admin)
+ end
+
+ context "when POST is successful" do
+ it "renders JSON with a new token" do
+ post url, params: { personal_access_token: token_attributes }
+
+ parsed_body = Gitlab::Json.parse(response.body)
+ expect(parsed_body['new_token']).not_to be_blank
+ expect(parsed_body['errors']).to be_blank
+ expect(response).to have_gitlab_http_status(:success)
+ end
+ end
+
+ context "when POST is unsuccessful" do
+ it "renders JSON with an error" do
+ post url, params: { personal_access_token: token_attributes.merge(scopes: []) }
+
+ parsed_body = Gitlab::Json.parse(response.body)
+ expect(parsed_body['new_token']).to be_blank
+ expect(parsed_body['errors']).not_to be_blank
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
end
diff --git a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
index f7cdc4c61ec..8a07e52019c 100644
--- a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
+++ b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
@@ -1,10 +1,6 @@
# frozen_string_literal: true
RSpec.shared_examples 'comment on merge request file' do
- before do
- stub_feature_flags(remove_user_attributes_projects: false)
- end
-
it 'adds a comment' do
click_diff_line(find_by_scrolling("[id='#{sample_commit.line_code}']"))
diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb
index 21f264a8b6a..7863548e7f3 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -35,6 +35,34 @@ RSpec.shared_examples 'edits content using the content editor' do
attach_file('content_editor_image', Rails.root.join('spec', 'fixtures', fixture_name), make_visible: true)
end
+ def wait_until_hidden_field_is_updated(value)
+ expect(page).to have_field('wiki[content]', with: value, type: 'hidden')
+ end
+
+ it 'saves page content in local storage if the user navigates away' do
+ switch_to_content_editor
+
+ expect(page).to have_css(content_editor_testid)
+
+ type_in_content_editor ' Typing text in the content editor'
+
+ wait_until_hidden_field_is_updated /Typing text in the content editor/
+
+ refresh
+
+ expect(page).to have_text('Typing text in the content editor')
+
+ refresh # also retained after second refresh
+
+ expect(page).to have_text('Typing text in the content editor')
+
+ click_link 'Cancel' # draft is deleted on cancel
+
+ page.go_back
+
+ expect(page).not_to have_text('Typing text in the content editor')
+ end
+
describe 'formatting bubble menu' do
it 'shows a formatting bubble menu for a regular paragraph and headings' do
switch_to_content_editor
@@ -189,4 +217,101 @@ RSpec.shared_examples 'edits content using the content editor' do
end
end
end
+
+ describe 'autocomplete suggestions' do
+ let(:suggestions_dropdown) { '[data-testid="content-editor-suggestions-dropdown"]' }
+
+ before do
+ if defined?(project)
+ create(:issue, project: project, title: 'My Cool Linked Issue')
+ create(:merge_request, source_project: project, title: 'My Cool Merge Request')
+ create(:label, project: project, title: 'My Cool Label')
+ create(:milestone, project: project, title: 'My Cool Milestone')
+
+ project.add_maintainer(create(:user, name: 'abc123', username: 'abc123'))
+ else # group wikis
+ project = create(:project, group: group)
+
+ create(:issue, project: project, title: 'My Cool Linked Issue')
+ create(:merge_request, source_project: project, title: 'My Cool Merge Request')
+ create(:group_label, group: group, title: 'My Cool Label')
+ create(:milestone, group: group, title: 'My Cool Milestone')
+
+ project.add_maintainer(create(:user, name: 'abc123', username: 'abc123'))
+ end
+
+ switch_to_content_editor
+
+ type_in_content_editor :enter
+ end
+
+ it 'shows suggestions for members with descriptions' do
+ type_in_content_editor '@a'
+
+ expect(find(suggestions_dropdown)).to have_text('abc123')
+ expect(find(suggestions_dropdown)).to have_text('all')
+ expect(find(suggestions_dropdown)).to have_text('Group Members (2)')
+
+ send_keys [:arrow_down, :enter]
+
+ expect(page).not_to have_css(suggestions_dropdown)
+ expect(page).to have_text('@abc123')
+ end
+
+ it 'shows suggestions for merge requests' do
+ type_in_content_editor '!'
+
+ expect(find(suggestions_dropdown)).to have_text('My Cool Merge Request')
+
+ send_keys :enter
+
+ expect(page).not_to have_css(suggestions_dropdown)
+ expect(page).to have_text('!1')
+ end
+
+ it 'shows suggestions for issues' do
+ type_in_content_editor '#'
+
+ expect(find(suggestions_dropdown)).to have_text('My Cool Linked Issue')
+
+ send_keys :enter
+
+ expect(page).not_to have_css(suggestions_dropdown)
+ expect(page).to have_text('#1')
+ end
+
+ it 'shows suggestions for milestones' do
+ type_in_content_editor '%'
+
+ expect(find(suggestions_dropdown)).to have_text('My Cool Milestone')
+
+ send_keys :enter
+
+ expect(page).not_to have_css(suggestions_dropdown)
+ expect(page).to have_text('%My Cool Milestone')
+ end
+
+ it 'shows suggestions for emojis' do
+ type_in_content_editor ':smile'
+
+ expect(find(suggestions_dropdown)).to have_text('🙂 slight_smile')
+ expect(find(suggestions_dropdown)).to have_text('😸 smile_cat')
+
+ send_keys :enter
+
+ expect(page).not_to have_css(suggestions_dropdown)
+
+ expect(page).to have_text('🙂')
+ end
+
+ it 'doesn\'t show suggestions dropdown if there are no suggestions to show' do
+ type_in_content_editor '%'
+
+ expect(find(suggestions_dropdown)).to have_text('My Cool Milestone')
+
+ type_in_content_editor 'x'
+
+ expect(page).not_to have_css(suggestions_dropdown)
+ end
+ end
end
diff --git a/spec/support/shared_examples/features/deploy_token_shared_examples.rb b/spec/support/shared_examples/features/deploy_token_shared_examples.rb
index 79ad5bd6c7f..9fe08e5c996 100644
--- a/spec/support/shared_examples/features/deploy_token_shared_examples.rb
+++ b/spec/support/shared_examples/features/deploy_token_shared_examples.rb
@@ -14,32 +14,32 @@ RSpec.shared_examples 'a deploy token in settings' do
end
end
- it 'add a new deploy token' do
+ it 'add a new deploy token', :js do
visit page_path
- fill_in 'deploy_token_name', with: 'new_deploy_key'
- fill_in 'deploy_token_expires_at', with: (Date.today + 1.month).to_s
- fill_in 'deploy_token_username', with: 'deployer'
- check 'deploy_token_read_repository'
- check 'deploy_token_read_registry'
+ fill_in _('Name'), with: 'new_deploy_key'
+ fill_in _('Expiration date (optional)'), with: (Date.today + 1.month).to_s
+ fill_in _('Username (optional)'), with: 'deployer'
+ check 'read_repository'
+ check 'read_registry'
click_button 'Create deploy token'
expect(page).to have_content("Your new #{entity_type} deploy token has been created")
within('.created-deploy-token-container') do
- expect(page).to have_selector("input[name='deploy-token-user'][value='deployer']")
- expect(page).to have_selector("input[name='deploy-token'][readonly='readonly']")
+ expect(find("input[name='deploy-token-user']").value).to eq("deployer")
+ expect(find("input[name='deploy-token'][readonly='readonly']")).to be_visible
end
- expect(find("input#deploy_token_name").value).to eq nil
+ expect(find("input#deploy_token_name").value).to be_empty
expect(find("input#deploy_token_read_repository").checked?).to eq false
end
- context "with form errors" do
+ context "with form errors", :js do
before do
visit page_path
- fill_in "deploy_token_name", with: "new_deploy_key"
- fill_in "deploy_token_username", with: "deployer"
+ fill_in _('Name'), with: "new_deploy_key"
+ fill_in _('Username (optional)'), with: "deployer"
click_button "Create deploy token"
end
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 f209070d82a..68c0d06e7d0 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -209,7 +209,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
wait_for_all_requests
expect(page).to have_content(comment)
- expect(page).to have_content "@#{user.username} closed"
+ expect(page).to have_content "#{user.name} closed"
new_comment = all(comments_selector).last
@@ -334,7 +334,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
click_button 'Start thread & close issue'
expect(page).to have_content(comment)
- expect(page).to have_content "@#{user.username} closed"
+ expect(page).to have_content "#{user.name} closed"
new_discussion = all(comments_selector)[-2]
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 0a5ad5a59c0..7737f8a73c5 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
@@ -77,9 +77,9 @@ RSpec.shared_examples 'it uploads and commits a new pdf file' do |drop: false|
end
if drop
- find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'))
+ find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'sample.pdf'))
else
- attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'), make_visible: true)
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'sample.pdf'), make_visible: true)
end
page.within('#modal-upload-blob') do
@@ -90,7 +90,7 @@ RSpec.shared_examples 'it uploads and commits a new pdf file' do |drop: false|
wait_for_all_requests
- visit(project_blob_path(project, 'upload_image/git-cheat-sheet.pdf'))
+ visit(project_blob_path(project, 'upload_image/sample.pdf'))
expect(page).to have_css('.js-pdf-viewer')
end
diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb
index 31ee08ea9db..1d4af944187 100644
--- a/spec/support/shared_examples/features/runners_shared_examples.rb
+++ b/spec/support/shared_examples/features/runners_shared_examples.rb
@@ -146,6 +146,18 @@ RSpec.shared_examples 'pauses, resumes and deletes a runner' do
end
end
+RSpec.shared_examples 'filters by tag' do
+ it 'shows correct runner when tag matches' do
+ expect(page).to have_content found_runner
+ expect(page).to have_content missing_runner
+
+ input_filtered_search_filter_is_only('Tags', tag)
+
+ expect(page).to have_content found_runner
+ expect(page).not_to have_content missing_runner
+ end
+end
+
RSpec.shared_examples 'submits edit runner form' do
it 'breadcrumb contains runner id and token' do
page.within '[data-testid="breadcrumb-links"]' do
diff --git a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
index 095c48cade8..84dc2b20ddc 100644
--- a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
+++ b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
@@ -3,6 +3,7 @@
RSpec.shared_examples 'search timeouts' do |scope|
context 'when search times out' do
before do
+ stub_feature_flags(search_page_vertical_nav: false)
allow_next_instance_of(SearchService) do |service|
allow(service).to receive(:search_objects).and_raise(ActiveRecord::QueryCanceled)
end
diff --git a/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
index 95c0a76d726..206116d66c8 100644
--- a/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
@@ -20,6 +20,8 @@ RSpec.shared_examples 'date sidebar widget' do
scroll_to(button)
button.click
+ execute_script('document.querySelector(".issuable-sidebar")?.scrollBy(0, 50)')
+
click_button today.to_s
wait_for_requests
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 8081c51577a..ed885d7a226 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
@@ -147,6 +147,18 @@ RSpec.shared_examples 'User creates wiki page' do
end
end
+ it 'saves page content in local storage if the user navigates away', :js do
+ fill_in(:wiki_title, with: "Test title")
+ fill_in(:wiki_content, with: "This is a test")
+ fill_in(:wiki_message, with: "Test commit message")
+
+ refresh
+
+ expect(page).to have_field(:wiki_title, with: "Test title")
+ expect(page).to have_field(:wiki_content, with: "This is a test")
+ expect(page).to have_field(:wiki_message, with: "Test commit message")
+ end
+
it 'creates a wiki page with Org markup', :aggregate_failures, :js do
org_content = <<~ORG
* Heading
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 5c63d6a973d..0334187e4b1 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
@@ -78,6 +78,18 @@ RSpec.shared_examples 'User updates wiki page' do
expect(page).to have_content('My awesome wiki!')
end
+ it 'saves page content in local storage if the user navigates away', :js do
+ fill_in(:wiki_title, with: "Test title")
+ fill_in(:wiki_content, with: "This is a test")
+ fill_in(:wiki_message, with: "Test commit message")
+
+ refresh
+
+ expect(page).to have_field(:wiki_title, with: "Test title")
+ expect(page).to have_field(:wiki_content, with: "This is a test")
+ expect(page).to have_field(:wiki_message, with: "Test commit message")
+ end
+
it 'updates the commit message as the title is changed', :js do
fill_in(:wiki_title, with: '& < > \ \ { } &')
diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb
index 32cb2b1d187..9b5326026b1 100644
--- a/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_wiki_pages_shared_examples.rb
@@ -53,37 +53,4 @@ RSpec.shared_examples 'User views wiki pages' do
end
end
end
-
- context 'ordered by created_at' do
- let(:pages_ordered_by_created_at) { [wiki_page1, wiki_page2, wiki_page3] }
-
- before do
- page.within('.wiki-sort-dropdown') do
- click_button('Title')
- click_button('Created date')
- end
- end
-
- context 'asc' do
- it 'pages are displayed in direct order' do
- pages.each.with_index do |page_title, index|
- expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
- end
- end
- end
-
- context 'desc' do
- before do
- page.within('.wiki-sort-dropdown') do
- page.find('.rspec-reverse-sort').click
- end
- end
-
- it 'pages are displayed in reversed order' do
- pages.reverse_each.with_index do |page_title, index|
- expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
- end
- end
- end
- end
end
diff --git a/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb b/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb
index faf1bb204c9..b4afde311ba 100644
--- a/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb
+++ b/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb
@@ -1,13 +1,23 @@
# frozen_string_literal: true
-RSpec.shared_examples 'N+1 query check' do
+
+RSpec.shared_examples 'N+1 query check' do |threshold: 0, skip_cached: true|
it 'prevents N+1 queries' do
execute_query # "warm up" to prevent undeterministic counts
expect(graphql_errors).to be_blank # Sanity check - ex falso quodlibet!
- control = ActiveRecord::QueryRecorder.new { execute_query }
+ control = ActiveRecord::QueryRecorder.new(skip_cached: skip_cached) { execute_query }
expect(control.count).to be > 0
search_params[:iids] << extra_iid_for_second_query
- expect { execute_query }.not_to exceed_query_limit(control)
+
+ expect { execute_query }.not_to exceed_query_count_limit(control, skip_cached: skip_cached, threshold: threshold)
+ end
+
+ def exceed_query_count_limit(control, skip_cached: true, threshold: 0)
+ if skip_cached
+ exceed_query_limit(control).with_threshold(threshold)
+ else
+ exceed_all_query_limit(control).with_threshold(threshold)
+ end
end
end
diff --git a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
index 82a9e8130f7..2e00abe2f8e 100644
--- a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
+++ b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
@@ -54,31 +54,6 @@ RSpec.shared_examples_for 'object cache helper' do
allow(Gitlab::ApplicationContext).to receive(:current_context_attribute).with(:caller_id).and_return(caller_id)
end
- context 'when feature flag is off' do
- before do
- stub_feature_flags(add_timing_to_certain_cache_actions: false)
- end
-
- it 'does not call increment' do
- expect(transaction).not_to receive(:increment).with(:cached_object_operations_total, any_args)
-
- subject
- end
-
- it 'does not call histogram' do
- expect(Gitlab::Metrics).not_to receive(:histogram)
-
- subject
- end
-
- it "is valid JSON" do
- parsed = Gitlab::Json.parse(subject.to_s)
-
- expect(parsed).to be_a(Hash)
- expect(parsed["id"]).to eq(presentable.id)
- end
- end
-
it 'increments the counter' do
expect(transaction)
.to receive(:increment)
@@ -157,34 +132,6 @@ RSpec.shared_examples_for 'collection cache helper' do
allow(Gitlab::ApplicationContext).to receive(:current_context_attribute).with(:caller_id).and_return(caller_id)
end
- context 'when feature flag is off' do
- before do
- stub_feature_flags(add_timing_to_certain_cache_actions: false)
- end
-
- it 'does not call increment' do
- expect(transaction).not_to receive(:increment).with(:cached_object_operations_total, any_args)
-
- subject
- end
-
- it 'does not call histogram' do
- expect(Gitlab::Metrics).not_to receive(:histogram)
-
- subject
- end
-
- it "is valid JSON" do
- parsed = Gitlab::Json.parse(subject.to_s)
-
- expect(parsed).to be_an(Array)
-
- presentable.each_with_index do |item, i|
- expect(parsed[i]["id"]).to eq(item.id)
- end
- end
- end
-
context 'when presentable has a group by clause' do
let(:presentable) { MergeRequest.group(:id) }
diff --git a/spec/support/shared_examples/lib/gitlab/memory/watchdog/monitor_result_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/memory/watchdog/monitor_result_shared_examples.rb
new file mode 100644
index 00000000000..98c0e7d506b
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/memory/watchdog/monitor_result_shared_examples.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'returns Watchdog Monitor result' do |threshold_violated:|
+ it 'returns if threshold is violated and payload' do
+ result = monitor.call
+
+ expect(result[:threshold_violated]).to eq(threshold_violated)
+ expect(result[:payload]).to eq(payload)
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/regex_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/regex_shared_examples.rb
new file mode 100644
index 00000000000..150741c6344
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/regex_shared_examples.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'regex rejecting path traversal' do
+ it { is_expected.not_to match('a../b') }
+ it { is_expected.not_to match('a..%2fb') }
+ it { is_expected.not_to match('a%2e%2e%2fb') }
+ it { is_expected.not_to match('a%2e%2e/b') }
+end
diff --git a/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb
index 6b6e25ca1dd..4b4a7f4ce9d 100644
--- a/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb
@@ -47,3 +47,47 @@ RSpec.shared_examples 'file template shared examples' do |filename, file_extensi
end
end
end
+
+RSpec.shared_examples 'acts as branch pipeline' do |jobs|
+ context 'when branch pipeline' do
+ let(:pipeline_branch) { default_branch }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch) }
+ let(:pipeline) { service.execute!(:push).payload }
+
+ it 'includes a job' do
+ expect(pipeline.builds.pluck(:name)).to match_array(jobs)
+ end
+ end
+end
+
+RSpec.shared_examples 'acts as MR pipeline' do |jobs, files|
+ context 'when MR pipeline' do
+ let(:pipeline_branch) { 'patch-1' }
+ let(:service) { MergeRequests::CreatePipelineService.new(project: project, current_user: user) }
+ let(:pipeline) { service.execute(merge_request).payload }
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: pipeline_branch,
+ target_project: project,
+ target_branch: default_branch)
+ end
+
+ before do
+ files.each do |filename, contents|
+ project.repository.create_file(
+ project.creator,
+ filename,
+ contents,
+ message: "Add #{filename}",
+ branch_name: pipeline_branch)
+ end
+ end
+
+ it 'includes a job' do
+ expect(pipeline).to be_merge_request_event
+ expect(pipeline.builds.pluck(:name)).to match_array(jobs)
+ end
+ end
+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 250a4c1b1bd..ac8655a907f 100644
--- a/spec/support/shared_examples/models/boards/listable_shared_examples.rb
+++ b/spec/support/shared_examples/models/boards/listable_shared_examples.rb
@@ -27,14 +27,6 @@ RSpec.shared_examples 'boards listable model' do |list_factory|
.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
describe '#destroyable?' do
diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
index 6cfeeabc952..6d0462a9ee8 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -166,10 +166,14 @@ RSpec.shared_examples "chat integration" do |integration_name|
let(:opts) { { title: "Awesome issue", description: "please fix" } }
let(:sample_data) do
service = Issues::CreateService.new(project: project, current_user: user, params: opts, spam_params: nil)
- issue = service.execute
+ issue = service.execute[:issue]
service.hook_data(issue, "open")
end
+ before do
+ project.add_developer(user)
+ end
+
it_behaves_like "triggered #{integration_name} integration"
end
diff --git a/spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb b/spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb
new file mode 100644
index 00000000000..a4db4e25db3
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb
@@ -0,0 +1,355 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples 'a cascading namespace setting boolean attribute' do
+ |settings_attribute_name:, settings_association: :namespace_settings|
+ let_it_be_with_reload(:group) { create(:group) }
+ let_it_be_with_reload(:subgroup) { create(:group, parent: group) }
+ let(:group_settings) { group.send(settings_association) }
+ let(:subgroup_settings) { subgroup.send(settings_association) }
+
+ describe "##{settings_attribute_name}" do
+ subject(:cascading_attribute) { subgroup_settings.send(settings_attribute_name) }
+
+ before do
+ stub_application_setting(settings_attribute_name => false)
+ end
+
+ context 'when there is no parent' do
+ context 'and the value is not nil' do
+ before do
+ group_settings.update!(settings_attribute_name => true)
+ end
+
+ it 'returns the local value' do
+ expect(group_settings.send(settings_attribute_name)).to eq(true)
+ end
+ end
+
+ context 'and the value is nil' do
+ before do
+ group_settings.update!(settings_attribute_name => nil)
+ end
+
+ it 'returns the application settings value' do
+ expect(group_settings.send(settings_attribute_name)).to eq(false)
+ end
+ end
+ end
+
+ context 'when parent does not lock the attribute' do
+ context 'and value is not nil' do
+ before do
+ group_settings.update!(settings_attribute_name => false)
+ end
+
+ it 'returns local setting when present' do
+ subgroup_settings.update!(settings_attribute_name => true)
+
+ expect(cascading_attribute).to eq(true)
+ end
+
+ it 'returns the parent value when local value is nil' do
+ subgroup_settings.update!(settings_attribute_name => nil)
+
+ expect(cascading_attribute).to eq(false)
+ end
+
+ it 'returns the correct dirty value' do
+ subgroup_settings.send("#{settings_attribute_name}=", true)
+
+ expect(cascading_attribute).to eq(true)
+ end
+
+ it 'does not return the application setting value when parent value is false' do
+ stub_application_setting(settings_attribute_name => true)
+
+ expect(cascading_attribute).to eq(false)
+ end
+ end
+
+ context 'and the value is nil' do
+ before do
+ group_settings.update!(settings_attribute_name => nil, "lock_#{settings_attribute_name}".to_sym => false)
+ subgroup_settings.update!(settings_attribute_name => nil)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ end
+
+ it 'cascades to the application settings value' do
+ expect(cascading_attribute).to eq(false)
+ end
+ end
+
+ context 'when multiple ancestors set a value' do
+ let(:third_level_subgroup) { create(:group, parent: subgroup) }
+
+ before do
+ group_settings.update!(settings_attribute_name => true)
+ subgroup_settings.update!(settings_attribute_name => false)
+ end
+
+ it 'returns the closest ancestor value' do
+ expect(third_level_subgroup.send(settings_association).send(settings_attribute_name)).to eq(false)
+ end
+ end
+ end
+
+ context 'when parent locks the attribute' do
+ before do
+ subgroup_settings.update!(settings_attribute_name => true)
+ group_settings.update!("lock_#{settings_attribute_name}" => true, settings_attribute_name => false)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ subgroup_settings.clear_memoization("#{settings_attribute_name}_locked_ancestor")
+ end
+
+ it 'returns the parent value' do
+ expect(cascading_attribute).to eq(false)
+ end
+
+ it 'does not allow the local value to be saved' do
+ subgroup_settings.send("#{settings_attribute_name}=", nil)
+
+ expect { subgroup_settings.save! }
+ .to raise_error(ActiveRecord::RecordInvalid,
+ /cannot be changed because it is locked by an ancestor/)
+ end
+ end
+
+ context 'when the application settings locks the attribute' do
+ before do
+ subgroup_settings.update!(settings_attribute_name => true)
+ stub_application_setting("lock_#{settings_attribute_name}" => true, settings_attribute_name => true)
+ end
+
+ it 'returns the application setting value' do
+ expect(cascading_attribute).to eq(true)
+ end
+
+ it 'does not allow the local value to be saved' do
+ subgroup_settings.send("#{settings_attribute_name}=", false)
+
+ expect { subgroup_settings.save! }
+ .to raise_error(
+ ActiveRecord::RecordInvalid,
+ /cannot be changed because it is locked by an ancestor/
+ )
+ end
+ end
+
+ context 'when parent locked the attribute then the application settings locks it' do
+ before do
+ subgroup_settings.update!(settings_attribute_name => true)
+ group_settings.update!("lock_#{settings_attribute_name}" => true, settings_attribute_name => false)
+ stub_application_setting("lock_#{settings_attribute_name}" => true, settings_attribute_name => true)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ subgroup_settings.clear_memoization("#{settings_attribute_name}_locked_ancestor")
+ end
+
+ it 'returns the application setting value' do
+ expect(cascading_attribute).to eq(true)
+ end
+ end
+ end
+
+ describe "##{settings_attribute_name}?" do
+ before do
+ subgroup_settings.update!(settings_attribute_name => true)
+ group_settings.update!("lock_#{settings_attribute_name}" => true, settings_attribute_name => false)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ subgroup_settings.clear_memoization("#{settings_attribute_name}_locked_ancestor")
+ end
+
+ it 'aliases the method when the attribute is a boolean' do
+ expect(subgroup_settings.send("#{settings_attribute_name}?"))
+ .to eq(subgroup_settings.send(settings_attribute_name))
+ end
+ end
+
+ describe "##{settings_attribute_name}=" do
+ before do
+ subgroup_settings.update!(settings_attribute_name => nil)
+ group_settings.update!(settings_attribute_name => true)
+ end
+
+ it 'does not save the value locally when it matches the cascaded value' do
+ subgroup_settings.update!(settings_attribute_name => true)
+
+ expect(subgroup_settings.read_attribute(settings_attribute_name)).to eq(nil)
+ end
+ end
+
+ describe "##{settings_attribute_name}_locked?" do
+ shared_examples 'not locked' do
+ it 'is not locked by an ancestor' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_ancestor?")).to eq(false)
+ end
+
+ it 'is not locked by application setting' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_application_setting?")).to eq(false)
+ end
+
+ it 'does not return a locked namespace' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_ancestor")).to be_nil
+ end
+ end
+
+ context 'when attribute is locked by self' do
+ before do
+ subgroup_settings.update!("lock_#{settings_attribute_name}" => true)
+ end
+
+ it 'is not locked by default' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked?")).to eq(false)
+ end
+
+ it 'is locked when including self' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked?", include_self: true)).to eq(true)
+ end
+ end
+
+ context 'when parent does not lock the attribute' do
+ it_behaves_like 'not locked'
+ end
+
+ context 'when parent locks the attribute' do
+ before do
+ group_settings.update!("lock_#{settings_attribute_name}".to_sym => true, settings_attribute_name => false)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ subgroup_settings.clear_memoization("#{settings_attribute_name}_locked_ancestor")
+ end
+
+ it 'is locked by an ancestor' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_ancestor?")).to eq(true)
+ end
+
+ it 'is not locked by application setting' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_application_setting?")).to eq(false)
+ end
+
+ it 'returns a locked namespace settings object' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_ancestor").namespace_id)
+ .to eq(group_settings.namespace_id)
+ end
+ end
+
+ context 'when not locked by application settings' do
+ before do
+ stub_application_setting("lock_#{settings_attribute_name}" => false)
+ end
+
+ it_behaves_like 'not locked'
+ end
+
+ context 'when locked by application settings' do
+ before do
+ stub_application_setting("lock_#{settings_attribute_name}" => true)
+ end
+
+ it 'is not locked by an ancestor' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_ancestor?")).to eq(false)
+ end
+
+ it 'is locked by application setting' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_by_application_setting?")).to eq(true)
+ end
+
+ it 'does not return a locked namespace' do
+ expect(subgroup_settings.send("#{settings_attribute_name}_locked_ancestor")).to be_nil
+ end
+ end
+ end
+
+ describe "#lock_#{settings_attribute_name}=" do
+ context 'when parent locks the attribute' do
+ before do
+ group_settings.update!("lock_#{settings_attribute_name}".to_sym => true, settings_attribute_name => false)
+
+ subgroup_settings.clear_memoization(settings_attribute_name)
+ subgroup_settings.clear_memoization("#{settings_attribute_name}_locked_ancestor")
+ end
+
+ it 'does not allow the attribute to be saved' do
+ subgroup_settings.send("lock_#{settings_attribute_name}=", true)
+
+ expect { subgroup_settings.save! }
+ .to raise_error(ActiveRecord::RecordInvalid,
+ /cannot be changed because it is locked by an ancestor/)
+ end
+ end
+
+ context 'when parent does not lock the attribute' do
+ before do
+ group_settings.update!("lock_#{settings_attribute_name}" => false, settings_attribute_name => false)
+
+ subgroup_settings.send("lock_#{settings_attribute_name}=", true)
+ end
+
+ it 'allows the lock to be set when the attribute is not nil' do
+ subgroup_settings.send("#{settings_attribute_name}=", true)
+
+ expect(subgroup_settings.save).to eq(true)
+ end
+
+ it 'does not allow the lock to be saved when the attribute is nil' do
+ subgroup_settings.send("#{settings_attribute_name}=", nil)
+
+ expect { subgroup_settings.save! }
+ .to raise_error(ActiveRecord::RecordInvalid,
+ /cannot be nil when locking the attribute/)
+ end
+
+ it 'copies the cascaded value when locking the attribute if the local value is nil', :aggregate_failures do
+ subgroup_settings.send("#{settings_attribute_name}=", nil)
+ subgroup_settings.send("lock_#{settings_attribute_name}=", true)
+
+ expect(subgroup_settings.read_attribute(settings_attribute_name)).to eq(false)
+ end
+ end
+
+ context 'when application settings locks the attribute' do
+ before do
+ stub_application_setting("lock_#{settings_attribute_name}".to_sym => true)
+ end
+
+ it 'does not allow the attribute to be saved' do
+ subgroup_settings.send("lock_#{settings_attribute_name}=", true)
+
+ expect { subgroup_settings.save! }
+ .to raise_error(ActiveRecord::RecordInvalid,
+ /cannot be changed because it is locked by an ancestor/)
+ end
+ end
+
+ context 'when application_settings does not lock the attribute' do
+ before do
+ stub_application_setting("lock_#{settings_attribute_name}".to_sym => false)
+ end
+
+ it 'allows the attribute to be saved' do
+ subgroup_settings.send("#{settings_attribute_name}=", true)
+ subgroup_settings.send("lock_#{settings_attribute_name}=", true)
+
+ expect(subgroup_settings.save).to eq(true)
+ end
+ end
+ end
+
+ describe 'after update callback' do
+ before do
+ group_settings.update!("lock_#{settings_attribute_name}" => false, settings_attribute_name => false)
+ subgroup_settings.update!("lock_#{settings_attribute_name}" => true, settings_attribute_name => false)
+ end
+
+ it 'clears descendant locks' do
+ group_settings.update!("lock_#{settings_attribute_name}" => true, settings_attribute_name => true)
+
+ expect(subgroup_settings.reload.send("lock_#{settings_attribute_name}")).to eq(false)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
index f3a12578912..a658d02f09a 100644
--- a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
@@ -92,8 +92,8 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
it 'obtains an exclusive lease during processing' do
expect(model)
.to receive(:in_lock)
- .with(model.counter_lock_key(incremented_attribute), ttl: described_class::WORKER_LOCK_TTL)
- .and_call_original
+ .with(model.counter_lock_key(incremented_attribute), ttl: described_class::WORKER_LOCK_TTL)
+ .and_call_original
subject
end
@@ -104,7 +104,14 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
model.delayed_increment_counter(incremented_attribute, -3)
end
- it 'updates the record and logs it' do
+ it 'updates the record and logs it', :aggregate_failures do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Acquiring lease for project statistics update',
+ attributes: [incremented_attribute]
+ )
+ )
+
expect(Gitlab::AppLogger).to receive(:info).with(
hash_including(
message: 'Flush counter attribute to database',
@@ -124,14 +131,14 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
it 'removes the increment entry from Redis' do
Gitlab::Redis::SharedState.with do |redis|
- key_exists = redis.exists(model.counter_key(incremented_attribute))
+ key_exists = redis.exists?(model.counter_key(incremented_attribute))
expect(key_exists).to be_truthy
end
subject
Gitlab::Redis::SharedState.with do |redis|
- key_exists = redis.exists(model.counter_key(incremented_attribute))
+ key_exists = redis.exists?(model.counter_key(incremented_attribute))
expect(key_exists).to be_falsey
end
end
@@ -162,7 +169,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
subject
Gitlab::Redis::SharedState.with do |redis|
- key_exists = redis.exists(model.counter_flushed_key(incremented_attribute))
+ key_exists = redis.exists?(model.counter_flushed_key(incremented_attribute))
expect(key_exists).to be_falsey
end
end
@@ -186,31 +193,88 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
end
end
- describe '#clear_counter!' do
+ describe '#reset_counter!' do
let(:attribute) { counter_attributes.first }
before do
+ model.update!(attribute => 123)
model.increment_counter(attribute, 10)
end
- it 'deletes the counter key for the given attribute and logs it' do
- expect(Gitlab::AppLogger).to receive(:info).with(
- hash_including(
- message: 'Clear counter attribute',
- attribute: attribute,
- project_id: model.project_id,
- 'correlation_id' => an_instance_of(String),
- 'meta.feature_category' => 'test',
- 'meta.caller_id' => 'caller'
- )
- )
+ subject { model.reset_counter!(attribute) }
- model.clear_counter!(attribute)
+ it 'resets the attribute value to 0 and clears existing counter', :aggregate_failures do
+ expect { subject }.to change { model.reload.send(attribute) }.from(123).to(0)
Gitlab::Redis::SharedState.with do |redis|
- key_exists = redis.exists(model.counter_key(attribute))
+ key_exists = redis.exists?(model.counter_key(attribute))
expect(key_exists).to be_falsey
end
end
+
+ it_behaves_like 'obtaining lease to update database' do
+ context 'when the execution raises error' do
+ before do
+ allow(model).to receive(:update!).and_raise(StandardError, 'Something went wrong')
+ end
+
+ it 'reraises error' do
+ expect { subject }.to raise_error(StandardError, 'Something went wrong')
+ end
+ end
+ end
+ end
+
+ describe '#update_counters_with_lease' do
+ let(:increments) { { build_artifacts_size: 1, packages_size: 2 } }
+
+ subject { model.update_counters_with_lease(increments) }
+
+ it 'updates counters of the record' do
+ expect { subject }
+ .to change { model.reload.build_artifacts_size }.by(1)
+ .and change { model.reload.packages_size }.by(2)
+ end
+
+ it_behaves_like 'obtaining lease to update database' do
+ context 'when the execution raises error' do
+ before do
+ allow(model.class).to receive(:update_counters).and_raise(StandardError, 'Something went wrong')
+ end
+
+ it 'reraises error' do
+ expect { subject }.to raise_error(StandardError, 'Something went wrong')
+ end
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'obtaining lease to update database' do
+ context 'when it is unable to obtain lock' do
+ before do
+ allow(model).to receive(:in_lock).and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
+ end
+
+ it 'logs a warning' do
+ allow(model).to receive(:in_lock).and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
+
+ expect(Gitlab::AppLogger).to receive(:warn).once
+
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'when feature flag counter_attribute_db_lease_for_update is disabled' do
+ before do
+ stub_feature_flags(counter_attribute_db_lease_for_update: false)
+ allow(model).to receive(:in_lock).and_call_original
+ end
+
+ it 'does not attempt to get a lock' do
+ expect(model).not_to receive(:in_lock)
+
+ subject
+ end
end
end
diff --git a/spec/support/shared_examples/models/concerns/has_wiki_shared_examples.rb b/spec/support/shared_examples/models/concerns/has_wiki_shared_examples.rb
index 0357b7462fb..65bc6c10490 100644
--- a/spec/support/shared_examples/models/concerns/has_wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/has_wiki_shared_examples.rb
@@ -15,7 +15,7 @@ RSpec.shared_examples 'model with wiki' do
context 'when the repository cannot be created' do
before do
- expect(container.wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError }
+ expect(container.wiki).to receive(:create_wiki_repository) { raise Wiki::CouldNotCreateWikiError }
end
it 'returns false and adds a validation error' do
diff --git a/spec/support/shared_examples/models/concerns/participable_shared_examples.rb b/spec/support/shared_examples/models/concerns/participable_shared_examples.rb
new file mode 100644
index 00000000000..ec7a9105bb2
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/participable_shared_examples.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'visible participants for issuable with read ability' do |model_class|
+ let_it_be(:user1) { create(:user) }
+
+ let(:model) { model_class.to_s.classify.constantize }
+
+ before do
+ allow(Ability).to receive(:allowed?).with(anything, :"read_#{model_class}", anything).and_return(true)
+ allow(model).to receive(:participant_attrs).and_return([:bar])
+ end
+
+ shared_examples 'check for participables read ability' do |ability_name|
+ it 'receives expected ability' do
+ instance = model.new
+
+ allow(instance).to receive(:bar).and_return(participable_source)
+
+ expect(Ability).to receive(:allowed?).with(anything, ability_name, instance)
+
+ expect(instance.visible_participants(user1)).to be_empty
+ end
+ end
+
+ context 'when source is an award emoji' do
+ let(:participable_source) { build(:award_emoji, :upvote) }
+
+ it_behaves_like 'check for participables read ability', :read_issuable_participables
+ end
+
+ context 'when source is a note' do
+ let(:participable_source) { build(:note) }
+
+ it_behaves_like 'check for participables read ability', :read_note
+ end
+
+ context 'when source is an internal note' do
+ let(:participable_source) { build(:note, :confidential) }
+
+ it_behaves_like 'check for participables read ability', :read_internal_note
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
index d06e8391a9a..e4958779957 100644
--- a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
@@ -1,10 +1,7 @@
# frozen_string_literal: true
RSpec.shared_examples 'a timebox' do |timebox_type|
- let(:project) { create(:project, :public) }
- let(:group) { create(:group) }
let(:timebox_args) { [] }
- let(:timebox) { create(timebox_type, *timebox_args, project: project) }
let(:issue) { create(:issue, project: project) }
let(:user) { create(:user) }
let(:timebox_table_name) { timebox_type.to_s.pluralize.to_sym }
@@ -14,28 +11,6 @@ RSpec.shared_examples 'a timebox' do |timebox_type|
let(:open_on_left) { nil }
let(:open_on_right) { nil }
- describe 'modules' do
- context 'with a project' do
- it_behaves_like 'AtomicInternalId' do
- let(:internal_id_attribute) { :iid }
- let(:instance) { build(timebox_type, *timebox_args, project: create(:project), group: nil) }
- let(:scope) { :project }
- let(:scope_attrs) { { project: instance.project } }
- let(:usage) { timebox_table_name }
- end
- end
-
- context 'with a group' do
- it_behaves_like 'AtomicInternalId' do
- let(:internal_id_attribute) { :iid }
- let(:instance) { build(timebox_type, *timebox_args, project: nil, group: create(:group)) }
- let(:scope) { :group }
- let(:scope_attrs) { { namespace: instance.group } }
- let(:usage) { timebox_table_name }
- end
- end
- end
-
describe "Validation" do
before do
allow(subject).to receive(:set_iid).and_return(false)
@@ -65,21 +40,9 @@ RSpec.shared_examples 'a timebox' do |timebox_type|
expect(timebox.errors[:due_date]).to include("date must not be after 9999-12-31")
end
end
-
- describe '#timebox_type_check' do
- it 'is invalid if it has both project_id and group_id' do
- timebox = build(timebox_type, *timebox_args, group: group)
- timebox.project = project
-
- expect(timebox).not_to be_valid
- expect(timebox.errors[:project_id]).to include("#{timebox_type} should belong either to a project or a group.")
- end
- end
end
describe "Associations" do
- it { is_expected.to belong_to(:project) }
- it { is_expected.to belong_to(:group) }
it { is_expected.to have_many(:issues) }
it { is_expected.to have_many(:merge_requests) }
it { is_expected.to have_many(:labels).through(:issues) }
@@ -91,38 +54,6 @@ RSpec.shared_examples 'a timebox' do |timebox_type|
end
end
- describe '#project_timebox?' do
- context 'when project_id is present' do
- it 'returns true' do
- expect(timebox.project_timebox?).to be_truthy
- end
- end
-
- context 'when project_id is not present' do
- let(:timebox) { build(timebox_type, *timebox_args, group: group) }
-
- it 'returns false' do
- expect(timebox.project_timebox?).to be_falsey
- end
- end
- end
-
- describe '#group_timebox?' do
- context 'when group_id is present' do
- let(:timebox) { build(timebox_type, *timebox_args, group: group) }
-
- it 'returns true' do
- expect(timebox.group_timebox?).to be_truthy
- end
- end
-
- context 'when group_id is not present' do
- it 'returns false' do
- expect(timebox.group_timebox?).to be_falsey
- end
- end
- end
-
describe '#safe_title' do
let(:timebox) { create(timebox_type, *timebox_args, title: "<b>foo & bar -> 2.2</b>") }
@@ -131,22 +62,6 @@ RSpec.shared_examples 'a timebox' do |timebox_type|
end
end
- describe '#resource_parent' do
- context 'when group is present' do
- let(:timebox) { build(timebox_type, *timebox_args, group: group) }
-
- it 'returns the group' do
- expect(timebox.resource_parent).to eq(group)
- end
- end
-
- context 'when project is present' do
- it 'returns the project' do
- expect(timebox.resource_parent).to eq(project)
- end
- end
- end
-
describe "#title" do
let(:timebox) { create(timebox_type, *timebox_args, title: "<b>foo & bar -> 2.2</b>") }
@@ -155,39 +70,6 @@ RSpec.shared_examples 'a timebox' do |timebox_type|
end
end
- describe '#merge_requests_enabled?' do
- context "per project" do
- it "is true for projects with MRs enabled" do
- project = create(:project, :merge_requests_enabled)
- timebox = create(timebox_type, *timebox_args, project: project)
-
- expect(timebox.merge_requests_enabled?).to be_truthy
- end
-
- it "is false for projects with MRs disabled" do
- project = create(:project, :repository_enabled, :merge_requests_disabled)
- timebox = create(timebox_type, *timebox_args, project: project)
-
- expect(timebox.merge_requests_enabled?).to be_falsey
- end
-
- it "is false for projects with repository disabled" do
- project = create(:project, :repository_disabled)
- timebox = create(timebox_type, *timebox_args, project: project)
-
- expect(timebox.merge_requests_enabled?).to be_falsey
- end
- end
-
- context "per group" do
- let(:timebox) { create(timebox_type, *timebox_args, group: group) }
-
- it "is always true for groups, for performance reasons" do
- expect(timebox.merge_requests_enabled?).to be_truthy
- end
- end
- end
-
describe '#to_ability_name' do
it 'returns timebox' do
timebox = build(timebox_type, *timebox_args)
diff --git a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
index e309aa50c6e..31ec25249d7 100644
--- a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
+++ b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
@@ -4,7 +4,7 @@ RSpec.shared_examples Integrations::HasWebHook do
include AfterNextHelpers
describe 'associations' do
- it { is_expected.to have_one(:service_hook).inverse_of(:integration).with_foreign_key(:service_id) }
+ it { is_expected.to have_one(:service_hook).inverse_of(:integration).with_foreign_key(:integration_id) }
end
describe 'callbacks' do
diff --git a/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb b/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb
index c92e819db19..3caf58da4d2 100644
--- a/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb
+++ b/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb
@@ -5,12 +5,14 @@ RSpec.shared_examples 'ci_cd_settings delegation' do
context 'when ci_cd_settings is destroyed but project is not' do
it 'allows methods delegated to ci_cd_settings to be nil', :aggregate_failures do
- project = create(:project)
attributes = project.ci_cd_settings.attributes.keys - %w(id project_id) - exclude_attributes
+
+ expect(attributes).to match_array(attributes_with_prefix.keys)
+
project.ci_cd_settings.destroy!
project.reload
- attributes.each do |attr|
- method = project.respond_to?("ci_#{attr}") ? "ci_#{attr}" : attr
+ attributes_with_prefix.each do |attr, prefix|
+ method = "#{prefix}#{attr}"
expect(project.send(method)).to be_nil, "#{attr} was not nil"
end
end
@@ -20,8 +22,6 @@ end
RSpec.shared_examples 'a ci_cd_settings predicate method' do |prefix: ''|
using RSpec::Parameterized::TableSyntax
- let_it_be(:project) { create(:project) }
-
context 'when ci_cd_settings is nil' do
before do
allow(project).to receive(:ci_cd_settings).and_return(nil)
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index 5f6a10bd754..b1aa90449e1 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.shared_examples 'wiki model' do
+ using RSpec::Parameterized::TableSyntax
+
let_it_be(:user) { create(:user, :commit_email) }
let(:wiki_container) { raise NotImplementedError }
@@ -124,36 +126,6 @@ RSpec.shared_examples 'wiki model' do
end
end
- describe '#wiki' do
- it 'contains a Gitlab::Git::Wiki instance' do
- expect(subject.wiki).to be_a Gitlab::Git::Wiki
- end
-
- it 'creates a new wiki repo if one does not yet exist' do
- expect(subject.create_page('index', 'test content')).to be_truthy
- end
-
- it 'creates a new wiki repo with a default commit message' do
- expect(subject.create_page('index', 'test content', :markdown, '')).to be_truthy
-
- page = subject.find_page('index')
-
- expect(page.last_version.message).to eq("#{user.username} created page: index")
- end
-
- context 'when the repository cannot be created' do
- let(:wiki_container) { wiki_container_without_repo }
-
- before do
- expect(subject.repository).to receive(:create_if_not_exists) { false }
- end
-
- it 'raises CouldNotCreateWikiError' do
- expect { subject.wiki }.to raise_exception(Wiki::CouldNotCreateWikiError)
- end
- end
- end
-
describe '#empty?' do
context 'when the wiki repository is empty' do
it 'returns true' do
@@ -180,70 +152,71 @@ RSpec.shared_examples 'wiki model' do
it 'returns false' do
expect(subject.empty?).to be(false)
end
-
- it 'only instantiates a Wiki page once' do
- expect(WikiPage).to receive(:new).once.and_call_original
-
- subject.empty?
- end
end
end
end
describe '#list_pages' do
- let(:wiki_pages) { subject.list_pages }
+ shared_examples 'wiki model #list_pages' do
+ let(:wiki_pages) { subject.list_pages }
- before do
- subject.create_page('index', 'This is an index')
- subject.create_page('index2', 'This is an index2')
- subject.create_page('an index3', 'This is an index3')
- end
+ before do
+ subject.create_page('index', 'This is an index')
+ subject.create_page('index2', 'This is an index2')
+ subject.create_page('an index3', 'This is an index3')
+ end
- it 'returns an array of WikiPage instances' do
- expect(wiki_pages).to be_present
- expect(wiki_pages).to all(be_a(WikiPage))
- end
+ it 'returns an array of WikiPage instances' do
+ expect(wiki_pages).to be_present
+ expect(wiki_pages).to all(be_a(WikiPage))
+ end
- it 'does not load WikiPage content by default' do
- wiki_pages.each do |page|
- expect(page.content).to be_empty
+ it 'does not load WikiPage content by default' do
+ wiki_pages.each do |page|
+ expect(page.content).to be_empty
+ end
end
- end
- it 'returns all pages by default' do
- expect(wiki_pages.count).to eq(3)
- end
+ it 'returns all pages by default' do
+ expect(wiki_pages.count).to eq(3)
+ end
- context 'with limit option' do
- it 'returns limited set of pages' do
- expect(subject.list_pages(limit: 1).count).to eq(1)
+ context 'with limit option' do
+ it 'returns limited set of pages' do
+ expect(subject.list_pages(limit: 1).count).to eq(1)
+ end
end
- end
- context 'with sorting options' do
- it 'returns pages sorted by title by default' do
- pages = ['an index3', 'index', 'index2']
+ context 'with sorting options' do
+ it 'returns pages sorted by title by default' do
+ pages = ['an index3', 'index', 'index2']
- expect(subject.list_pages.map(&:title)).to eq(pages)
- expect(subject.list_pages(direction: 'desc').map(&:title)).to eq(pages.reverse)
+ expect(subject.list_pages.map(&:title)).to eq(pages)
+ expect(subject.list_pages(direction: 'desc').map(&:title)).to eq(pages.reverse)
+ end
end
- it 'returns pages sorted by created_at' do
- pages = ['index', 'index2', 'an index3']
+ context 'with load_content option' do
+ let(:pages) { subject.list_pages(load_content: true) }
- expect(subject.list_pages(sort: 'created_at').map(&:title)).to eq(pages)
- expect(subject.list_pages(sort: 'created_at', direction: 'desc').map(&:title)).to eq(pages.reverse)
+ it 'loads WikiPage content' do
+ expect(pages.first.content).to eq('This is an index3')
+ expect(pages.second.content).to eq('This is an index')
+ expect(pages.third.content).to eq('This is an index2')
+ end
end
end
- context 'with load_content option' do
- let(:pages) { subject.list_pages(load_content: true) }
-
- it 'loads WikiPage content' do
- expect(pages.first.content).to eq('This is an index3')
- expect(pages.second.content).to eq('This is an index')
- expect(pages.third.content).to eq('This is an index2')
+ context 'list pages with legacy wiki rpcs' do
+ before do
+ stub_feature_flags(wiki_list_page_with_normal_repository_rpcs: false)
end
+
+ it_behaves_like 'wiki model #list_pages'
+ end
+
+ context 'list pages with normal repository rpcs' do
+ it_behaves_like 'wiki model #list_pages'
end
end
@@ -338,6 +311,74 @@ RSpec.shared_examples 'wiki model' do
end
end
+ context "wiki repository's default branch is updated" do
+ before do
+ old_default_branch = wiki.default_branch
+ subject.create_page('page in updated default branch', 'content')
+ subject.repository.add_branch(user, 'another_branch', old_default_branch)
+ subject.repository.rm_branch(user, old_default_branch)
+ subject.repository.expire_status_cache
+ end
+
+ it 'returns the page in the updated default branch' do
+ wiki = described_class.new(wiki_container, user)
+ page = wiki.find_page('page in updated default branch')
+
+ expect(wiki.default_branch).to eql('another_branch')
+ expect(page.title).to eq('page in updated default branch')
+ end
+ end
+
+ context "wiki repository's HEAD is updated" do
+ before do
+ subject.create_page('page in updated HEAD', 'content')
+ subject.repository.add_branch(user, 'another_branch', subject.default_branch)
+ subject.repository.change_head('another_branch')
+ subject.repository.expire_status_cache
+ end
+
+ it 'returns the page in the new HEAD' do
+ wiki = described_class.new(wiki_container, user)
+ page = subject.find_page('page in updated HEAD')
+
+ expect(wiki.default_branch).to eql('another_branch')
+ expect(page.title).to eq('page in updated HEAD')
+ end
+ end
+
+ context 'pages with relative paths' do
+ where(:path, :title) do
+ [
+ ['~hello.md', '~Hello'],
+ ['hello~world.md', 'Hello~World'],
+ ['~~~hello.md', '~~~Hello'],
+ ['~/hello.md', '~/Hello'],
+ ['hello.md', '/Hello'],
+ ['hello.md', '../Hello'],
+ ['hello.md', './Hello'],
+ ['dir/hello.md', '/dir/Hello']
+ ]
+ end
+
+ with_them do
+ before do
+ wiki.repository.create_file(
+ user, path, "content of wiki file",
+ branch_name: wiki.default_branch,
+ message: "created page #{path}",
+ author_email: user.email,
+ author_name: user.name
+ )
+ end
+
+ it "can find page with `#{params[:title]}` title" do
+ page = subject.find_page(title)
+
+ expect(page.content).to eq("content of wiki file")
+ end
+ end
+ end
+
context 'pages with different file extensions' do
where(:extension, :path, :title) do
[
@@ -378,14 +419,6 @@ RSpec.shared_examples 'wiki model' do
end
end
- context 'find page with legacy wiki service' do
- before do
- stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
- end
-
- it_behaves_like 'wiki model #find_page'
- end
-
context 'find page with normal repository RPCs' do
it_behaves_like 'wiki model #find_page'
end
@@ -404,14 +437,6 @@ RSpec.shared_examples 'wiki model' do
end
end
- context 'find sidebar with legacy wiki service' do
- before do
- stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
- end
-
- it_behaves_like 'wiki model #find_sidebar'
- end
-
context 'find sidebar with normal repository RPCs' do
it_behaves_like 'wiki model #find_sidebar'
end
@@ -421,7 +446,7 @@ RSpec.shared_examples 'wiki model' do
let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
before do
- subject.wiki # Make sure the wiki repo exists
+ subject.create_wiki_repository # Make sure the wiki repo exists
subject.repository.create_file(user, 'image.png', image, branch_name: subject.default_branch, message: 'add image')
end
@@ -456,6 +481,22 @@ RSpec.shared_examples 'wiki model' do
expect(file.raw_data).to be_empty
end
end
+
+ context "wiki repository's default branch is updated" do
+ before do
+ old_default_branch = wiki.default_branch
+ subject.repository.add_branch(user, 'another_branch', old_default_branch)
+ subject.repository.rm_branch(user, old_default_branch)
+ subject.repository.expire_status_cache
+ end
+
+ it 'returns the page in the updated default branch' do
+ wiki = described_class.new(wiki_container, user)
+ file = wiki.find_file('image.png')
+
+ expect(file.mime_type).to eq('image/png')
+ end
+ end
end
describe '#create_page' do
@@ -480,7 +521,7 @@ RSpec.shared_examples 'wiki model' do
it 'sets the correct commit message' do
subject.create_page('test page', 'some content', :markdown, 'commit message')
- expect(subject.list_pages.first.page.version.message).to eq('commit message')
+ expect(subject.list_pages.first.version.message).to eq('commit message')
end
it 'sets the correct commit email' do
@@ -577,6 +618,8 @@ RSpec.shared_examples 'wiki model' do
'foo' | :org | ['foo.md'] | false
'foo' | :markdown | ['dir/foo.md'] | true
'/foo' | :markdown | ['foo.md'] | false
+ '~foo' | :markdown | [] | true
+ '~~~foo' | :markdown | [] | true
'./foo' | :markdown | ['foo.md'] | false
'../foo' | :markdown | ['foo.md'] | false
'../../foo' | :markdown | ['foo.md'] | false
@@ -607,14 +650,6 @@ RSpec.shared_examples 'wiki model' do
end
it_behaves_like 'create_page tests'
-
- context 'create page with legacy find_page wiki service' do
- it_behaves_like 'create_page tests' do
- before do
- stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
- end
- end
- end
end
describe '#update_page' do
@@ -687,6 +722,8 @@ RSpec.shared_examples 'wiki model' do
using RSpec::Parameterized::TableSyntax
where(:original_title, :original_format, :updated_title, :updated_format, :expected_title, :expected_path) do
+ 'test page' | :markdown | '~new test page' | :asciidoc | '~new test page' | '~new-test-page.asciidoc'
+ 'test page' | :markdown | '~~~new test page' | :asciidoc | '~~~new test page' | '~~~new-test-page.asciidoc'
'test page' | :markdown | 'new test page' | :asciidoc | 'new test page' | 'new-test-page.asciidoc'
'test page' | :markdown | 'new dir/new test page' | :asciidoc | 'new dir/new test page' | 'new-dir/new-test-page.asciidoc'
'test dir/test page' | :markdown | 'new dir/new test page' | :asciidoc | 'new dir/new test page' | 'new-dir/new-test-page.asciidoc'
@@ -696,13 +733,13 @@ RSpec.shared_examples 'wiki model' do
'test dir/test page' | :markdown | nil | :markdown | 'test dir/test page' | 'test-dir/test-page.md'
'test page' | :markdown | '' | :markdown | 'test page' | 'test-page.md'
'test.page' | :markdown | '' | :markdown | 'test.page' | 'test.page.md'
- 'testpage' | :markdown | '../testpage' | :markdown | 'testpage' | 'testpage.md'
- 'dir/testpage' | :markdown | 'dir/../testpage' | :markdown | 'testpage' | 'testpage.md'
- 'dir/testpage' | :markdown | './dir/testpage' | :markdown | 'dir/testpage' | 'dir/testpage.md'
- 'dir/testpage' | :markdown | '../dir/testpage' | :markdown | 'dir/testpage' | 'dir/testpage.md'
- 'dir/testpage' | :markdown | '../dir/../testpage' | :markdown | 'testpage' | 'testpage.md'
- 'dir/testpage' | :markdown | '../dir/../dir/testpage' | :markdown | 'dir/testpage' | 'dir/testpage.md'
- 'dir/testpage' | :markdown | '../dir/../another/testpage' | :markdown | 'another/testpage' | 'another/testpage.md'
+ 'testpage' | :markdown | '../testpage' | :markdown | 'testpage' | 'testpage.md'
+ 'dir/testpage' | :markdown | 'dir/../testpage' | :markdown | 'testpage' | 'testpage.md'
+ 'dir/testpage' | :markdown | './dir/testpage' | :markdown | 'dir/testpage' | 'dir/testpage.md'
+ 'dir/testpage' | :markdown | '../dir/testpage' | :markdown | 'dir/testpage' | 'dir/testpage.md'
+ 'dir/testpage' | :markdown | '../dir/../testpage' | :markdown | 'testpage' | 'testpage.md'
+ 'dir/testpage' | :markdown | '../dir/../dir/testpage' | :markdown | 'dir/testpage' | 'dir/testpage.md'
+ 'dir/testpage' | :markdown | '../dir/../another/testpage' | :markdown | 'another/testpage' | 'another/testpage.md'
end
end
@@ -711,17 +748,6 @@ RSpec.shared_examples 'wiki model' do
include_context 'extended examples'
end
- context 'update page with legacy find_page wiki service' do
- it_behaves_like 'update_page tests' do
- before do
- stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
- end
-
- include_context 'common examples'
- include_context 'extended examples'
- end
- end
-
context 'when format is invalid' do
let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') }
@@ -862,7 +888,7 @@ RSpec.shared_examples 'wiki model' do
end
describe '#create_wiki_repository' do
- let(:head_path) { Rails.root.join(TestEnv.repos_path, "#{wiki.disk_path}.git", 'HEAD') }
+ let(:head_path) { Gitlab::GitalyClient::StorageSettings.allow_disk_access { Rails.root.join(TestEnv.repos_path, "#{wiki.disk_path}.git", 'HEAD') } }
let(:default_branch) { 'foo' }
before do
@@ -895,4 +921,40 @@ RSpec.shared_examples 'wiki model' do
end
end
end
+
+ describe '#preview_slug' do
+ where(:title, :file_extension, :format, :expected_slug) do
+ 'The Best Thing' | :md | :markdown | 'The-Best-Thing'
+ 'The Best Thing' | :txt | :plaintext | 'The-Best-Thing'
+ 'A Subject/Title Here' | :txt | :plaintext | 'A-Subject/Title-Here'
+ 'A subject' | :txt | :plaintext | 'A-subject'
+ 'A 1/B 2/C 3' | :txt | :plaintext | 'A-1/B-2/C-3'
+ 'subject/title' | :txt | :plaintext | 'subject/title'
+ 'subject/title.md' | :txt | :plaintext | 'subject/title.md'
+ 'foo%2Fbar' | :txt | :plaintext | 'foo%2Fbar'
+ '' | :md | :markdown | '.md'
+ '' | :txt | :plaintext | '.txt'
+ end
+
+ with_them do
+ before do
+ subject.repository.create_file(
+ user, "#{title}.#{file_extension}", 'content',
+ branch_name: subject.default_branch,
+ message: "Add #{title}"
+ )
+ end
+
+ it do
+ expect(described_class.preview_slug(title, file_extension)).to eq(expected_slug)
+ end
+
+ it 'matches the slug generated by gitaly' do
+ skip('Gitaly cannot generate a slug for an empty title') unless title.present?
+
+ gitaly_slug = subject.list_pages.first.slug
+ expect(described_class.preview_slug(title, file_extension)).to eq(gitaly_slug)
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb b/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb
index 991d6289373..b9d4709efd5 100644
--- a/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb
+++ b/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.shared_examples 'model with wiki policies' do
- include ProjectHelpers
+ include UserHelpers
include AdminModeHelper
let(:container) { raise NotImplementedError }
diff --git a/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb
index 9c2d30a9c8c..f7731af8dc6 100644
--- a/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb
+++ b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb
@@ -1,53 +1,5 @@
# frozen_string_literal: true
-RSpec.shared_examples 'handling invalid params' do |service_response_extra: {}, supports_caching: false|
- context 'when no params are specified' do
- let(:params) { {} }
-
- it_behaves_like 'not removing anything',
- service_response_extra: service_response_extra,
- supports_caching: supports_caching
- end
-
- context 'with invalid regular expressions' do
- shared_examples 'handling an invalid regex' do
- it 'keeps all tags' do
- expect(Projects::ContainerRepository::DeleteTagsService)
- .not_to receive(:new)
- expect_no_caching unless supports_caching
-
- subject
- end
-
- it { is_expected.to eq(status: :error, message: 'invalid regex') }
-
- it 'calls error tracking service' do
- expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original
-
- subject
- end
- end
-
- context 'when name_regex_delete is invalid' do
- let(:params) { { 'name_regex_delete' => '*test*' } }
-
- it_behaves_like 'handling an invalid regex'
- end
-
- context 'when name_regex is invalid' do
- let(:params) { { 'name_regex' => '*test*' } }
-
- it_behaves_like 'handling an invalid regex'
- end
-
- context 'when name_regex_keep is invalid' do
- let(:params) { { 'name_regex_keep' => '*test*' } }
-
- it_behaves_like 'handling an invalid regex'
- end
- end
-end
-
RSpec.shared_examples 'when regex matching everything is specified' do
|service_response_extra: {}, supports_caching: false, delete_expectations:|
let(:params) do
@@ -227,20 +179,6 @@ RSpec.shared_examples 'when running a container_expiration_policy' do
is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra))
end
end
-
- context 'without container_expiration_policy param' do
- let(:params) do
- {
- 'name_regex_delete' => '.*',
- 'keep_n' => 1,
- 'older_than' => '1 day'
- }
- end
-
- it 'fails' do
- is_expected.to eq(status: :error, message: 'access denied')
- end
- end
end
RSpec.shared_examples 'not removing anything' do |service_response_extra: {}, supports_caching: false|
diff --git a/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb b/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb
new file mode 100644
index 00000000000..e725de8ad31
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'does not exceed the issuable size limit' do
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:user3) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ project.add_maintainer(user1)
+ project.add_maintainer(user2)
+ project.add_maintainer(user3)
+ end
+
+ context 'when feature flag is turned on' do
+ context "when the number of users of issuable does exceed the limit" do
+ before do
+ stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 2)
+ end
+
+ it 'will not add more than the allowed number of users' do
+ allow_next_instance_of(update_service) do |service|
+ expect(service).not_to receive(:execute)
+ end
+
+ note = described_class.new(project, user, opts.merge(
+ note: note_text,
+ noteable_type: noteable_type,
+ noteable_id: issuable.id,
+ confidential: false
+ )).execute
+
+ expect(note.errors[:validation]).to match_array([validation_message])
+ end
+ end
+
+ context "when the number of users does not exceed the limit" do
+ before do
+ stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 6)
+ end
+
+ it 'calls execute and does not return an error' do
+ allow_next_instance_of(update_service) do |service|
+ expect(service).to receive(:execute).and_call_original
+ end
+
+ note = described_class.new(project, user, opts.merge(
+ note: note_text,
+ noteable_type: noteable_type,
+ noteable_id: issuable.id,
+ confidential: false
+ )).execute
+
+ expect(note.errors[:validation]).to be_empty
+ end
+ end
+ end
+
+ context 'when feature flag is off' do
+ before do
+ stub_feature_flags(feature_flag_hash)
+ end
+
+ context "when the number of users of issuable does exceed the limit" do
+ before do
+ stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 2)
+ end
+
+ it 'will not add more than the allowed number of users' do
+ allow_next_instance_of(MergeRequests::UpdateService) do |service|
+ expect(service).to receive(:execute).and_call_original
+ end
+
+ note = described_class.new(project, user, opts.merge(
+ note: note_text,
+ noteable_type: 'MergeRequest',
+ noteable_id: issuable.id,
+ confidential: false
+ )).execute
+
+ expect(note.errors[:validation]).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
index 017e6274cb0..59e641e2af6 100644
--- a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
+++ b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
@@ -2,18 +2,13 @@
RSpec.shared_examples 'GET resource access tokens available' do
let_it_be(:active_resource_access_token) { create(:personal_access_token, user: bot_user) }
- let_it_be(:inactive_resource_access_token) { create(:personal_access_token, :revoked, user: bot_user) }
it 'retrieves active resource access tokens' do
subject
- expect(assigns(:active_resource_access_tokens)).to contain_exactly(active_resource_access_token)
- end
-
- it 'retrieves inactive resource access tokens' do
- subject
-
- expect(assigns(:inactive_resource_access_tokens)).to contain_exactly(inactive_resource_access_token)
+ token_entities = assigns(:active_resource_access_tokens)
+ expect(token_entities.length).to eq(1)
+ expect(token_entities[0][:name]).to eq(active_resource_access_token.name)
end
it 'lists all available scopes' do
@@ -21,15 +16,6 @@ RSpec.shared_examples 'GET resource access tokens available' do
expect(assigns(:scopes)).to eq(Gitlab::Auth.resource_bot_scopes)
end
-
- it 'retrieves newly created personal access token value' do
- token_value = 'random-value'
- allow(PersonalAccessToken).to receive(:redis_getdel).with("#{user.id}:#{resource.id}").and_return(token_value)
-
- subject
-
- expect(assigns(:new_resource_access_token)).to eq(token_value)
- end
end
RSpec.shared_examples 'POST resource access tokens available' do
@@ -37,10 +23,13 @@ RSpec.shared_examples 'POST resource access tokens available' do
PersonalAccessToken.order(:created_at).last
end
- it 'returns success message' do
+ it 'renders JSON with a token' do
subject
- expect(flash[:notice]).to match('Your new access token has been created.')
+ parsed_body = Gitlab::Json.parse(response.body)
+ expect(parsed_body['new_token']).not_to be_blank
+ expect(parsed_body['errors']).to be_blank
+ expect(response).to have_gitlab_http_status(:success)
end
it 'creates resource access token' do
@@ -59,12 +48,6 @@ RSpec.shared_examples 'POST resource access tokens available' do
expect(created_token.user).to be_project_bot
end
- it 'stores newly created token redis store' do
- expect(PersonalAccessToken).to receive(:redis_store!)
-
- subject
- end
-
it { expect { subject }.to change { User.count }.by(1) }
it { expect { subject }.to change { PersonalAccessToken.count }.by(1) }
@@ -87,10 +70,13 @@ RSpec.shared_examples 'POST resource access tokens available' do
expect { subject }.not_to change { User.count }
end
- it 'shows a failure alert' do
+ it 'renders JSON with an error' do
subject
- expect(flash[:alert]).to match("Failed to create new access token: Failed!")
+ parsed_body = Gitlab::Json.parse(response.body)
+ expect(parsed_body['new_token']).to be_blank
+ expect(parsed_body['errors']).to contain_exactly('Failed!')
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
end
diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
index de7032450a5..14a83d2889b 100644
--- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
@@ -24,7 +24,7 @@ RSpec.shared_examples 'Debian packages upload request' do |status, body = nil|
if status == :created
it 'creates package files', :aggregate_failures do
expect(::Packages::Debian::FindOrCreateIncomingService).to receive(:new).with(container, user).and_call_original
- expect(::Packages::Debian::CreatePackageFileService).to receive(:new).with(be_a(Packages::Package), be_an(Hash)).and_call_original
+ expect(::Packages::Debian::CreatePackageFileService).to receive(:new).with(package: be_a(Packages::Package), current_user: be_an(User), params: be_an(Hash)).and_call_original
if file_name.end_with? '.changes'
expect(::Packages::Debian::ProcessChangesWorker).to receive(:perform_async)
diff --git a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
index 06ed0448b50..8bf6b162508 100644
--- a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb
@@ -247,6 +247,15 @@ RSpec.shared_examples 'handling helm chart index requests' do
end
end
+ context 'with access to package registry for everyone' do
+ before do
+ project.update!(visibility: Gitlab::VisibilityLevel::PRIVATE)
+ project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC)
+ end
+
+ it_behaves_like 'process helm service index request', :anonymous, :success
+ end
+
context 'when an invalid token is passed' do
let(:headers) { basic_auth_header(user.username, 'wrong') }
diff --git a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb
index 013945bd578..d666a754d9f 100644
--- a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb
@@ -134,6 +134,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix|
context 'the hook is backed-off' do
before do
+ WebHook::FAILURE_THRESHOLD.times { hook.backoff! }
hook.backoff!
end
diff --git a/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb b/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb
index a3378d4619b..1045a92f332 100644
--- a/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb
@@ -20,7 +20,7 @@ RSpec.shared_examples 'issuable update endpoint' do
end
it 'updates the issuable with labels param as array' do
- stub_const("Gitlab::QueryLimiting::Transaction::THRESHOLD", 110)
+ allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(110)
params = { labels: ['label1', 'label2', 'foo, bar', '&,?'] }
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index b651ffc8996..85ac2b5e1ea 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -260,7 +260,11 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
project.send("add_#{user_role}", user) if user_role
project.update!(visibility: visibility.to_s)
package.update!(name: package_name) unless package_name == 'non-existing-package'
- allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
+ if scope == :instance
+ allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
+ else
+ allow_fetch_cascade_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
+ end
end
example_name = "#{params[:expected_result]} metadata request"
diff --git a/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb
index 86b6975bf9f..1d79a61fbb0 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb
@@ -14,6 +14,7 @@ RSpec.shared_examples 'accept package tags request' do |status:|
before do
allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: false)
+ allow_fetch_cascade_application_setting(attribute: "npm_package_requests_forwarding", return_value: false)
end
context 'with valid package name' do
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
index f411b5699a9..11e19d8d067 100644
--- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -291,7 +291,7 @@ RSpec.shared_examples 'pypi simple API endpoint' do
end
before do
- allow_fetch_application_setting(attribute: "pypi_package_requests_forwarding", return_value: forward)
+ allow_fetch_cascade_application_setting(attribute: "pypi_package_requests_forwarding", return_value: forward)
end
it_behaves_like params[:shared_examples_name], :reporter, params[:expected_status]
diff --git a/spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_token_examples.rb b/spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_token_examples.rb
index 379327be0db..e2e2658d803 100644
--- a/spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_token_examples.rb
+++ b/spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_token_examples.rb
@@ -18,6 +18,7 @@ RSpec.shared_examples 'requires valid Google Oauth2 token' do
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
allow(client).to receive(:validate_token).and_return(true)
allow(client).to receive(:list_projects).and_return(mock_gcp_projects) if mock_gcp_projects
+ allow(client).to receive(:create_cloudsql_instance)
end
allow_next_instance_of(BranchesFinder) do |finder|
diff --git a/spec/support/shared_examples/serializers/issuable_current_user_properties_shared_examples.rb b/spec/support/shared_examples/serializers/issuable_current_user_properties_shared_examples.rb
new file mode 100644
index 00000000000..6c285bfba91
--- /dev/null
+++ b/spec/support/shared_examples/serializers/issuable_current_user_properties_shared_examples.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'issuable entity current_user properties' do
+ describe 'can_create_confidential_note' do
+ subject do
+ described_class.new(resource, request: request)
+ .as_json[:current_user][:can_create_confidential_note]
+ end
+
+ context 'when user can create confidential notes' do
+ before do
+ resource.resource_parent.add_reporter(user)
+ end
+
+ it { is_expected.to be(true) }
+ end
+
+ context 'when user cannot create confidential notes' do
+ it { is_expected.to eq(false) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/base_rpm_service_shared_examples.rb b/spec/support/shared_examples/services/base_rpm_service_shared_examples.rb
new file mode 100644
index 00000000000..c9520852a5b
--- /dev/null
+++ b/spec/support/shared_examples/services/base_rpm_service_shared_examples.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'handling rpm xml file' do
+ include_context 'with rpm package data'
+
+ let(:xml) { nil }
+ let(:data) { {} }
+
+ context 'when generate empty xml' do
+ it 'generate expected xml' do
+ expect(subject).to eq(empty_xml)
+ end
+ end
+
+ context 'when updating existing xml' do
+ let(:xml) { empty_xml }
+ let(:data) { xml_update_params }
+
+ shared_examples 'changing root tag attribute' do
+ it "increment previous 'packages' value by 1" do
+ previous_value = Nokogiri::XML(xml).at(described_class::ROOT_TAG).attributes["packages"].value.to_i
+ new_value = Nokogiri::XML(subject).at(described_class::ROOT_TAG).attributes["packages"].value.to_i
+
+ expect(previous_value + 1).to eq(new_value)
+ end
+ end
+
+ it 'generate valid xml add expected xml node to existing xml' do
+ # Have one root attribute
+ result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
+ expect(result.children.count).to eq(1)
+
+ # Root node has 1 child with generated node
+ expect(result.xpath("//#{described_class::ROOT_TAG}/package").count).to eq(1)
+ end
+
+ context 'when empty xml' do
+ it_behaves_like 'changing root tag attribute'
+ end
+
+ context 'when xml has children' do
+ let(:xml) { described_class.new(xml: empty_xml, data: data).execute }
+
+ it 'has children nodes' do
+ result = Nokogiri::XML::Document.parse(xml).remove_namespaces!
+ expect(result.children.count).to be > 0
+ end
+
+ it_behaves_like 'changing root tag attribute'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/merge_request_shared_examples.rb b/spec/support/shared_examples/services/merge_request_shared_examples.rb
index b3ba0a1be93..cfd75d3cfcd 100644
--- a/spec/support/shared_examples/services/merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/services/merge_request_shared_examples.rb
@@ -19,29 +19,13 @@ RSpec.shared_examples 'reviewer_ids filter' do
let(:reviewer2) { create(:user) }
context 'when the current user can admin the merge_request' do
- context 'when merge_request_reviewer feature is enabled' do
+ context 'with a reviewer who can read the merge_request' do
before do
- stub_feature_flags(merge_request_reviewer: true)
+ project.add_developer(reviewer1)
end
- context 'with a reviewer who can read the merge_request' do
- before do
- project.add_developer(reviewer1)
- end
-
- it 'contains reviewers who can read the merge_request' do
- expect(execute.reviewers).to contain_exactly(reviewer1)
- end
- end
- end
-
- context 'when merge_request_reviewer feature is disabled' do
- before do
- stub_feature_flags(merge_request_reviewer: false)
- end
-
- it 'contains no reviewers' do
- expect(execute.reviewers).to eq []
+ it 'contains reviewers who can read the merge_request' do
+ expect(execute.reviewers).to contain_exactly(reviewer1)
end
end
end
diff --git a/spec/support/shared_examples/services/reviewers_change_trigger_shared_examples.rb b/spec/support/shared_examples/services/reviewers_change_trigger_shared_examples.rb
new file mode 100644
index 00000000000..cc37ea0c6f0
--- /dev/null
+++ b/spec/support/shared_examples/services/reviewers_change_trigger_shared_examples.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'triggers GraphQL subscription mergeRequestReviewersUpdated' do
+ specify do
+ expect(GraphqlTriggers).to receive(:merge_request_reviewers_updated).with(merge_request)
+
+ action
+ end
+end
+
+RSpec.shared_examples 'does not trigger GraphQL subscription mergeRequestReviewersUpdated' do
+ specify do
+ expect(GraphqlTriggers).not_to receive(:merge_request_reviewers_updated)
+
+ action
+ end
+end
diff --git a/spec/support/view_component.rb b/spec/support/view_component.rb
index 9166a06fc8c..912bfda6d33 100644
--- a/spec/support/view_component.rb
+++ b/spec/support/view_component.rb
@@ -4,4 +4,11 @@ require 'view_component/test_helpers'
RSpec.configure do |config|
config.include ViewComponent::TestHelpers, type: :component
config.include Capybara::RSpecMatchers, type: :component
+ config.include Devise::Test::ControllerHelpers, type: :component
+
+ config.before(:each, type: :component) do
+ @request = controller.request
+ end
+
+ config.include_context 'when page has no HTML escapes', type: :component
end