summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/capybara.rb1
-rw-r--r--spec/support/database_cleaner.rb14
-rw-r--r--spec/support/helpers/admin_mode_helpers.rb2
-rw-r--r--spec/support/helpers/graphql_helpers.rb4
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb87
-rw-r--r--spec/support/helpers/live_debugger.rb10
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb15
-rw-r--r--spec/support/helpers/position_tracer_helpers.rb2
-rw-r--r--spec/support/helpers/sentry_client_helpers.rb14
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb18
-rw-r--r--spec/support/helpers/stub_object_storage.rb7
-rw-r--r--spec/support/import_export/configuration_helper.rb41
-rw-r--r--spec/support/matchers/graphql_matchers.rb19
-rw-r--r--spec/support/matchers/navigation_matcher.rb2
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb3
-rw-r--r--spec/support/shared_examples/controllers/environments_controller_shared_examples.rb67
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/diff_file_collections.rb42
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/graphql/notes_creation_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb182
-rw-r--r--spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/lib/sentry/client_shared_examples.rb56
-rw-r--r--spec/support/shared_examples/mail_room_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/mentionable_shared_examples.rb150
-rw-r--r--spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb4
-rw-r--r--spec/support/shared_examples/models/chat_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_cleanup_worker_base_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/models/user_mentions_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/policies/project_policy_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb36
-rw-r--r--spec/support/shared_examples/services/base_helm_service_shared_examples.rb14
-rw-r--r--spec/support/sidekiq.rb26
-rw-r--r--spec/support/sidekiq_middleware.rb27
-rw-r--r--spec/support/webmock.rb6
35 files changed, 857 insertions, 117 deletions
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 2bd4750dffa..5ae042e4148 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -16,6 +16,7 @@ JSConsoleError = Class.new(StandardError)
JS_CONSOLE_FILTER = Regexp.union([
'"[HMR] Waiting for update signal from WDS..."',
'"[WDS] Hot Module Replacement enabled."',
+ '"[WDS] Live Reloading enabled."',
"Download the Vue Devtools extension"
])
diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb
index 25260a56578..aaf408f6143 100644
--- a/spec/support/database_cleaner.rb
+++ b/spec/support/database_cleaner.rb
@@ -1,21 +1,7 @@
# frozen_string_literal: true
-require 'database_cleaner/active_record/deletion'
require_relative 'db_cleaner'
-module FakeInformationSchema
- # Work around a bug in DatabaseCleaner when using the deletion strategy:
- # https://github.com/DatabaseCleaner/database_cleaner/issues/347
- #
- # On MySQL, if the information schema is said to exist, we use an inaccurate
- # row count leading to some tables not being cleaned when they should
- def information_schema_exists?(_connection)
- false
- end
-end
-
-DatabaseCleaner::ActiveRecord::Deletion.prepend(FakeInformationSchema)
-
RSpec.configure do |config|
include DbCleaner
diff --git a/spec/support/helpers/admin_mode_helpers.rb b/spec/support/helpers/admin_mode_helpers.rb
index de8ffe40536..e995a7d4f5e 100644
--- a/spec/support/helpers/admin_mode_helpers.rb
+++ b/spec/support/helpers/admin_mode_helpers.rb
@@ -3,7 +3,7 @@
# Helper for enabling admin mode in tests
module AdminModeHelper
- # Users are logged in by default in user mode and have to switch to admin
+ # Administrators are logged in by default in user mode and have to switch to admin
# mode for accessing any administrative functionality. This helper lets a user
# be in admin mode without requiring a second authentication step (provided
# the user is an admin)
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 80a3f7df05f..e21b3aea3da 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -297,6 +297,10 @@ module GraphqlHelpers
extract_attribute ? item['node'][extract_attribute] : item['node']
end
end
+
+ def global_id_of(model)
+ model.to_global_id.to_s
+ end
end
# This warms our schema, doing this as part of loading the helpers to avoid
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 677aef57661..ad4ae93a027 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -84,7 +84,7 @@ module KubernetesHelpers
end
logs_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods/#{pod_name}" \
- "/log?#{container_query_param}tailLines=#{Clusters::Platforms::Kubernetes::LOGS_LIMIT}"
+ "/log?#{container_query_param}tailLines=#{Clusters::Platforms::Kubernetes::LOGS_LIMIT}&timestamps=true"
if status
response = { status: status }
@@ -194,6 +194,11 @@ module KubernetesHelpers
.to_return(kube_response({}))
end
+ def stub_kubeclient_put_cluster_role_binding(api_url, name)
+ WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/#{name}")
+ .to_return(kube_response({}))
+ end
+
def stub_kubeclient_get_role_binding(api_url, name, namespace: 'default')
WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}")
.to_return(kube_response({}))
@@ -219,11 +224,21 @@ module KubernetesHelpers
.to_return(kube_response({}))
end
+ def stub_kubeclient_get_namespaces(api_url)
+ WebMock.stub_request(:get, api_url + '/api/v1/namespaces')
+ .to_return(kube_response(kube_v1_namespace_list_body))
+ end
+
def stub_kubeclient_get_namespace(api_url, namespace: 'default')
WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}")
.to_return(kube_response({}))
end
+ def stub_kubeclient_put_cluster_role(api_url, name)
+ WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterroles/#{name}")
+ .to_return(kube_response({}))
+ end
+
def stub_kubeclient_put_role(api_url, name, namespace: 'default')
WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{name}")
.to_return(kube_response({}))
@@ -257,6 +272,20 @@ module KubernetesHelpers
}
end
+ def kube_v1_namespace_list_body
+ {
+ "kind" => "NamespaceList",
+ "apiVersion" => "v1",
+ "items" => [
+ {
+ "metadata" => {
+ "name" => "knative-serving"
+ }
+ }
+ ]
+ }
+ end
+
def kube_v1beta1_discovery_body
{
"kind" => "APIResourceList",
@@ -302,7 +331,7 @@ module KubernetesHelpers
end
def kube_logs_body
- "Log 1\nLog 2\nLog 3"
+ "2019-12-13T14:04:22.123456Z Log 1\n2019-12-13T14:04:23.123456Z Log 2\n2019-12-13T14:04:24.123456Z Log 3"
end
def kube_deployments_body
@@ -322,7 +351,7 @@ module KubernetesHelpers
def kube_knative_services_body(**options)
{
"kind" => "List",
- "items" => [knative_07_service(options)]
+ "items" => [knative_09_service(options)]
}
end
@@ -511,6 +540,58 @@ module KubernetesHelpers
end
# noinspection RubyStringKeysInHashInspection
+ def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production')
+ { "apiVersion" => "serving.knative.dev/v1alpha1",
+ "kind" => "Service",
+ "metadata" =>
+ { "annotations" =>
+ { "serving.knative.dev/creator" => "system:serviceaccount:#{namespace}:#{namespace}-service-account",
+ "serving.knative.dev/lastModifier" => "system:serviceaccount:#{namespace}:#{namespace}-service-account" },
+ "creationTimestamp" => "2019-10-22T21:19:13Z",
+ "generation" => 1,
+ "labels" => { "service" => name },
+ "name" => name,
+ "namespace" => namespace,
+ "resourceVersion" => "289726",
+ "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}",
+ "uid" => "988349fa-f511-11e9-9ea1-42010a80005e" },
+ "spec" => {
+ "template" => {
+ "metadata" => {
+ "annotations" => { "Description" => description },
+ "creationTimestamp" => "2019-10-22T21:19:12Z",
+ "labels" => { "service" => name }
+ },
+ "spec" => {
+ "containers" => [{
+ "env" =>
+ [{ "name" => "timestamp", "value" => "2019-10-22 21:19:12" }],
+ "image" => "image_name",
+ "name" => "user-container",
+ "resources" => {}
+ }],
+ "timeoutSeconds" => 300
+ }
+ },
+ "traffic" => [{ "latestRevision" => true, "percent" => 100 }]
+ },
+ "status" =>
+ { "address" => { "url" => "http://#{name}.#{namespace}.svc.cluster.local" },
+ "conditions" =>
+ [{ "lastTransitionTime" => "2019-10-22T21:20:15Z", "status" => "True", "type" => "ConfigurationsReady" },
+ { "lastTransitionTime" => "2019-10-22T21:20:15Z", "status" => "True", "type" => "Ready" },
+ { "lastTransitionTime" => "2019-10-22T21:20:15Z", "status" => "True", "type" => "RoutesReady" }],
+ "latestCreatedRevisionName" => "#{name}-92tsj",
+ "latestReadyRevisionName" => "#{name}-92tsj",
+ "observedGeneration" => 1,
+ "traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }],
+ "url" => "http://#{name}.#{namespace}.#{domain}" },
+ "environment_scope" => environment,
+ "cluster_id" => 5,
+ "podcount" => 0 }
+ end
+
+ # noinspection RubyStringKeysInHashInspection
def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production')
{ "apiVersion" => "serving.knative.dev/v1alpha1",
"kind" => "Service",
diff --git a/spec/support/helpers/live_debugger.rb b/spec/support/helpers/live_debugger.rb
index d6091035b59..cdb068760f4 100644
--- a/spec/support/helpers/live_debugger.rb
+++ b/spec/support/helpers/live_debugger.rb
@@ -6,11 +6,17 @@ module LiveDebugger
def live_debug
puts
puts "Current example is paused for live debugging."
- puts "Opening #{current_url} in your default browser..."
+
+ if ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
+ puts "Switch to the Chrome window that was automatically opened to run the test in order to view current page"
+ else
+ puts "Opening #{current_url} in your default browser..."
+ end
+
puts "The current user credentials are: #{@current_user.username} / #{@current_user.password}" if @current_user
puts "Press any key to resume the execution of the example!!"
- `open #{current_url}`
+ `open #{current_url}` if ENV['CHROME_HEADLESS'] !~ /^(false|no|0)$/i
loop until $stdin.getch
diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb
index 0e86b6dfda7..5b425d0964d 100644
--- a/spec/support/helpers/metrics_dashboard_helpers.rb
+++ b/spec/support/helpers/metrics_dashboard_helpers.rb
@@ -19,7 +19,11 @@ module MetricsDashboardHelpers
end
def system_dashboard_path
- Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH
+ Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH
+ end
+
+ def pod_dashboard_path
+ Metrics::Dashboard::PodDashboardService::DASHBOARD_PATH
end
def business_metric_title
@@ -53,6 +57,15 @@ module MetricsDashboardHelpers
it_behaves_like 'valid dashboard service response for schema'
end
+ shared_examples_for 'caches the unprocessed dashboard for subsequent calls' do
+ it do
+ expect(YAML).to receive(:safe_load).once.and_call_original
+
+ described_class.new(*service_params).get_dashboard
+ described_class.new(*service_params).get_dashboard
+ end
+ end
+
shared_examples_for 'valid embedded dashboard service response' do
let(:dashboard_schema) { JSON.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/embedded_dashboard.json')) }
diff --git a/spec/support/helpers/position_tracer_helpers.rb b/spec/support/helpers/position_tracer_helpers.rb
index bbf6e06dd40..7516694d4fe 100644
--- a/spec/support/helpers/position_tracer_helpers.rb
+++ b/spec/support/helpers/position_tracer_helpers.rb
@@ -50,7 +50,7 @@ module PositionTracerHelpers
end
def create_branch(new_name, branch_name)
- CreateBranchService.new(project, current_user).execute(new_name, branch_name)
+ ::Branches::CreateService.new(project, current_user).execute(new_name, branch_name)
end
def create_file(branch_name, file_name, content)
diff --git a/spec/support/helpers/sentry_client_helpers.rb b/spec/support/helpers/sentry_client_helpers.rb
new file mode 100644
index 00000000000..7476b5fb249
--- /dev/null
+++ b/spec/support/helpers/sentry_client_helpers.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module SentryClientHelpers
+ private
+
+ def stub_sentry_request(url, body: {}, status: 200, headers: {})
+ stub_request(:get, url)
+ .to_return(
+ status: status,
+ headers: { 'Content-Type' => 'application/json' }.merge(headers),
+ body: body.to_json
+ )
+ end
+end
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index fe343da7838..ff4b9db8ad9 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -19,24 +19,28 @@ module StubGitlabCalls
end
def stub_ci_pipeline_yaml_file(ci_yaml_content)
- allow_any_instance_of(Repository).to receive(:gitlab_ci_yml_for).and_return(ci_yaml_content)
+ allow_any_instance_of(Repository)
+ .to receive(:gitlab_ci_yml_for)
+ .and_return(ci_yaml_content)
# Ensure we don't hit auto-devops when config not found in repository
unless ci_yaml_content
allow_any_instance_of(Project).to receive(:auto_devops_enabled?).and_return(false)
end
+
+ # Stub the first call to `include:[local: .gitlab-ci.yml]` when
+ # evaluating the CI root config content.
+ if Feature.enabled?(:ci_root_config_content, default_enabled: true)
+ allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
+ .to receive(:content)
+ .and_return(ci_yaml_content)
+ end
end
def stub_pipeline_modified_paths(pipeline, modified_paths)
allow(pipeline).to receive(:modified_paths).and_return(modified_paths)
end
- def stub_repository_ci_yaml_file(sha:, path: '.gitlab-ci.yml')
- allow_any_instance_of(Repository)
- .to receive(:gitlab_ci_yml_for).with(sha, path)
- .and_return(gitlab_ci_yaml)
- end
-
def stub_ci_builds_disabled
allow_any_instance_of(Project).to receive(:builds_enabled?).and_return(false)
end
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index 3f7002b8768..392300a4436 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -56,6 +56,13 @@ module StubObjectStorage
**params)
end
+ def stub_package_file_object_storage(**params)
+ stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
+ uploader: ::Packages::PackageFileUploader,
+ remote_directory: 'packages',
+ **params)
+ end
+
def stub_uploads_object_storage(uploader = described_class, **params)
stub_object_storage_uploader(config: Gitlab.config.uploads.object_store,
uploader: uploader,
diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb
index 122df7f27f0..2e5a99bb8b2 100644
--- a/spec/support/import_export/configuration_helper.rb
+++ b/spec/support/import_export/configuration_helper.rb
@@ -10,21 +10,54 @@ module ConfigurationHelper
end
end
+ def all_relations(tree, tree_path = [])
+ tree.flat_map do |relation_name, relations|
+ relation_path = tree_path + [relation_name]
+ [relation_path] + all_relations(relations, relation_path)
+ end
+ end
+
+ def config_hash(config = Gitlab::ImportExport.config_file)
+ Gitlab::ImportExport::Config.new(config: config).to_h
+ end
+
+ def relation_paths_for(key, config: Gitlab::ImportExport.config_file)
+ # - project is not part of the tree, so it has to be added manually.
+ all_relations({ project: config_hash(config).dig(:tree, key) })
+ end
+
+ def relation_names_for(key, config: Gitlab::ImportExport.config_file)
+ names = names_from_tree(config_hash(config).dig(:tree, key))
+ # Remove duplicated or add missing models
+ # - project is not part of the tree, so it has to be added manually.
+ # - milestone, labels, merge_request have both singular and plural versions in the tree, so remove the duplicates.
+ # - User, Author... Models we do not care about for checking models
+ names.flatten.uniq - %w(milestones labels user author merge_request design) + [key.to_s]
+ end
+
def relation_class_for_name(relation_name)
relation_name = Gitlab::ImportExport::RelationFactory.overrides[relation_name.to_sym] || relation_name
Gitlab::ImportExport::RelationFactory.relation_class(relation_name)
end
- def parsed_attributes(relation_name, attributes)
- excluded_attributes = config_hash['excluded_attributes'][relation_name]
- included_attributes = config_hash['included_attributes'][relation_name]
-
+ def parsed_attributes(relation_name, attributes, config: Gitlab::ImportExport.config_file)
+ import_export_config = config_hash(config)
+ excluded_attributes = import_export_config[:excluded_attributes][relation_name.to_sym]
+ included_attributes = import_export_config[:included_attributes][relation_name.to_sym]
attributes = attributes - JSON[excluded_attributes.to_json] if excluded_attributes
attributes = attributes & JSON[included_attributes.to_json] if included_attributes
attributes
end
+ def prohibited_key?(key)
+ key =~ Gitlab::ImportExport::AttributeCleaner::PROHIBITED_REFERENCES && !permitted_key?(key)
+ end
+
+ def permitted_key?(key)
+ Gitlab::ImportExport::AttributeCleaner::ALLOWED_REFERENCES.include?(key)
+ end
+
def associations_for(safe_model)
safe_model.reflect_on_all_associations.map { |assoc| assoc.name.to_s }
end
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index d735c10f698..dbf457a9200 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -28,6 +28,19 @@ RSpec::Matchers.define :have_graphql_fields do |*expected|
end
end
+RSpec::Matchers.define :include_graphql_fields do |*expected|
+ expected_field_names = expected.map { |name| GraphqlHelpers.fieldnamerize(name) }
+
+ match do |kls|
+ expect(kls.fields.keys).to include(*expected_field_names)
+ end
+
+ failure_message do |kls|
+ missing = expected_field_names - kls.fields.keys
+ "is missing fields: <#{missing.inspect}>" if missing.any?
+ end
+end
+
RSpec::Matchers.define :have_graphql_field do |field_name, args = {}|
match do |kls|
field = kls.fields[GraphqlHelpers.fieldnamerize(field_name)]
@@ -64,6 +77,12 @@ RSpec::Matchers.define :have_graphql_type do |expected|
end
end
+RSpec::Matchers.define :have_non_null_graphql_type do |expected|
+ match do |field|
+ expect(field.type).to eq(!expected.to_graphql)
+ end
+end
+
RSpec::Matchers.define :have_graphql_resolver do |expected|
match do |field|
case expected
diff --git a/spec/support/matchers/navigation_matcher.rb b/spec/support/matchers/navigation_matcher.rb
index ad73c96031e..a0beecbfb2c 100644
--- a/spec/support/matchers/navigation_matcher.rb
+++ b/spec/support/matchers/navigation_matcher.rb
@@ -9,6 +9,6 @@ end
RSpec::Matchers.define :have_active_sub_navigation do |expected|
match do |page|
- expect(page.find('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)')).to have_content(expected)
+ expect(page).to have_css('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)', text: expected)
end
end
diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
index 65398c13d90..480c5a0fda0 100644
--- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -39,12 +39,13 @@ RSpec.shared_context 'ProjectPolicy context' do
update_pipeline create_merge_request_from create_wiki push_code
resolve_note create_container_image update_container_image
create_environment create_deployment update_deployment create_release update_release
+ update_environment
]
end
let(:base_maintainer_permissions) do
%i[
- push_to_delete_protected_branch update_project_snippet update_environment
+ push_to_delete_protected_branch update_project_snippet
admin_project_snippet admin_project_member admin_note admin_wiki admin_project
admin_commit_status admin_build admin_container_image
admin_pipeline admin_environment admin_deployment destroy_release add_cluster
diff --git a/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb b/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb
new file mode 100644
index 00000000000..3540f60bf1b
--- /dev/null
+++ b/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+shared_examples_for 'successful response for #cancel_auto_stop' do
+ include GitlabRoutingHelper
+
+ context 'when request is html' do
+ let(:params) { environment_params(format: :html) }
+
+ it 'redirects to show page' do
+ subject
+
+ expect(response).to redirect_to(environment_path(environment))
+ expect(flash[:notice]).to eq('Auto stop successfully canceled.')
+ end
+
+ it 'expires etag caching' do
+ expect_next_instance_of(Gitlab::EtagCaching::Store) do |etag_caching|
+ expect(etag_caching).to receive(:touch).with(project_environments_path(project, format: :json))
+ end
+
+ subject
+ end
+ end
+
+ context 'when request is js' do
+ let(:params) { environment_params(format: :json) }
+
+ it 'responds as ok' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['message']).to eq('Auto stop successfully canceled.')
+ end
+
+ it 'expires etag caching' do
+ expect_next_instance_of(Gitlab::EtagCaching::Store) do |etag_caching|
+ expect(etag_caching).to receive(:touch).with(project_environments_path(project, format: :json))
+ end
+
+ subject
+ end
+ end
+end
+
+shared_examples_for 'failed response for #cancel_auto_stop' do
+ context 'when request is html' do
+ let(:params) { environment_params(format: :html) }
+
+ it 'redirects to show page' do
+ subject
+
+ expect(response).to redirect_to(environment_path(environment))
+ expect(flash[:alert]).to eq("Failed to cancel auto stop because #{message}.")
+ end
+ end
+
+ context 'when request is js' do
+ let(:params) { environment_params(format: :json) }
+
+ it 'responds as unprocessable entity' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq("Failed to cancel auto stop because #{message}.")
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index c24418b2f90..8962d98218a 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -74,7 +74,7 @@ shared_examples 'handle uploads' do
end
before do
- expect(FileUploader).to receive(:generate_secret).and_return(secret)
+ allow(FileUploader).to receive(:generate_secret).and_return(secret)
UploadService.new(model, jpg, uploader_class).execute
end
@@ -88,6 +88,18 @@ shared_examples 'handle uploads' do
end
end
+ context 'when the upload does not have a MIME type that Rails knows' do
+ let(:po) { fixture_file_upload('spec/fixtures/missing_metadata.po', 'text/plain') }
+
+ it 'falls back to the null type' do
+ UploadService.new(model, po, uploader_class).execute
+
+ get :show, params: params.merge(secret: secret, filename: 'missing_metadata.po')
+
+ expect(response.headers['Content-Type']).to eq('application/octet-stream')
+ end
+ end
+
context "when the model is public" do
before do
model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/support/shared_examples/diff_file_collections.rb b/spec/support/shared_examples/diff_file_collections.rb
index 4c64abd2a97..c8bd137bf84 100644
--- a/spec/support/shared_examples/diff_file_collections.rb
+++ b/spec/support/shared_examples/diff_file_collections.rb
@@ -57,3 +57,45 @@ shared_examples 'unfoldable diff' do
subject.unfold_diff_files([position])
end
end
+
+shared_examples 'cacheable diff collection' do
+ let(:cache) { instance_double(Gitlab::Diff::HighlightCache) }
+
+ before do
+ expect(Gitlab::Diff::HighlightCache).to receive(:new).with(subject) { cache }
+ end
+
+ describe '#write_cache' do
+ it 'calls Gitlab::Diff::HighlightCache#write_if_empty' do
+ expect(cache).to receive(:write_if_empty).once
+
+ subject.write_cache
+ end
+ end
+
+ describe '#clear_cache' do
+ it 'calls Gitlab::Diff::HighlightCache#clear' do
+ expect(cache).to receive(:clear).once
+
+ subject.clear_cache
+ end
+ end
+
+ describe '#cache_key' do
+ it 'calls Gitlab::Diff::HighlightCache#key' do
+ expect(cache).to receive(:key).once
+
+ subject.cache_key
+ end
+ end
+
+ describe '#diff_files' do
+ it 'calls Gitlab::Diff::HighlightCache#decorate' do
+ expect(cache).to receive(:decorate)
+ .with(instance_of(Gitlab::Diff::File))
+ .exactly(cacheable_files_count).times
+
+ subject.diff_files
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
index c0db4cdde72..da966fd2200 100644
--- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -8,11 +8,14 @@ RSpec.shared_examples 'a creatable merge request' do
page.within '.dropdown-menu-user' do
click_link user2.name
end
+
expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user2.name
end
+
click_link 'Assign to me'
+
expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user.name
@@ -22,6 +25,7 @@ RSpec.shared_examples 'a creatable merge request' do
page.within '.issue-milestone' do
click_link milestone.title
end
+
expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
page.within '.js-milestone-select' do
expect(page).to have_content milestone.title
@@ -32,6 +36,7 @@ RSpec.shared_examples 'a creatable merge request' do
click_link label.title
click_link label2.title
end
+
page.within '.js-label-select' do
expect(page).to have_content label.title
end
@@ -58,8 +63,9 @@ RSpec.shared_examples 'a creatable merge request' do
it 'updates the branches when selecting a new target project', :js do
target_project_member = target_project.owner
- CreateBranchService.new(target_project, target_project_member)
- .execute('a-brand-new-branch-to-test', 'master')
+ ::Branches::CreateService.new(target_project, target_project_member)
+ .execute('a-brand-new-branch-to-test', 'master')
+
visit project_new_merge_request_path(source_project)
first('.js-target-project').click
diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
index f2e1a95345b..522211340ea 100644
--- a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
@@ -26,6 +26,7 @@ end
RSpec.shared_examples 'a Note mutation when there are active record validation errors' do |model: Note|
before do
expect_next_instance_of(model) do |note|
+ allow(note).to receive_message_chain(:errors, :empty?).and_return(true)
expect(note).to receive(:valid?).at_least(:once).and_return(false)
expect(note).to receive_message_chain(
:errors,
diff --git a/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb b/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb
new file mode 100644
index 00000000000..134e38833cf
--- /dev/null
+++ b/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb
@@ -0,0 +1,182 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'default whitelist' do
+ it 'sanitizes tags that are not whitelisted' do
+ act = %q{<textarea>no inputs</textarea> and <blink>no blinks</blink>}
+ exp = 'no inputs and no blinks'
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'sanitizes tag attributes' do
+ act = %q{<a href="http://example.com/bar.html" onclick="bar">Text</a>}
+ exp = %q{<a href="http://example.com/bar.html">Text</a>}
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'sanitizes javascript in attributes' do
+ act = %q(<a href="javascript:alert('foo')">Text</a>)
+ exp = '<a>Text</a>'
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'sanitizes mixed-cased javascript in attributes' do
+ act = %q(<a href="javaScript:alert('foo')">Text</a>)
+ exp = '<a>Text</a>'
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'allows whitelisted HTML tags from the user' do
+ exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>"
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'sanitizes `class` attribute on any element' do
+ act = %q{<strong class="foo">Strong</strong>}
+ expect(filter(act).to_html).to eq %q{<strong>Strong</strong>}
+ end
+
+ it 'sanitizes `id` attribute on any element' do
+ act = %q{<em id="foo">Emphasis</em>}
+ expect(filter(act).to_html).to eq %q{<em>Emphasis</em>}
+ end
+end
+
+RSpec.shared_examples 'XSS prevention' do
+ # Adapted from the Sanitize test suite: http://git.io/vczrM
+ protocols = {
+ 'protocol-based JS injection: simple, no spaces' => {
+ input: '<a href="javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: simple, spaces before' => {
+ input: '<a href="javascript :alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: simple, spaces after' => {
+ input: '<a href="javascript: alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: simple, spaces before and after' => {
+ input: '<a href="javascript : alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: preceding colon' => {
+ input: '<a href=":javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: UTF-8 encoding' => {
+ input: '<a href="javascript&#58;">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: long UTF-8 encoding' => {
+ input: '<a href="javascript&#0058;">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: long UTF-8 encoding without semicolons' => {
+ input: '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: hex encoding' => {
+ input: '<a href="javascript&#x3A;">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: long hex encoding' => {
+ input: '<a href="javascript&#x003A;">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: hex encoding without semicolons' => {
+ input: '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: null char' => {
+ input: "<a href=java\0script:alert(\"XSS\")>foo</a>",
+ output: '<a href="java"></a>'
+ },
+
+ 'protocol-based JS injection: invalid URL char' => {
+ input: '<img src=java\script:alert("XSS")>',
+ output: '<img>'
+ },
+
+ 'protocol-based JS injection: Unicode' => {
+ input: %Q(<a href="\u0001java\u0003script:alert('XSS')">foo</a>),
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: spaces and entities' => {
+ input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
+ output: '<a href="">foo</a>'
+ },
+
+ 'protocol whitespace' => {
+ input: '<a href=" http://example.com/"></a>',
+ output: '<a href="http://example.com/"></a>'
+ }
+ }
+
+ protocols.each do |name, data|
+ it "disallows #{name}" do
+ doc = filter(data[:input])
+
+ expect(doc.to_html).to eq data[:output]
+ end
+ end
+
+ it 'disallows data links' do
+ input = '<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">XSS</a>'
+ output = filter(input)
+
+ expect(output.to_html).to eq '<a>XSS</a>'
+ end
+
+ it 'disallows vbscript links' do
+ input = '<a href="vbscript:alert(document.domain)">XSS</a>'
+ output = filter(input)
+
+ expect(output.to_html).to eq '<a>XSS</a>'
+ end
+end
+
+RSpec.shared_examples 'sanitize link' do
+ it 'removes `rel` attribute from `a` elements' do
+ act = %q{<a href="#" rel="nofollow">Link</a>}
+ exp = %q{<a href="#">Link</a>}
+
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'disallows invalid URIs' do
+ expect(Addressable::URI).to receive(:parse).with('foo://example.com')
+ .and_raise(Addressable::URI::InvalidURIError)
+
+ input = '<a href="foo://example.com">Foo</a>'
+ output = filter(input)
+
+ expect(output.to_html).to eq '<a>Foo</a>'
+ end
+
+ it 'allows non-standard anchor schemes' do
+ exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
+ act = filter(exp)
+
+ expect(act.to_html).to eq exp
+ end
+
+ it 'allows relative links' do
+ exp = %q{<a href="foo/bar.md">foo/bar.md</a>}
+ act = filter(exp)
+
+ expect(act.to_html).to eq exp
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb
index f26a8554055..4ef9a9930f7 100644
--- a/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb
@@ -2,7 +2,7 @@
# Shared examples for ProjectTreeRestorer (shared to allow the testing
# of EE-specific features)
-RSpec.shared_examples 'restores project correctly' do |**results|
+RSpec.shared_examples 'restores project successfully' do |**results|
it 'restores the project' do
expect(shared.errors).to be_empty
expect(restored_project_json).to be_truthy
@@ -32,6 +32,10 @@ RSpec.shared_examples 'restores project correctly' do |**results|
it 'does not set params that are excluded from import_export settings' do
expect(project.import_type).to be_nil
- expect(project.creator_id).not_to eq 123
+ expect(project.creator_id).not_to eq 999
+ end
+
+ it 'records exact number of import failures' do
+ expect(project.import_failures.size).to eq(results.fetch(:import_failures, 0))
end
end
diff --git a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
new file mode 100644
index 00000000000..76b71ebd3c5
--- /dev/null
+++ b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+# Requires sentry_api_request and subject to be defined
+RSpec.shared_examples 'calls sentry api' do
+ it 'calls sentry api' do
+ subject
+
+ expect(sentry_api_request).to have_been_requested
+ end
+end
+
+# Requires sentry_api_url and subject to be defined
+RSpec.shared_examples 'no Sentry redirects' do
+ let(:redirect_to) { 'https://redirected.example.com' }
+ let(:other_url) { 'https://other.example.org' }
+
+ let!(:redirected_req_stub) { stub_sentry_request(other_url) }
+
+ let!(:redirect_req_stub) do
+ stub_sentry_request(
+ sentry_api_url,
+ status: 302,
+ headers: { location: redirect_to }
+ )
+ end
+
+ it 'does not follow redirects' do
+ expect { subject }.to raise_exception(Sentry::Client::Error, 'Sentry response status code: 302')
+ expect(redirect_req_stub).to have_been_requested
+ expect(redirected_req_stub).not_to have_been_requested
+ end
+end
+
+RSpec.shared_examples 'maps Sentry exceptions' do
+ exceptions = {
+ Gitlab::HTTP::Error => 'Error when connecting to Sentry',
+ Net::OpenTimeout => 'Connection to Sentry timed out',
+ SocketError => 'Received SocketError when trying to connect to Sentry',
+ OpenSSL::SSL::SSLError => 'Sentry returned invalid SSL data',
+ Errno::ECONNREFUSED => 'Connection refused',
+ StandardError => 'Sentry request failed due to StandardError'
+ }
+
+ exceptions.each do |exception, message|
+ context "#{exception}" do
+ before do
+ stub_request(:get, sentry_request_url).to_raise(exception)
+ end
+
+ it do
+ expect { subject }
+ .to raise_exception(Sentry::Client::Error, message)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/mail_room_shared_examples.rb b/spec/support/shared_examples/mail_room_shared_examples.rb
new file mode 100644
index 00000000000..4cca29250e2
--- /dev/null
+++ b/spec/support/shared_examples/mail_room_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+shared_examples_for 'only truthy if both enabled and address are truthy' do |target_proc|
+ context 'with both enabled and address as truthy values' do
+ it 'is truthy' do
+ stub_config(enabled: true, address: 'localhost')
+
+ expect(target_proc.call).to be_truthy
+ end
+ end
+
+ context 'with address only as truthy' do
+ it 'is falsey' do
+ stub_config(enabled: false, address: 'localhost')
+
+ expect(target_proc.call).to be_falsey
+ end
+ end
+
+ context 'with enabled only as truthy' do
+ it 'is falsey' do
+ stub_config(enabled: true, address: nil)
+
+ expect(target_proc.call).to be_falsey
+ end
+ end
+
+ context 'with neither address nor enabled as truthy' do
+ it 'is falsey' do
+ stub_config(enabled: false, address: nil)
+
+ expect(target_proc.call).to be_falsey
+ end
+ end
+end
diff --git a/spec/support/shared_examples/mentionable_shared_examples.rb b/spec/support/shared_examples/mentionable_shared_examples.rb
index 93a8c4709a6..6efc471ce75 100644
--- a/spec/support/shared_examples/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/mentionable_shared_examples.rb
@@ -195,3 +195,153 @@ shared_examples 'an editable mentionable' do
subject.create_new_cross_references!(author)
end
end
+
+shared_examples_for 'mentions in description' do |mentionable_type|
+ describe 'when store_mentioned_users_to_db feature disabled' do
+ before do
+ stub_feature_flags(store_mentioned_users_to_db: false)
+ mentionable.store_mentions!
+ end
+
+ context 'when mentionable description contains mentions' do
+ let(:user) { create(:user) }
+ let(:mentionable) { create(mentionable_type, description: "#{user.to_reference} some description") }
+
+ it 'stores no mentions' do
+ expect(mentionable.user_mentions.count).to eq 0
+ end
+ end
+ end
+
+ describe 'when store_mentioned_users_to_db feature enabled' do
+ before do
+ stub_feature_flags(store_mentioned_users_to_db: true)
+ mentionable.store_mentions!
+ end
+
+ context 'when mentionable description has no mentions' do
+ let(:mentionable) { create(mentionable_type, description: "just some description") }
+
+ it 'stores no mentions' do
+ expect(mentionable.user_mentions.count).to eq 0
+ end
+ end
+
+ context 'when mentionable description contains mentions' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+
+ let(:mentionable_desc) { "#{user.to_reference} some description #{group.to_reference(full: true)} and @all" }
+ let(:mentionable) { create(mentionable_type, description: mentionable_desc) }
+
+ it 'stores mentions' do
+ add_member(user)
+
+ expect(mentionable.user_mentions.count).to eq 1
+ expect(mentionable.referenced_users).to match_array([user])
+ expect(mentionable.referenced_projects(user)).to match_array([mentionable.project].compact) # epic.project is nil, and we want empty []
+ expect(mentionable.referenced_groups(user)).to match_array([group])
+ end
+ end
+ end
+end
+
+shared_examples_for 'mentions in notes' do |mentionable_type|
+ context 'when mentionable notes contain mentions' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:note_desc) { "#{user.to_reference} and #{group.to_reference(full: true)} and @all" }
+ let!(:mentionable) { note.noteable }
+
+ before do
+ note.update(note: note_desc)
+ note.store_mentions!
+ add_member(user)
+ end
+
+ it 'returns all mentionable mentions' do
+ expect(mentionable.user_mentions.count).to eq 1
+ expect(mentionable.referenced_users).to eq [user]
+ expect(mentionable.referenced_projects(user)).to eq [mentionable.project].compact # epic.project is nil, and we want empty []
+ expect(mentionable.referenced_groups(user)).to eq [group]
+ end
+ end
+end
+
+shared_examples_for 'load mentions from DB' do |mentionable_type|
+ context 'load stored mentions' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:mentioned_user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:note_desc) { "#{mentioned_user.to_reference} and #{group.to_reference(full: true)} and @all" }
+
+ before do
+ note.update(note: note_desc)
+ note.store_mentions!
+ add_member(user)
+ end
+
+ context 'when stored user mention contains ids of inexistent records' do
+ before do
+ user_mention = note.send(:model_user_mention)
+ mention_ids = {
+ mentioned_users_ids: user_mention.mentioned_users_ids.to_a << User.maximum(:id).to_i.succ,
+ mentioned_projects_ids: user_mention.mentioned_projects_ids.to_a << Project.maximum(:id).to_i.succ,
+ mentioned_groups_ids: user_mention.mentioned_groups_ids.to_a << Group.maximum(:id).to_i.succ
+ }
+ user_mention.update(mention_ids)
+ end
+
+ it 'filters out inexistent mentions' do
+ expect(mentionable.referenced_users).to match_array([mentioned_user])
+ expect(mentionable.referenced_projects(user)).to match_array([mentionable.project].compact) # epic.project is nil, and we want empty []
+ expect(mentionable.referenced_groups(user)).to match_array([group])
+ end
+ end
+
+ context 'when private projects and groups are mentioned' do
+ let(:mega_user) { create(:user) }
+ let(:private_project) { create(:project, :private) }
+ let(:project_member) { create(:project_member, user: create(:user), project: private_project) }
+ let(:private_group) { create(:group, :private) }
+ let(:group_member) { create(:group_member, user: create(:user), group: private_group) }
+
+ before do
+ user_mention = note.send(:model_user_mention)
+ mention_ids = {
+ mentioned_projects_ids: user_mention.mentioned_projects_ids.to_a << private_project.id,
+ mentioned_groups_ids: user_mention.mentioned_groups_ids.to_a << private_group.id
+ }
+ user_mention.update(mention_ids)
+
+ add_member(mega_user)
+ private_project.add_developer(mega_user)
+ private_group.add_developer(mega_user)
+ end
+
+ context 'when user has no access to some mentions' do
+ it 'filters out inaccessible mentions' do
+ expect(mentionable.referenced_projects(user)).to match_array([mentionable.project].compact) # epic.project is nil, and we want empty []
+ expect(mentionable.referenced_groups(user)).to match_array([group])
+ end
+ end
+
+ context 'when user has access to all mentions' do
+ it 'returns all mentions' do
+ expect(mentionable.referenced_projects(mega_user)).to match_array([mentionable.project, private_project].compact) # epic.project is nil, and we want empty []
+ expect(mentionable.referenced_groups(mega_user)).to match_array([group, private_group])
+ end
+ end
+ end
+ end
+end
+
+def add_member(user)
+ issuable_parent = if mentionable.is_a?(Epic)
+ mentionable.group
+ else
+ mentionable.project
+ end
+
+ issuable_parent&.add_developer(user)
+end
diff --git a/spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb b/spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb
index 80120629a32..18d025a4b07 100644
--- a/spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb
+++ b/spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb
@@ -3,6 +3,10 @@
# This pending test can be removed when `single_mr_diff_view` is enabled by default
# disabling the feature flag above is then not needed anymore.
RSpec.shared_examples 'rendering a single diff version' do |attribute|
+ before do
+ stub_feature_flags(diffs_batch_load: false)
+ end
+
pending 'allows editing diff settings single_mr_diff_view is enabled' do
project = create(:project, :repository)
user = project.creator
diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb
index 98bf647a9bc..7936a8eb974 100644
--- a/spec/support/shared_examples/models/chat_service_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb
@@ -80,7 +80,7 @@ shared_examples_for "chat service" do |service_name|
it_behaves_like "triggered #{service_name} service"
- it "specifies the webhook when it is configured" do
+ it "specifies the webhook when it is configured", if: defined?(client) do
expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object)
subject.execute(sample_data)
diff --git a/spec/support/shared_examples/models/cluster_cleanup_worker_base_shared_examples.rb b/spec/support/shared_examples/models/cluster_cleanup_worker_base_shared_examples.rb
new file mode 100644
index 00000000000..66bbd908ea8
--- /dev/null
+++ b/spec/support/shared_examples/models/cluster_cleanup_worker_base_shared_examples.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+shared_examples 'cluster cleanup worker base specs' do
+ it 'transitions to errored if sidekiq retries exhausted' do
+ job = { 'args' => [cluster.id, 0], 'jid' => '123' }
+
+ described_class.sidekiq_retries_exhausted_block.call(job)
+
+ expect(cluster.reload.cleanup_status_name).to eq(:cleanup_errored)
+ end
+end
diff --git a/spec/support/shared_examples/models/user_mentions_shared_examples.rb b/spec/support/shared_examples/models/user_mentions_shared_examples.rb
new file mode 100644
index 00000000000..b94994ea712
--- /dev/null
+++ b/spec/support/shared_examples/models/user_mentions_shared_examples.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+shared_examples_for 'has user mentions' do
+ describe '#has_mentions?' do
+ context 'when no mentions' do
+ it 'returns false' do
+ expect(subject.mentioned_users_ids).to be nil
+ expect(subject.mentioned_projects_ids).to be nil
+ expect(subject.mentioned_groups_ids).to be nil
+ expect(subject.has_mentions?).to be false
+ end
+ end
+
+ context 'when mentioned_users_ids not null' do
+ subject { described_class.new(mentioned_users_ids: [1, 2, 3]) }
+
+ it 'returns true' do
+ expect(subject.has_mentions?).to be true
+ end
+ end
+
+ context 'when mentioned projects' do
+ subject { described_class.new(mentioned_projects_ids: [1, 2, 3]) }
+
+ it 'returns true' do
+ expect(subject.has_mentions?).to be true
+ end
+ end
+
+ context 'when mentioned groups' do
+ subject { described_class.new(mentioned_groups_ids: [1, 2, 3]) }
+
+ it 'returns true' do
+ expect(subject.has_mentions?).to be true
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
index 13b7ade658b..1831fc10628 100644
--- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb
+++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
@@ -4,7 +4,7 @@ RSpec.shared_examples 'archived project policies' do
let(:feature_write_abilities) do
described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature|
described_class.create_update_admin_destroy(feature)
- end + additional_reporter_permissions + additional_maintainer_permissions
+ end + additional_maintainer_permissions
end
let(:other_write_abilities) do
@@ -18,6 +18,7 @@ RSpec.shared_examples 'archived project policies' do
resolve_note
award_emoji
admin_tag
+ admin_issue_link
]
end
@@ -151,6 +152,7 @@ end
RSpec.shared_examples 'project policies as developer' do
context 'abilities for non-public projects' do
let(:project) { create(:project, namespace: owner.namespace) }
+
subject { described_class.new(developer, project) }
it do
diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
deleted file mode 100644
index 3834b8b2b87..00000000000
--- a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-shared_examples 'duplicate quick action' do
- context 'mark issue as duplicate' do
- let(:original_issue) { create(:issue, project: project) }
-
- context 'when the current user can update issues' do
- it 'does not create a note, and marks the issue as a duplicate' do
- add_note("/duplicate ##{original_issue.to_reference}")
-
- expect(page).not_to have_content "/duplicate #{original_issue.to_reference}"
- expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
-
- expect(issue.reload).to be_closed
- end
- end
-
- context 'when the current user cannot update the issue' do
- let(:guest) { create(:user) }
- before do
- project.add_guest(guest)
- gitlab_sign_out
- sign_in(guest)
- visit project_issue_path(project, issue)
- end
-
- it 'does not create a note, and does not mark the issue as a duplicate' do
- add_note("/duplicate ##{original_issue.to_reference}")
-
- expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
-
- expect(issue.reload).to be_open
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb
index fa76b95f768..19f5334b4b2 100644
--- a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb
@@ -11,20 +11,10 @@ shared_examples 'logs kubernetes errors' do
}
end
- let(:logger_hash) do
- error_hash.merge(
- exception: error_name,
- message: error_message,
- backtrace: instance_of(Array)
- )
- end
-
it 'logs into kubernetes.log and Sentry' do
- expect(service.send(:logger)).to receive(:error).with(hash_including(logger_hash))
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
error,
- extra: hash_including(error_hash)
+ hash_including(error_hash)
)
service.execute
diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb
index 246efedc7e5..a6d6d5fc6e1 100644
--- a/spec/support/sidekiq.rb
+++ b/spec/support/sidekiq.rb
@@ -1,31 +1,5 @@
# frozen_string_literal: true
-require 'sidekiq/testing'
-
-# If Sidekiq::Testing.inline! is used, SQL transactions done inside
-# Sidekiq worker are included in the SQL query limit (in a real
-# deployment sidekiq worker is executed separately). To avoid
-# increasing SQL limit counter, the request is marked as whitelisted
-# during Sidekiq block
-class DisableQueryLimit
- def call(worker_instance, msg, queue)
- transaction = Gitlab::QueryLimiting::Transaction.current
-
- if !transaction.respond_to?(:whitelisted) || transaction.whitelisted
- yield
- else
- transaction.whitelisted = true
- yield
- transaction.whitelisted = false
- end
- end
-end
-
-Sidekiq::Testing.server_middleware do |chain|
- chain.add Gitlab::SidekiqStatus::ServerMiddleware
- chain.add DisableQueryLimit
-end
-
RSpec.configure do |config|
config.around(:each, :sidekiq) do |example|
Sidekiq::Worker.clear_all
diff --git a/spec/support/sidekiq_middleware.rb b/spec/support/sidekiq_middleware.rb
new file mode 100644
index 00000000000..f6694713101
--- /dev/null
+++ b/spec/support/sidekiq_middleware.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'sidekiq/testing'
+
+# If Sidekiq::Testing.inline! is used, SQL transactions done inside
+# Sidekiq worker are included in the SQL query limit (in a real
+# deployment sidekiq worker is executed separately). To avoid
+# increasing SQL limit counter, the request is marked as whitelisted
+# during Sidekiq block
+class DisableQueryLimit
+ def call(worker_instance, msg, queue)
+ transaction = Gitlab::QueryLimiting::Transaction.current
+
+ if !transaction.respond_to?(:whitelisted) || transaction.whitelisted
+ yield
+ else
+ transaction.whitelisted = true
+ yield
+ transaction.whitelisted = false
+ end
+ end
+end
+
+Sidekiq::Testing.server_middleware do |chain|
+ chain.add Gitlab::SidekiqStatus::ServerMiddleware
+ chain.add DisableQueryLimit
+end
diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb
index 32b88edc2df..57acc3b63b1 100644
--- a/spec/support/webmock.rb
+++ b/spec/support/webmock.rb
@@ -8,7 +8,11 @@ def webmock_allowed_hosts
if ENV.key?('ELASTIC_URL')
hosts << URI.parse(ENV['ELASTIC_URL']).host
end
- end.uniq
+
+ if Gitlab.config.webpack&.dev_server&.enabled
+ hosts << Gitlab.config.webpack.dev_server.host
+ end
+ end.compact.uniq
end
WebMock.disable_net_connect!(allow_localhost: true, allow: webmock_allowed_hosts)