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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 16:18:24 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 16:18:24 +0300
commit0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch)
tree4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /spec/support
parent744144d28e3e7fddc117924fef88de5d9674fe4c (diff)
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/database/ci_tables.rb22
-rw-r--r--spec/support/database/cross-join-allowlist.yml196
-rw-r--r--spec/support/database/gitlab_schema.rb25
-rw-r--r--spec/support/database/multiple_databases.rb9
-rw-r--r--spec/support/database/prevent_cross_database_modification.rb10
-rw-r--r--spec/support/database/prevent_cross_joins.rb57
-rw-r--r--spec/support/database_cleaner.rb4
-rw-r--r--spec/support/database_load_balancing.rb5
-rw-r--r--spec/support/db_cleaner.rb2
-rw-r--r--spec/support/helpers/bare_repo_operations.rb20
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb31
-rw-r--r--spec/support/helpers/email_helpers.rb4
-rw-r--r--spec/support/helpers/features/members_helpers.rb (renamed from spec/support/helpers/features/members_table_helpers.rb)0
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb9
-rw-r--r--spec/support/helpers/live_debugger.rb2
-rw-r--r--spec/support/helpers/migrations_helpers.rb2
-rw-r--r--spec/support/helpers/reference_parser_helpers.rb5
-rw-r--r--spec/support/helpers/session_helpers.rb26
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb4
-rw-r--r--spec/support/helpers/stub_gitlab_data.rb7
-rw-r--r--spec/support/helpers/test_env.rb4
-rw-r--r--spec/support/services/migrate_to_ghost_user_service_shared_examples.rb31
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb9
-rw-r--r--spec/support/shared_contexts/finders/packages/npm/package_finder_shared_context.rb14
-rw-r--r--spec/support/shared_contexts/graphql/resolvers/runners_resolver_shared_context.rb22
-rw-r--r--spec/support/shared_contexts/issuable/merge_request_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb3
-rw-r--r--spec/support/shared_contexts/pages_zip_with_spoofed_size_shared_context.rb41
-rw-r--r--spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb10
-rw-r--r--spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb17
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/controllers/concerns/integrations_actions_shared_examples.rb59
-rw-r--r--spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/controllers/issuable_anonymous_search_disabled_examples.rb55
-rw-r--r--spec/support/shared_examples/features/atom/issuable_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/features/deploy_token_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb6
-rw-r--r--spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/manage_applications_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/rss_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb124
-rw-r--r--spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/mailers/notify_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/models/concerns/featurable_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/models/concerns/sanitizable_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb56
-rw-r--r--spec/support/shared_examples/models/mentionable_shared_examples.rb36
-rw-r--r--spec/support/shared_examples/namespaces/traversal_scope_examples.rb125
-rw-r--r--spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb202
-rw-r--r--spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/services/incident_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/work_item_base_types_importer.rb10
60 files changed, 1374 insertions, 247 deletions
diff --git a/spec/support/database/ci_tables.rb b/spec/support/database/ci_tables.rb
deleted file mode 100644
index 99fc7ac2501..00000000000
--- a/spec/support/database/ci_tables.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-# This module stores the CI-related database tables which are
-# going to be moved to a separate database.
-module Database
- module CiTables
- def self.include?(name)
- ci_tables.include?(name)
- end
-
- def self.ci_tables
- @@ci_tables ||= Set.new.tap do |tables| # rubocop:disable Style/ClassVars
- tables.merge(Ci::ApplicationRecord.descendants.map(&:table_name).compact)
-
- # It was decided that taggings/tags are best placed with CI
- # https://gitlab.com/gitlab-org/gitlab/-/issues/333413
- tables.add('taggings')
- tables.add('tags')
- end
- end
- end
-end
diff --git a/spec/support/database/cross-join-allowlist.yml b/spec/support/database/cross-join-allowlist.yml
new file mode 100644
index 00000000000..2b4cfc6773a
--- /dev/null
+++ b/spec/support/database/cross-join-allowlist.yml
@@ -0,0 +1,196 @@
+- "./ee/spec/controllers/operations_controller_spec.rb"
+- "./ee/spec/controllers/projects/issues_controller_spec.rb"
+- "./ee/spec/controllers/projects/security/vulnerabilities_controller_spec.rb"
+- "./ee/spec/features/ci/ci_minutes_spec.rb"
+- "./ee/spec/features/merge_request/user_merges_immediately_spec.rb"
+- "./ee/spec/features/merge_request/user_sees_merge_widget_spec.rb"
+- "./ee/spec/features/merge_trains/two_merge_requests_on_train_spec.rb"
+- "./ee/spec/features/merge_trains/user_adds_merge_request_to_merge_train_spec.rb"
+- "./ee/spec/features/merge_trains/user_adds_to_merge_train_when_pipeline_succeeds_spec.rb"
+- "./ee/spec/features/projects/pipelines/pipeline_spec.rb"
+- "./ee/spec/features/projects/settings/auto_rollback_spec.rb"
+- "./ee/spec/features/projects/settings/pipeline_subscriptions_spec.rb"
+- "./ee/spec/features/projects/settings/protected_environments_spec.rb"
+- "./ee/spec/finders/ee/namespaces/projects_finder_spec.rb"
+- "./ee/spec/finders/group_projects_finder_spec.rb"
+- "./ee/spec/finders/security/findings_finder_spec.rb"
+- "./ee/spec/graphql/ee/resolvers/namespace_projects_resolver_spec.rb"
+- "./ee/spec/lib/analytics/devops_adoption/snapshot_calculator_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/migrate_security_scans_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/populate_latest_pipeline_ids_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/populate_resolved_on_default_branch_column_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/populate_uuids_for_security_findings_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/populate_vulnerability_feedback_pipeline_id_spec.rb"
+- "./ee/spec/lib/ee/gitlab/usage_data_spec.rb"
+- "./ee/spec/migrations/schedule_populate_resolved_on_default_branch_column_spec.rb"
+- "./ee/spec/models/ci/build_spec.rb"
+- "./ee/spec/models/ci/minutes/project_monthly_usage_spec.rb"
+- "./ee/spec/models/ci/pipeline_spec.rb"
+- "./ee/spec/models/ee/vulnerability_spec.rb"
+- "./ee/spec/models/merge_request_spec.rb"
+- "./ee/spec/models/project_spec.rb"
+- "./ee/spec/models/security/finding_spec.rb"
+- "./ee/spec/models/security/scan_spec.rb"
+- "./ee/spec/presenters/ci/pipeline_presenter_spec.rb"
+- "./ee/spec/requests/api/ci/minutes_spec.rb"
+- "./ee/spec/requests/api/graphql/ci/minutes/usage_spec.rb"
+- "./ee/spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb"
+- "./ee/spec/requests/api/graphql/mutations/vulnerabilities/create_external_issue_link_spec.rb"
+- "./ee/spec/requests/api/graphql/project/pipeline/security_report_summary_spec.rb"
+- "./ee/spec/requests/api/graphql/vulnerabilities/location_spec.rb"
+- "./ee/spec/requests/api/groups_spec.rb"
+- "./ee/spec/requests/api/namespaces_spec.rb"
+- "./ee/spec/requests/api/vulnerability_findings_spec.rb"
+- "./ee/spec/serializers/dashboard_environment_entity_spec.rb"
+- "./ee/spec/serializers/dashboard_environments_serializer_spec.rb"
+- "./ee/spec/services/auto_merge/add_to_merge_train_when_pipeline_succeeds_service_spec.rb"
+- "./ee/spec/services/ci/create_pipeline_service/runnable_builds_spec.rb"
+- "./ee/spec/services/ci/minutes/additional_packs/change_namespace_service_spec.rb"
+- "./ee/spec/services/ci/minutes/additional_packs/create_service_spec.rb"
+- "./ee/spec/services/ci/minutes/refresh_cached_data_service_spec.rb"
+- "./ee/spec/services/ci/process_pipeline_service_spec.rb"
+- "./ee/spec/services/ci/trigger_downstream_subscription_service_spec.rb"
+- "./ee/spec/services/clear_namespace_shared_runners_minutes_service_spec.rb"
+- "./ee/spec/services/deployments/auto_rollback_service_spec.rb"
+- "./ee/spec/services/ee/ci/job_artifacts/destroy_all_expired_service_spec.rb"
+- "./ee/spec/services/ee/ci/job_artifacts/destroy_batch_service_spec.rb"
+- "./ee/spec/services/ee/issues/build_from_vulnerability_service_spec.rb"
+- "./ee/spec/services/ee/merge_requests/create_pipeline_service_spec.rb"
+- "./ee/spec/services/ee/merge_requests/refresh_service_spec.rb"
+- "./ee/spec/services/security/report_summary_service_spec.rb"
+- "./ee/spec/services/security/vulnerability_counting_service_spec.rb"
+- "./ee/spec/workers/scan_security_report_secrets_worker_spec.rb"
+- "./ee/spec/workers/security/store_scans_worker_spec.rb"
+- "./spec/controllers/admin/runners_controller_spec.rb"
+- "./spec/controllers/groups/runners_controller_spec.rb"
+- "./spec/controllers/groups/settings/ci_cd_controller_spec.rb"
+- "./spec/controllers/projects/logs_controller_spec.rb"
+- "./spec/controllers/projects/merge_requests_controller_spec.rb"
+- "./spec/controllers/projects/runners_controller_spec.rb"
+- "./spec/controllers/projects/serverless/functions_controller_spec.rb"
+- "./spec/controllers/projects/settings/ci_cd_controller_spec.rb"
+- "./spec/features/admin/admin_runners_spec.rb"
+- "./spec/features/groups/settings/ci_cd_spec.rb"
+- "./spec/features/ide/user_opens_merge_request_spec.rb"
+- "./spec/features/merge_request/user_merges_immediately_spec.rb"
+- "./spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb"
+- "./spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb"
+- "./spec/features/merge_request/user_resolves_wip_mr_spec.rb"
+- "./spec/features/merge_request/user_sees_deployment_widget_spec.rb"
+- "./spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb"
+- "./spec/features/merge_request/user_sees_merge_widget_spec.rb"
+- "./spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb"
+- "./spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb"
+- "./spec/features/merge_request/user_sees_pipelines_spec.rb"
+- "./spec/features/project_group_variables_spec.rb"
+- "./spec/features/project_variables_spec.rb"
+- "./spec/features/projects/badges/list_spec.rb"
+- "./spec/features/projects/environments_pod_logs_spec.rb"
+- "./spec/features/projects/infrastructure_registry_spec.rb"
+- "./spec/features/projects/jobs_spec.rb"
+- "./spec/features/projects/package_files_spec.rb"
+- "./spec/features/projects/pipelines/pipeline_spec.rb"
+- "./spec/features/projects/pipelines/pipelines_spec.rb"
+- "./spec/features/projects/serverless/functions_spec.rb"
+- "./spec/features/projects/settings/pipelines_settings_spec.rb"
+- "./spec/features/runners_spec.rb"
+- "./spec/features/security/project/internal_access_spec.rb"
+- "./spec/features/security/project/private_access_spec.rb"
+- "./spec/features/security/project/public_access_spec.rb"
+- "./spec/features/triggers_spec.rb"
+- "./spec/finders/ci/pipelines_finder_spec.rb"
+- "./spec/finders/ci/pipelines_for_merge_request_finder_spec.rb"
+- "./spec/finders/ci/runners_finder_spec.rb"
+- "./spec/finders/clusters/knative_services_finder_spec.rb"
+- "./spec/finders/projects/serverless/functions_finder_spec.rb"
+- "./spec/frontend/fixtures/runner.rb"
+- "./spec/graphql/mutations/ci/runner/delete_spec.rb"
+- "./spec/graphql/resolvers/ci/group_runners_resolver_spec.rb"
+- "./spec/graphql/resolvers/ci/job_token_scope_resolver_spec.rb"
+- "./spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb"
+- "./spec/graphql/types/ci/job_token_scope_type_spec.rb"
+- "./spec/helpers/packages_helper_spec.rb"
+- "./spec/lib/api/entities/package_spec.rb"
+- "./spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb"
+- "./spec/lib/gitlab/prometheus/query_variables_spec.rb"
+- "./spec/mailers/emails/pipelines_spec.rb"
+- "./spec/migrations/cleanup_legacy_artifact_migration_spec.rb"
+- "./spec/migrations/migrate_protected_attribute_to_pending_builds_spec.rb"
+- "./spec/migrations/re_schedule_latest_pipeline_id_population_with_all_security_related_artifact_types_spec.rb"
+- "./spec/migrations/schedule_migrate_security_scans_spec.rb"
+- "./spec/models/ci/build_spec.rb"
+- "./spec/models/ci/job_artifact_spec.rb"
+- "./spec/models/ci/job_token/scope_spec.rb"
+- "./spec/models/ci/pipeline_spec.rb"
+- "./spec/models/ci/runner_spec.rb"
+- "./spec/models/clusters/applications/runner_spec.rb"
+- "./spec/models/deployment_spec.rb"
+- "./spec/models/environment_spec.rb"
+- "./spec/models/merge_request_spec.rb"
+- "./spec/models/project_spec.rb"
+- "./spec/models/user_spec.rb"
+- "./spec/presenters/ci/build_runner_presenter_spec.rb"
+- "./spec/presenters/ci/pipeline_presenter_spec.rb"
+- "./spec/presenters/packages/detail/package_presenter_spec.rb"
+- "./spec/requests/api/ci/pipelines_spec.rb"
+- "./spec/requests/api/ci/runner/jobs_request_post_spec.rb"
+- "./spec/requests/api/ci/runner/runners_post_spec.rb"
+- "./spec/requests/api/ci/runners_spec.rb"
+- "./spec/requests/api/commit_statuses_spec.rb"
+- "./spec/requests/api/graphql/group_query_spec.rb"
+- "./spec/requests/api/graphql/merge_request/merge_request_spec.rb"
+- "./spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb"
+- "./spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb"
+- "./spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb"
+- "./spec/requests/api/graphql/mutations/merge_requests/create_spec.rb"
+- "./spec/requests/api/graphql/packages/composer_spec.rb"
+- "./spec/requests/api/graphql/packages/conan_spec.rb"
+- "./spec/requests/api/graphql/packages/maven_spec.rb"
+- "./spec/requests/api/graphql/packages/nuget_spec.rb"
+- "./spec/requests/api/graphql/packages/package_spec.rb"
+- "./spec/requests/api/graphql/packages/pypi_spec.rb"
+- "./spec/requests/api/graphql/project/merge_request/pipelines_spec.rb"
+- "./spec/requests/api/graphql/project/merge_request_spec.rb"
+- "./spec/requests/api/graphql/project/merge_requests_spec.rb"
+- "./spec/requests/api/graphql/project/pipeline_spec.rb"
+- "./spec/requests/api/merge_requests_spec.rb"
+- "./spec/requests/api/package_files_spec.rb"
+- "./spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb"
+- "./spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb"
+- "./spec/services/ci/create_pipeline_service/needs_spec.rb"
+- "./spec/services/ci/create_pipeline_service_spec.rb"
+- "./spec/services/ci/destroy_pipeline_service_spec.rb"
+- "./spec/services/ci/expire_pipeline_cache_service_spec.rb"
+- "./spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb"
+- "./spec/services/ci/job_artifacts/destroy_associations_service_spec.rb"
+- "./spec/services/ci/job_artifacts/destroy_batch_service_spec.rb"
+- "./spec/services/ci/pipeline_processing/shared_processing_service.rb"
+- "./spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb"
+- "./spec/services/ci/register_job_service_spec.rb"
+- "./spec/services/clusters/applications/prometheus_config_service_spec.rb"
+- "./spec/services/deployments/older_deployments_drop_service_spec.rb"
+- "./spec/services/environments/auto_stop_service_spec.rb"
+- "./spec/services/environments/stop_service_spec.rb"
+- "./spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb"
+- "./spec/services/merge_requests/create_service_spec.rb"
+- "./spec/services/merge_requests/post_merge_service_spec.rb"
+- "./spec/services/merge_requests/refresh_service_spec.rb"
+- "./spec/support/prometheus/additional_metrics_shared_examples.rb"
+- "./spec/support/shared_examples/ci/pipeline_email_shared_examples.rb"
+- "./spec/support/shared_examples/features/packages_shared_examples.rb"
+- "./spec/support/shared_examples/features/search_settings_shared_examples.rb"
+- "./spec/support/shared_examples/features/variable_list_shared_examples.rb"
+- "./spec/support/shared_examples/models/concerns/limitable_shared_examples.rb"
+- "./spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb"
+- "./spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb"
+- "./spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb"
+- "./spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb"
+- "./spec/support/shared_examples/requests/api/status_shared_examples.rb"
+- "./spec/support/shared_examples/requests/graphql_shared_examples.rb"
+- "./spec/support/shared_examples/services/onboarding_progress_shared_examples.rb"
+- "./spec/support/shared_examples/services/packages_shared_examples.rb"
+- "./spec/support/shared_examples/workers/idempotency_shared_examples.rb"
+- "./spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb"
+- "./spec/workers/pipeline_process_worker_spec.rb"
+- "./spec/workers/pipeline_schedule_worker_spec.rb"
diff --git a/spec/support/database/gitlab_schema.rb b/spec/support/database/gitlab_schema.rb
new file mode 100644
index 00000000000..fe05fb998e6
--- /dev/null
+++ b/spec/support/database/gitlab_schema.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# This module gathes information about table to schema mapping
+# to understand table affinity
+module Database
+ module GitlabSchema
+ def self.table_schemas(tables)
+ tables.map { |table| table_schema(table) }.to_set
+ end
+
+ def self.table_schema(name)
+ tables_to_schema[name] || :undefined
+ end
+
+ def self.tables_to_schema
+ @tables_to_schema ||= all_classes_with_schema.to_h do |klass|
+ [klass.table_name, klass.gitlab_schema]
+ end
+ end
+
+ def self.all_classes_with_schema
+ ActiveRecord::Base.descendants.reject(&:abstract_class?).select(&:gitlab_schema?) # rubocop:disable Database/MultipleDatabases
+ end
+ end
+end
diff --git a/spec/support/database/multiple_databases.rb b/spec/support/database/multiple_databases.rb
new file mode 100644
index 00000000000..8ce642a682c
--- /dev/null
+++ b/spec/support/database/multiple_databases.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Database
+ module MultipleDatabases
+ def skip_if_multiple_databases_not_setup
+ skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
+ end
+ end
+end
diff --git a/spec/support/database/prevent_cross_database_modification.rb b/spec/support/database/prevent_cross_database_modification.rb
index 460ee99391b..b4c968e3c41 100644
--- a/spec/support/database/prevent_cross_database_modification.rb
+++ b/spec/support/database/prevent_cross_database_modification.rb
@@ -74,18 +74,20 @@ module Database
return if cross_database_context[:transaction_depth_by_db].values.all?(&:zero?)
- tables = PgQuery.parse(sql).dml_tables
+ parsed_query = PgQuery.parse(sql)
+ tables = sql.downcase.include?(' for update') ? parsed_query.tables : parsed_query.dml_tables
return if tables.empty?
cross_database_context[:modified_tables_by_db][database].merge(tables)
all_tables = cross_database_context[:modified_tables_by_db].values.map(&:to_a).flatten
+ schemas = Database::GitlabSchema.table_schemas(all_tables)
- unless PreventCrossJoins.only_ci_or_only_main?(all_tables)
+ if schemas.many?
raise Database::PreventCrossDatabaseModification::CrossDatabaseModificationAcrossUnsupportedTablesError,
- "Cross-database data modification queries (CI and Main) were detected within " \
- "a transaction '#{all_tables.join(", ")}' discovered"
+ "Cross-database data modification of '#{schemas.to_a.join(", ")}' were detected within " \
+ "a transaction modifying the '#{all_tables.to_a.join(", ")}'"
end
end
end
diff --git a/spec/support/database/prevent_cross_joins.rb b/spec/support/database/prevent_cross_joins.rb
index 789721ccd38..4b78aa9014c 100644
--- a/spec/support/database/prevent_cross_joins.rb
+++ b/spec/support/database/prevent_cross_joins.rb
@@ -11,7 +11,7 @@
#
# class User
# def ci_owned_runners
-# ::Gitlab::Database.allow_cross_joins_across_databases!(url: link-to-issue-url)
+# ::Gitlab::Database.allow_cross_joins_across_databases(url: link-to-issue-url)
#
# ...
# end
@@ -21,33 +21,43 @@ module Database
module PreventCrossJoins
CrossJoinAcrossUnsupportedTablesError = Class.new(StandardError)
+ ALLOW_THREAD_KEY = :allow_cross_joins_across_databases
+
def self.validate_cross_joins!(sql)
- return if Thread.current[:allow_cross_joins_across_databases]
+ return if Thread.current[ALLOW_THREAD_KEY]
+
+ # Allow spec/support/database_cleaner.rb queries to disable/enable triggers for many tables
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/339396
+ return if sql.include?("DISABLE TRIGGER") || sql.include?("ENABLE TRIGGER")
# PgQuery might fail in some cases due to limited nesting:
# https://github.com/pganalyze/pg_query/issues/209
- tables = PgQuery.parse(sql).tables
+ #
+ # Also, we disable GC while parsing because of https://github.com/pganalyze/pg_query/issues/226
+ begin
+ GC.disable
+ tables = PgQuery.parse(sql).tables
+ ensure
+ GC.enable
+ end
- unless only_ci_or_only_main?(tables)
+ schemas = Database::GitlabSchema.table_schemas(tables)
+
+ if schemas.include?(:gitlab_ci) && schemas.include?(:gitlab_main)
+ Thread.current[:has_cross_join_exception] = true
raise CrossJoinAcrossUnsupportedTablesError,
- "Unsupported cross-join across '#{tables.join(", ")}' discovered " \
- "when executing query '#{sql}'"
+ "Unsupported cross-join across '#{tables.join(", ")}' modifying '#{schemas.to_a.join(", ")}' discovered " \
+ "when executing query '#{sql}'. Please refer to https://docs.gitlab.com/ee/development/database/multiple_databases.html#removing-joins-between-ci_-and-non-ci_-tables for details on how to resolve this exception."
end
end
- # Returns true if a set includes only CI tables, or includes only non-CI tables
- def self.only_ci_or_only_main?(tables)
- tables.all? { |table| CiTables.include?(table) } ||
- tables.none? { |table| CiTables.include?(table) }
- end
-
module SpecHelpers
def with_cross_joins_prevented
subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |event|
::Database::PreventCrossJoins.validate_cross_joins!(event.payload[:sql])
end
- Thread.current[:allow_cross_joins_across_databases] = false
+ Thread.current[ALLOW_THREAD_KEY] = false
yield
ensure
@@ -57,8 +67,12 @@ module Database
module GitlabDatabaseMixin
def allow_cross_joins_across_databases(url:)
- Thread.current[:allow_cross_joins_across_databases] = true
- super
+ old_value = Thread.current[ALLOW_THREAD_KEY]
+ Thread.current[ALLOW_THREAD_KEY] = true
+
+ yield
+ ensure
+ Thread.current[ALLOW_THREAD_KEY] = old_value
end
end
end
@@ -67,11 +81,18 @@ end
Gitlab::Database.singleton_class.prepend(
Database::PreventCrossJoins::GitlabDatabaseMixin)
+ALLOW_LIST = Set.new(YAML.load_file(File.join(__dir__, 'cross-join-allowlist.yml'))).freeze
+
RSpec.configure do |config|
config.include(::Database::PreventCrossJoins::SpecHelpers)
- # TODO: remove `:prevent_cross_joins` to enable the check by default
- config.around(:each, :prevent_cross_joins) do |example|
- with_cross_joins_prevented { example.run }
+ config.around do |example|
+ Thread.current[:has_cross_join_exception] = false
+
+ if ALLOW_LIST.include?(example.file_path)
+ example.run
+ else
+ with_cross_joins_prevented { example.run }
+ end
end
end
diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb
index 01bf390d9e9..b31881e3082 100644
--- a/spec/support/database_cleaner.rb
+++ b/spec/support/database_cleaner.rb
@@ -14,7 +14,7 @@ RSpec.configure do |config|
end
config.append_after(:context, :migration) do
- delete_from_all_tables!
+ 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).
# And since:
@@ -61,7 +61,7 @@ RSpec.configure do |config|
example.run
- delete_from_all_tables!
+ delete_from_all_tables!(except: ['work_item_types'])
self.class.use_transactional_tests = true
end
diff --git a/spec/support/database_load_balancing.rb b/spec/support/database_load_balancing.rb
index 03fa7886295..f22c69ea613 100644
--- a/spec/support/database_load_balancing.rb
+++ b/spec/support/database_load_balancing.rb
@@ -4,7 +4,10 @@ RSpec.configure do |config|
config.before(:each, :db_load_balancing) do
allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
- proxy = ::Gitlab::Database::LoadBalancing::ConnectionProxy.new([Gitlab::Database.main.config['host']])
+ config = Gitlab::Database::LoadBalancing::Configuration
+ .new(ActiveRecord::Base, [Gitlab::Database.main.config['host']])
+ lb = ::Gitlab::Database::LoadBalancing::LoadBalancer.new(config)
+ proxy = ::Gitlab::Database::LoadBalancing::ConnectionProxy.new(lb)
allow(ActiveRecord::Base).to receive(:load_balancing_proxy).and_return(proxy)
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 155dc3c17d9..940ff2751d3 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -12,7 +12,7 @@ module DbCleaner
end
def deletion_except_tables
- []
+ ['work_item_types']
end
def setup_database_cleaner
diff --git a/spec/support/helpers/bare_repo_operations.rb b/spec/support/helpers/bare_repo_operations.rb
index 98fa13db6c2..e29e12a15f6 100644
--- a/spec/support/helpers/bare_repo_operations.rb
+++ b/spec/support/helpers/bare_repo_operations.rb
@@ -17,26 +17,6 @@ class BareRepoOperations
commit_id[0]
end
- # Based on https://stackoverflow.com/a/25556917/1856239
- def commit_file(file, dst_path, branch = 'master')
- head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || Gitlab::Git::EMPTY_TREE_ID
-
- execute(['read-tree', '--empty'])
- execute(['read-tree', head_id])
-
- blob_id = execute(['hash-object', '--stdin', '-w']) do |stdin|
- stdin.write(file.read)
- end
-
- execute(['update-index', '--add', '--cacheinfo', '100644', blob_id[0], dst_path])
-
- tree_id = execute(['write-tree'])
-
- commit_id = commit_tree(tree_id[0], "Add #{dst_path}", parent: head_id)
-
- execute(['update-ref', "refs/heads/#{branch}", commit_id])
- end
-
private
def execute(args, allow_failure: false)
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index e48c8125d84..3ec52f8c832 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -23,12 +23,39 @@ module CycleAnalyticsHelpers
end
end
+ def select_event_label(sel)
+ page.within(sel) do
+ find('.dropdown-toggle').click
+ page.find(".dropdown-menu").all(".dropdown-item")[1].click
+ end
+ end
+
+ def fill_in_custom_label_stage_fields
+ index = page.all('[data-testid="value-stream-stage-fields"]').length
+ last_stage = page.all('[data-testid="value-stream-stage-fields"]').last
+
+ within last_stage do
+ find('[name*="custom-stage-name-"]').fill_in with: "Cool custom label stage - name #{index}"
+ select_dropdown_option_by_value "custom-stage-start-event-", :issue_label_added
+ select_dropdown_option_by_value "custom-stage-end-event-", :issue_label_removed
+
+ select_event_label("[data-testid*='custom-stage-start-event-label-']")
+ select_event_label("[data-testid*='custom-stage-end-event-label-']")
+ end
+ end
+
def add_custom_stage_to_form
page.find_button(s_('CreateValueStreamForm|Add another stage')).click
fill_in_custom_stage_fields
end
+ def add_custom_label_stage_to_form
+ page.find_button(s_('CreateValueStreamForm|Add another stage')).click
+
+ fill_in_custom_label_stage_fields
+ end
+
def save_value_stream(custom_value_stream_name)
fill_in 'create-value-stream-name', with: custom_value_stream_name
@@ -44,12 +71,12 @@ module CycleAnalyticsHelpers
save_value_stream(custom_value_stream_name)
end
- def wait_for_stages_to_load(selector = '.js-path-navigation')
+ def wait_for_stages_to_load(selector = '[data-testid="vsa-path-navigation"]')
expect(page).to have_selector selector
wait_for_requests
end
- def select_group(target_group, ready_selector = '.js-path-navigation')
+ def select_group(target_group, ready_selector = '[data-testid="vsa-path-navigation"]')
visit group_analytics_cycle_analytics_path(target_group)
wait_for_stages_to_load(ready_selector)
diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb
index 6df33e68629..d0f6fd466d0 100644
--- a/spec/support/helpers/email_helpers.rb
+++ b/spec/support/helpers/email_helpers.rb
@@ -2,7 +2,7 @@
module EmailHelpers
def sent_to_user(user, recipients: email_recipients)
- recipients.count { |to| to == user.notification_email }
+ recipients.count { |to| to == user.notification_email_or_default }
end
def reset_delivered_emails!
@@ -45,7 +45,7 @@ module EmailHelpers
end
def find_email_for(user)
- ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) }
+ ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email_or_default) }
end
def have_referable_subject(referable, include_project: true, reply: false)
diff --git a/spec/support/helpers/features/members_table_helpers.rb b/spec/support/helpers/features/members_helpers.rb
index 2e86e014a1b..2e86e014a1b 100644
--- a/spec/support/helpers/features/members_table_helpers.rb
+++ b/spec/support/helpers/features/members_helpers.rb
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index 4c90b907d2d..5174c145a93 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -42,9 +42,12 @@ module JavaScriptFixturesHelpers
# Public: Reads a GraphQL query from the filesystem as a string
#
- # query_path - file path to the GraphQL query, relative to `app/assets/javascripts`
- def get_graphql_query_as_string(query_path)
- path = Rails.root / 'app/assets/javascripts' / query_path
+ # query_path - file path to the GraphQL query, relative to `app/assets/javascripts`.
+ # ee - boolean, when true `query_path` will be looked up in `/ee`.
+ def get_graphql_query_as_string(query_path, ee: false)
+ base = (ee ? 'ee/' : '') + 'app/assets/javascripts'
+
+ path = Rails.root / base / query_path
queries = Gitlab::Graphql::Queries.find(path)
if queries.length == 1
queries.first.text(mode: Gitlab.ee? ? :ee : :ce )
diff --git a/spec/support/helpers/live_debugger.rb b/spec/support/helpers/live_debugger.rb
index f4199d518a3..d196a6dc746 100644
--- a/spec/support/helpers/live_debugger.rb
+++ b/spec/support/helpers/live_debugger.rb
@@ -16,7 +16,7 @@ module LiveDebugger
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}` if is_headless_disabled?
+ `open #{current_url}` unless is_headless_disabled?
loop until $stdin.getch
diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb
index ef212938af5..7799e49d4c1 100644
--- a/spec/support/helpers/migrations_helpers.rb
+++ b/spec/support/helpers/migrations_helpers.rb
@@ -30,7 +30,7 @@ module MigrationsHelpers
end
end
- klass.tap { Gitlab::Database::Partitioning::PartitionManager.new.sync_partitions }
+ klass.tap { Gitlab::Database::Partitioning.sync_partitions([klass]) }
end
def migrations_paths
diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb
index a6a7948d9d9..b9796ebbe62 100644
--- a/spec/support/helpers/reference_parser_helpers.rb
+++ b/spec/support/helpers/reference_parser_helpers.rb
@@ -5,9 +5,10 @@ module ReferenceParserHelpers
Nokogiri::HTML.fragment('<a></a>').children[0]
end
- def expect_gathered_references(result, visible, not_visible_count)
+ def expect_gathered_references(result, visible, nodes, visible_nodes)
expect(result[:visible]).to eq(visible)
- expect(result[:not_visible].count).to eq(not_visible_count)
+ expect(result[:nodes]).to eq(nodes)
+ expect(result[:visible_nodes]).to eq(visible_nodes)
end
RSpec.shared_examples 'no project N+1 queries' do
diff --git a/spec/support/helpers/session_helpers.rb b/spec/support/helpers/session_helpers.rb
new file mode 100644
index 00000000000..4ef099a393e
--- /dev/null
+++ b/spec/support/helpers/session_helpers.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module SessionHelpers
+ def expect_single_session_with_authenticated_ttl
+ expect_single_session_with_expiration(Settings.gitlab['session_expire_delay'] * 60)
+ end
+
+ def expect_single_session_with_short_ttl
+ expect_single_session_with_expiration(Settings.gitlab['unauthenticated_session_expire_delay'])
+ end
+
+ def expect_single_session_with_expiration(expiration)
+ session_keys = get_session_keys
+
+ expect(session_keys.size).to eq(1)
+ expect(get_ttl(session_keys.first)).to be_within(5).of(expiration)
+ end
+
+ def get_session_keys
+ Gitlab::Redis::SharedState.with { |redis| redis.scan_each(match: 'session:gitlab:*').to_a }
+ end
+
+ def get_ttl(key)
+ Gitlab::Redis::SharedState.with { |redis| redis.ttl(key) }
+ end
+end
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index 3824ff2b68d..5ab778c11cb 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -18,6 +18,10 @@ module StubGitlabCalls
stub_ci_pipeline_yaml_file(gitlab_ci_yaml)
end
+ def gitlab_ci_yaml
+ File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+ end
+
def stub_ci_pipeline_yaml_file(ci_yaml_content)
allow_any_instance_of(Repository)
.to receive(:gitlab_ci_yml_for)
diff --git a/spec/support/helpers/stub_gitlab_data.rb b/spec/support/helpers/stub_gitlab_data.rb
deleted file mode 100644
index ed518393c03..00000000000
--- a/spec/support/helpers/stub_gitlab_data.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-module StubGitlabData
- def gitlab_ci_yaml
- File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
- end
-end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index aa5fcf222f2..badd4e8212c 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -452,6 +452,10 @@ module TestEnv
example_group
end
+ def seed_db
+ Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.import
+ end
+
private
# These are directories that should be preserved at cleanup time
diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
index 8a88f0335a9..2fbc01a9195 100644
--- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -32,7 +32,7 @@ RSpec.shared_examples "migrating a deleted user's associated records to the ghos
expect(user).to be_blocked
end
- it 'migrates all associated fields to te "Ghost user"' do
+ it 'migrates all associated fields to the "Ghost user"' do
service.execute
migrated_record = record_class.find_by_id(record.id)
@@ -46,40 +46,19 @@ RSpec.shared_examples "migrating a deleted user's associated records to the ghos
context "when #{record_class_name} migration fails and is rolled back" do
before do
expect_any_instance_of(ActiveRecord::Associations::CollectionProxy)
- .to receive(:update_all).and_raise(ActiveRecord::Rollback)
+ .to receive(:update_all).and_raise(ActiveRecord::StatementTimeout)
end
it 'rolls back the user block' do
- service.execute
+ expect { service.execute }.to raise_error(ActiveRecord::StatementTimeout)
expect(user.reload).not_to be_blocked
end
- it "doesn't unblock an previously-blocked user" do
+ it "doesn't unblock a previously-blocked user" do
user.block
- service.execute
-
- expect(user.reload).to be_blocked
- end
- end
-
- context "when #{record_class_name} migration fails with a non-rollback exception" do
- before do
- expect_any_instance_of(ActiveRecord::Associations::CollectionProxy)
- .to receive(:update_all).and_raise(ArgumentError)
- end
-
- it 'rolls back the user block' do
- service.execute rescue nil
-
- expect(user.reload).not_to be_blocked
- end
-
- it "doesn't unblock an previously-blocked user" do
- user.block
-
- service.execute rescue nil
+ expect { service.execute }.to raise_error(ActiveRecord::StatementTimeout)
expect(user.reload).to be_blocked
end
diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb
index 14c6c85cc43..0dc66eeb2ee 100644
--- a/spec/support/shared_contexts/email_shared_context.rb
+++ b/spec/support/shared_contexts/email_shared_context.rb
@@ -18,6 +18,15 @@ RSpec.shared_context :email_shared_context do
end
end
+def email_fixture(path)
+ fixture_file(path).gsub('project_id', project.project_id.to_s)
+end
+
+def service_desk_fixture(path, slug: nil, key: 'mykey')
+ slug ||= project.full_path_slug.to_s
+ fixture_file(path).gsub('project_slug', slug).gsub('project_key', key)
+end
+
RSpec.shared_examples :reply_processing_shared_examples do
context 'when the user could not be found' do
before do
diff --git a/spec/support/shared_contexts/finders/packages/npm/package_finder_shared_context.rb b/spec/support/shared_contexts/finders/packages/npm/package_finder_shared_context.rb
new file mode 100644
index 00000000000..a2cb9d41f45
--- /dev/null
+++ b/spec/support/shared_contexts/finders/packages/npm/package_finder_shared_context.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'last_of_each_version setup context' do
+ let_it_be(:package1) { create(:npm_package, name: 'test', version: '1.2.3', project: project) }
+ let_it_be(:package2) { create(:npm_package, name: 'test2', version: '1.2.3', project: project) }
+
+ let(:package_name) { 'test' }
+ let(:version) { '1.2.3' }
+
+ before do
+ # create a duplicated package without triggering model validation errors
+ package2.update_column(:name, 'test')
+ end
+end
diff --git a/spec/support/shared_contexts/graphql/resolvers/runners_resolver_shared_context.rb b/spec/support/shared_contexts/graphql/resolvers/runners_resolver_shared_context.rb
new file mode 100644
index 00000000000..aa857cfdb70
--- /dev/null
+++ b/spec/support/shared_contexts/graphql/resolvers/runners_resolver_shared_context.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_context 'runners resolver setup' do
+ let_it_be(:user) { create_default(:user, :admin) }
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:subgroup) { create(:group, :public, parent: group) }
+ let_it_be(:project) { create(:project, :public, group: group) }
+
+ let_it_be(:inactive_project_runner) do
+ create(:ci_runner, :project, projects: [project], description: 'inactive project runner', token: 'abcdef', active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner))
+ end
+
+ let_it_be(:offline_project_runner) do
+ create(:ci_runner, :project, projects: [project], description: 'offline project runner', token: 'defghi', contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner))
+ end
+
+ let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token: 'mnopqr', description: 'group runner', contacted_at: 2.seconds.ago) }
+ let_it_be(:subgroup_runner) { create(:ci_runner, :group, groups: [subgroup], token: 'mnopqr', description: 'subgroup runner', contacted_at: 1.second.ago) }
+ let_it_be(:instance_runner) { create(:ci_runner, :instance, description: 'shared runner', token: 'stuvxz', contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) }
+end
diff --git a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
index 2c56411ca4c..b9cde12c537 100644
--- a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
+++ b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
@@ -16,7 +16,7 @@ RSpec.shared_context 'merge request show action' do
assign(:merge_request, merge_request)
assign(:note, note)
assign(:noteable, merge_request)
- assign(:pipelines, [])
+ assign(:number_of_pipelines, 0)
assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, merge_request))
preload_view_requirements(merge_request, note)
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 8ae0885056e..2abc52fce85 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -118,7 +118,8 @@ RSpec.shared_context 'project navbar structure' do
_('Access Tokens'),
_('Repository'),
_('CI/CD'),
- _('Monitor')
+ _('Monitor'),
+ (s_('UsageQuota|Usage Quotas') if Feature.enabled?(:project_storage_ui, default_enabled: :yaml))
]
}
].compact
diff --git a/spec/support/shared_contexts/pages_zip_with_spoofed_size_shared_context.rb b/spec/support/shared_contexts/pages_zip_with_spoofed_size_shared_context.rb
deleted file mode 100644
index 4cec5ab3b74..00000000000
--- a/spec/support/shared_contexts/pages_zip_with_spoofed_size_shared_context.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-# the idea of creating zip archive with spoofed size is borrowed from
-# https://github.com/rubyzip/rubyzip/pull/403/files#diff-118213fb4baa6404a40f89e1147661ebR88
-RSpec.shared_context 'pages zip with spoofed size' do
- let(:real_zip_path) { Tempfile.new(['real', '.zip']).path }
- let(:fake_zip_path) { Tempfile.new(['fake', '.zip']).path }
-
- before do
- full_file_name = 'public/index.html'
- true_size = 500_000
- fake_size = 1
-
- ::Zip::File.open(real_zip_path, ::Zip::File::CREATE) do |zf|
- zf.get_output_stream(full_file_name) do |os|
- os.write 'a' * true_size
- end
- end
-
- compressed_size = nil
- ::Zip::File.open(real_zip_path) do |zf|
- a_entry = zf.find_entry(full_file_name)
- compressed_size = a_entry.compressed_size
- end
-
- true_size_bytes = [compressed_size, true_size, full_file_name.size].pack('LLS')
- fake_size_bytes = [compressed_size, fake_size, full_file_name.size].pack('LLS')
-
- data = File.binread(real_zip_path)
- data.gsub! true_size_bytes, fake_size_bytes
-
- File.open(fake_zip_path, 'wb') do |file|
- file.write data
- end
- end
-
- after do
- File.delete(real_zip_path) if File.exist?(real_zip_path)
- File.delete(fake_zip_path) if File.exist?(fake_zip_path)
- end
-end
diff --git a/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb
index 815108be447..89f290d8d68 100644
--- a/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb
+++ b/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb
@@ -8,14 +8,20 @@ RSpec.shared_context 'npm api setup' do
let_it_be(:group) { create(:group, name: 'test-group') }
let_it_be(:namespace) { group }
let_it_be(:project, reload: true) { create(:project, :public, namespace: namespace) }
- let_it_be(:package, reload: true) { create(:npm_package, project: project, name: "@#{group.path}/scoped_package") }
+ let_it_be(:package1, reload: true) { create(:npm_package, project: project, name: "@#{group.path}/scoped_package", version: '1.2.4') }
+ let_it_be(:package, reload: true) { create(:npm_package, project: project, name: "@#{group.path}/scoped_package", version: '1.2.3') }
let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
- let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) }
+ let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running, project: project) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
let(:package_name) { package.name }
+
+ before do
+ # create a duplicated package without triggering model validation errors
+ package1.update_column(:version, '1.2.3')
+ end
end
RSpec.shared_context 'set package name from package name type' do
diff --git a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb
index 6b49a415889..2b810e790f0 100644
--- a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb
+++ b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb
@@ -6,21 +6,25 @@ RSpec.shared_context 'stubbed service ping metrics definitions' do
let(:metrics_definitions) { standard_metrics + subscription_metrics + operational_metrics + optional_metrics }
let(:standard_metrics) do
[
- metric_attributes('uuid', "standard")
+ metric_attributes('uuid', 'standard'),
+ metric_attributes('recorded_at', 'standard'),
+ metric_attributes('settings.collected_data_categories', 'standard', 'object')
]
end
let(:operational_metrics) do
[
- metric_attributes('counts.merge_requests', "operational"),
+ metric_attributes('counts.merge_requests', 'operational'),
metric_attributes('counts.todos', "operational")
]
end
let(:optional_metrics) do
[
- metric_attributes('counts.boards', "optional"),
- metric_attributes('gitaly.filesystems', '').except('data_category')
+ metric_attributes('counts.boards', 'optional', 'number'),
+ metric_attributes('gitaly.filesystems', '').except('data_category'),
+ metric_attributes('usage_activity_by_stage.monitor.projects_with_enabled_alert_integrations_histogram', 'optional', 'object'),
+ metric_attributes('topology', 'optional', 'object')
]
end
@@ -34,10 +38,11 @@ RSpec.shared_context 'stubbed service ping metrics definitions' do
)
end
- def metric_attributes(key_path, category)
+ def metric_attributes(key_path, category, value_type = 'string')
{
'key_path' => key_path,
- 'data_category' => category
+ 'data_category' => category,
+ 'value_type' => value_type
}
end
end
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
index cadc753513d..1e303197990 100644
--- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -3,14 +3,10 @@
RSpec.shared_examples 'multiple issue boards' do
context 'authorized user' do
before do
- stub_feature_flags(board_new_list: false)
-
parent.add_maintainer(user)
login_as(user)
- stub_feature_flags(board_new_list: false)
-
visit boards_path
wait_for_requests
end
@@ -79,13 +75,13 @@ RSpec.shared_examples 'multiple issue boards' do
expect(page).to have_content(board2.name)
end
- click_button 'Add list'
+ click_button 'Create list'
- wait_for_requests
+ click_button 'Select a label'
- page.within '.dropdown-menu-issues-board-new' do
- click_link planning.title
- end
+ page.choose(planning.title)
+
+ click_button 'Add to board'
wait_for_requests
diff --git a/spec/support/shared_examples/controllers/concerns/integrations_actions_shared_examples.rb b/spec/support/shared_examples/controllers/concerns/integrations_actions_shared_examples.rb
new file mode 100644
index 00000000000..748a3acf17b
--- /dev/null
+++ b/spec/support/shared_examples/controllers/concerns/integrations_actions_shared_examples.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples IntegrationsActions do
+ let(:integration) do
+ create(:datadog_integration,
+ integration_attributes.merge(
+ api_url: 'http://example.com',
+ api_key: 'secret'
+ )
+ )
+ end
+
+ describe 'GET #edit' do
+ before do
+ get :edit, params: routing_params
+ end
+
+ it 'assigns the integration' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:integration)).to eq(integration)
+ end
+ end
+
+ describe 'PUT #update' do
+ let(:params) do
+ {
+ datadog_env: 'env',
+ datadog_service: 'service'
+ }
+ end
+
+ before do
+ put :update, params: routing_params.merge(integration: params)
+ end
+
+ it 'updates the integration with the provided params and redirects to the form' do
+ expect(response).to redirect_to(routing_params.merge(action: :edit))
+ expect(integration.reload).to have_attributes(params)
+ end
+
+ context 'when sending a password field' do
+ let(:params) { super().merge(api_key: 'new') }
+
+ it 'updates the integration with the password and other params' do
+ expect(response).to be_redirect
+ expect(integration.reload).to have_attributes(params)
+ end
+ end
+
+ context 'when sending a blank password field' do
+ let(:params) { super().merge(api_key: '') }
+
+ it 'ignores the password field and saves the other params' do
+ expect(response).to be_redirect
+ expect(integration.reload).to have_attributes(params.merge(api_key: 'secret'))
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
index a9c6da7bc2b..0ffa32dec9e 100644
--- a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
@@ -82,16 +82,6 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
expect(json_response.dig("provider_repos", 1, "id")).to eq(org_repo.id)
end
- it "does not show already added project" do
- project = create(:project, import_type: provider, namespace: user.namespace, import_status: :finished, import_source: 'asd/vim')
- stub_client(repos: [repo], orgs: [], each_page: [OpenStruct.new(objects: [repo])].to_enum)
-
- get :status, format: :json
-
- expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
- expect(json_response.dig("provider_repos")).to eq([])
- end
-
it "touches the etag cache store" do
stub_client(repos: [], orgs: [], each_page: [])
diff --git a/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
index b9ae0e23e26..44baadaaade 100644
--- a/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
@@ -19,14 +19,4 @@ RSpec.shared_examples 'import controller status' do
expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id)
end
-
- it "does not show already added project" do
- project = create(:project, import_type: provider_name, namespace: user.namespace, import_status: :finished, import_source: import_source)
- stub_client(client_repos_field => [repo])
-
- get :status, format: :json
-
- expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
- expect(json_response.dig("provider_repos")).to eq([])
- end
end
diff --git a/spec/support/shared_examples/controllers/issuable_anonymous_search_disabled_examples.rb b/spec/support/shared_examples/controllers/issuable_anonymous_search_disabled_examples.rb
new file mode 100644
index 00000000000..e77acb93798
--- /dev/null
+++ b/spec/support/shared_examples/controllers/issuable_anonymous_search_disabled_examples.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'issuable list with anonymous search disabled' do |action|
+ let(:controller_action) { :index }
+ let(:params_with_search) { params.merge(search: 'some search term') }
+
+ context 'when disable_anonymous_search is enabled' do
+ before do
+ stub_feature_flags(disable_anonymous_search: true)
+ end
+
+ it 'shows a flash message' do
+ get controller_action, params: params_with_search
+
+ expect(flash.now[:notice]).to eq('You must sign in to search for specific terms.')
+ end
+
+ context 'when search param is not given' do
+ it 'does not show a flash message' do
+ get controller_action, params: params
+
+ expect(flash.now[:notice]).to be_nil
+ end
+ end
+
+ context 'when user is signed-in' do
+ it 'does not show a flash message' do
+ sign_in(create(:user))
+ get controller_action, params: params_with_search
+
+ expect(flash.now[:notice]).to be_nil
+ end
+ end
+
+ context 'when format is not HTML' do
+ it 'does not show a flash message' do
+ get controller_action, params: params_with_search.merge(format: :atom)
+
+ expect(flash.now[:notice]).to be_nil
+ end
+ end
+ end
+
+ context 'when disable_anonymous_search is disabled' do
+ before do
+ stub_feature_flags(disable_anonymous_search: false)
+ end
+
+ it 'does not show a flash message' do
+ get controller_action, params: params_with_search
+
+ expect(flash.now[:notice]).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/atom/issuable_shared_examples.rb b/spec/support/shared_examples/features/atom/issuable_shared_examples.rb
new file mode 100644
index 00000000000..17993830f37
--- /dev/null
+++ b/spec/support/shared_examples/features/atom/issuable_shared_examples.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples "an authenticated issuable atom feed" do
+ it "renders atom feed with common issuable information" do
+ expect(response_headers['Content-Type'])
+ .to have_content('application/atom+xml')
+ expect(body).to have_selector('author email', text: issuable.author_public_email)
+ expect(body).to have_selector('assignees assignee email', text: issuable.assignees.first.public_email)
+ expect(body).to have_selector('assignee email', text: issuable.assignees.first.public_email)
+ expect(body).to have_selector('entry summary', text: issuable.title)
+ end
+end
diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb
new file mode 100644
index 00000000000..2332285540a
--- /dev/null
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'edits content using the content editor' do
+ it 'formats text as bold using bubble menu' do
+ content_editor_testid = '[data-testid="content-editor"] [contenteditable]'
+
+ expect(page).to have_css(content_editor_testid)
+
+ find(content_editor_testid).send_keys 'Typing text in the content editor'
+ find(content_editor_testid).send_keys [:shift, :left]
+
+ expect(page).to have_css('[data-testid="formatting-bubble-menu"]')
+ 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 fd77297a490..e70f9b52c09 100644
--- a/spec/support/shared_examples/features/deploy_token_shared_examples.rb
+++ b/spec/support/shared_examples/features/deploy_token_shared_examples.rb
@@ -1,15 +1,22 @@
# frozen_string_literal: true
RSpec.shared_examples 'a deploy token in settings' do
- it 'view deploy tokens' do
+ it 'view deploy tokens', :js do
+ user.update!(time_display_relative: true)
+
+ visit page_path
+
within('.deploy-tokens') do
expect(page).to have_content(deploy_token.name)
expect(page).to have_content('read_repository')
expect(page).to have_content('read_registry')
+ expect(page).to have_content('in 4 days')
end
end
it 'add a new deploy token' 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'
@@ -24,4 +31,18 @@ RSpec.shared_examples 'a deploy token in settings' do
expect(page).to have_selector("input[name='deploy-token'][readonly='readonly']")
end
end
+
+ context 'when User#time_display_relative is false', :js do
+ before do
+ user.update!(time_display_relative: false)
+ end
+
+ it 'shows absolute times for expires_at' do
+ visit page_path
+
+ within('.deploy-tokens') do
+ expect(page).to have_content(deploy_token.expires_at.strftime('%b %d'))
+ end
+ end
+ end
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 fb2e422559d..318ba67b9e9 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -7,7 +7,7 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
let(:menu_selector) { "#{dropdown_selector} .dropdown-menu" }
let(:submit_selector) { "#{form_selector} .js-comment-submit-button" }
let(:close_selector) { "#{form_selector} .btn-comment-and-close" }
- let(:comments_selector) { '.timeline > .note.timeline-entry' }
+ let(:comments_selector) { '.timeline > .note.timeline-entry:not(.being-posted)' }
let(:comment) { 'My comment' }
it 'clicking "Comment" will post a comment' do
@@ -187,7 +187,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
let(:toggle_selector) { "#{dropdown_selector} .dropdown-toggle-split" }
let(:menu_selector) { "#{dropdown_selector} .dropdown-menu" }
let(:close_selector) { "#{form_selector} .btn-comment-and-close" }
- let(:comments_selector) { '.timeline > .note.timeline-entry' }
+ let(:comments_selector) { '.timeline > .note.timeline-entry:not(.being-posted)' }
let(:comment) { 'My comment' }
it 'clicking "Comment" will post a comment' do
@@ -197,6 +197,8 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
find(submit_button_selector).click
+ wait_for_all_requests
+
expect(page).to have_content(comment)
new_comment = all(comments_selector).last
diff --git a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
index c0cfc27ceaf..149486320ae 100644
--- a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
+++ b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
@@ -15,7 +15,7 @@ RSpec.shared_examples 'issuable invite members' do
page.within '.dropdown-menu-user' do
expect(page).to have_link('Invite Members')
- expect(page).to have_selector('[data-track-event="click_invite_members"]')
+ expect(page).to have_selector('[data-track-action="click_invite_members"]')
expect(page).to have_selector('[data-track-label="edit_assignee"]')
end
diff --git a/spec/support/shared_examples/features/manage_applications_shared_examples.rb b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
index 38bb87eaed2..0161899cb76 100644
--- a/spec/support/shared_examples/features/manage_applications_shared_examples.rb
+++ b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
@@ -9,9 +9,11 @@ RSpec.shared_examples 'manage applications' do
visit new_application_path
expect(page).to have_content 'Add new application'
+ expect(find('#doorkeeper_application_expire_access_tokens')).to be_checked
fill_in :doorkeeper_application_name, with: application_name
fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri
+ uncheck :doorkeeper_application_expire_access_tokens
check :doorkeeper_application_scopes_read_user
click_on 'Save application'
@@ -22,6 +24,8 @@ RSpec.shared_examples 'manage applications' do
click_on 'Edit'
+ expect(find('#doorkeeper_application_expire_access_tokens')).not_to be_checked
+
application_name_changed = "#{application_name} changed"
fill_in :doorkeeper_application_name, with: application_name_changed
diff --git a/spec/support/shared_examples/features/rss_shared_examples.rb b/spec/support/shared_examples/features/rss_shared_examples.rb
index c7c2aeea358..0991de21d8d 100644
--- a/spec/support/shared_examples/features/rss_shared_examples.rb
+++ b/spec/support/shared_examples/features/rss_shared_examples.rb
@@ -25,3 +25,23 @@ RSpec.shared_examples "it has an RSS button without a feed token" do
.to have_css("a:has(.qa-rss-icon):not([href*='feed_token'])") # rubocop:disable QA/SelectorUsage
end
end
+
+RSpec.shared_examples "updates atom feed link" do |type|
+ it "for #{type}" do
+ sign_in(user)
+ visit path
+
+ link = find_link('Subscribe to RSS feed')
+ params = CGI.parse(URI.parse(link[:href]).query)
+ auto_discovery_link = find("link[type='application/atom+xml']", visible: false)
+ auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
+
+ expected = {
+ 'feed_token' => [user.feed_token],
+ 'assignee_id' => [user.id.to_s]
+ }
+
+ expect(params).to include(expected)
+ expect(auto_discovery_params).to include(expected)
+ end
+end
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 9587da0233e..7ced8508a31 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
@@ -136,6 +136,14 @@ RSpec.shared_examples 'User updates wiki page' do
expect(find('textarea#wiki_content').value).to eq('Updated Wiki Content')
end
end
+
+ context 'when using the content editor' do
+ before do
+ click_button 'Use the new editor'
+ end
+
+ it_behaves_like 'edits content using the content editor'
+ end
end
context 'when the page is in a subdir', :js do
diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
index 61feeff57bb..96df5a5f972 100644
--- a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
@@ -157,7 +157,7 @@ RSpec.shared_examples 'User views a wiki page' do
expect(page).to have_link('updated home', href: wiki_page_path(wiki, wiki_page, version_id: commit2, action: :diff))
end
- it 'between the current and the previous version of a page' do
+ it 'between the current and the previous version of a page', :js do
commit = wiki.commit
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
@@ -169,7 +169,7 @@ RSpec.shared_examples 'User views a wiki page' do
expect_diff_links(commit)
end
- it 'between two old versions of a page' do
+ it 'between two old versions of a page', :js do
wiki_page.update(message: 'latest home change', content: 'updated [another link](other-page)') # rubocop:disable Rails/SaveBang:
commit = wiki.commit('HEAD^')
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
@@ -184,7 +184,7 @@ RSpec.shared_examples 'User views a wiki page' do
expect_diff_links(commit)
end
- it 'for the oldest version of a page' do
+ it 'for the oldest version of a page', :js do
commit = wiki.commit('HEAD^')
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb
new file mode 100644
index 00000000000..6342064beb8
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+shared_examples 'deployment metrics examples' do
+ def create_deployment(args)
+ project = args[:project]
+ environment = project.environments.production.first || create(:environment, :production, project: project)
+ create(:deployment, :success, args.merge(environment: environment))
+
+ # this is needed for the dora_deployment_frequency_in_vsa feature flag so we have aggregated data
+ ::Dora::DailyMetrics::RefreshWorker.new.perform(environment.id, Time.current.to_date.to_s) if Gitlab.ee?
+ end
+
+ describe "#deploys" do
+ subject { stage_summary.third }
+
+ context 'when from date is given' do
+ before do
+ travel_to(5.days.ago) { create_deployment(project: project) }
+ create_deployment(project: project)
+ end
+
+ it "finds the number of deploys made created after the 'from date'" do
+ expect(subject[:value]).to eq('1')
+ end
+
+ it 'returns the localized title' do
+ Gitlab::I18n.with_locale(:ru) do
+ expect(subject[:title]).to eq(n_('Deploy', 'Deploys', 1))
+ end
+ end
+ end
+
+ it "doesn't find commits from other projects" do
+ travel_to(5.days.from_now) do
+ create_deployment(project: create(:project, :repository))
+ end
+
+ expect(subject[:value]).to eq('-')
+ end
+
+ context 'when `to` parameter is given' do
+ before do
+ travel_to(5.days.ago) { create_deployment(project: project) }
+ travel_to(5.days.from_now) { create_deployment(project: project) }
+ end
+
+ it "doesn't find any record" do
+ options[:to] = Time.now
+
+ expect(subject[:value]).to eq('-')
+ end
+
+ it "finds records created between `from` and `to` range" do
+ options[:from] = 10.days.ago
+ options[:to] = 10.days.from_now
+
+ expect(subject[:value]).to eq('2')
+ end
+ end
+ end
+
+ describe '#deployment_frequency' do
+ subject { stage_summary.fourth[:value] }
+
+ it 'includes the unit: `per day`' do
+ expect(stage_summary.fourth[:unit]).to eq _('per day')
+ end
+
+ before do
+ travel_to(5.days.ago) { create_deployment(project: project) }
+ end
+
+ it 'returns 0.0 when there were deploys but the frequency was too low' do
+ options[:from] = 30.days.ago
+
+ # 1 deployment over 30 days
+ # frequency of 0.03, rounded off to 0.0
+ expect(subject).to eq('0')
+ end
+
+ it 'returns `-` when there were no deploys' do
+ options[:from] = 4.days.ago
+
+ # 0 deployment in the last 4 days
+ expect(subject).to eq('-')
+ end
+
+ context 'when `to` is nil' do
+ it 'includes range until now' do
+ options[:from] = 6.days.ago
+ options[:to] = nil
+
+ # 1 deployment over 7 days
+ expect(subject).to eq('0.1')
+ end
+ end
+
+ context 'when `to` is given' do
+ before do
+ travel_to(5.days.from_now) { create_deployment(project: project, finished_at: Time.zone.now) }
+ end
+
+ it 'finds records created between `from` and `to` range' do
+ options[:from] = 10.days.ago
+ options[:to] = 10.days.from_now
+
+ # 2 deployments over 20 days
+ expect(subject).to eq('0.1')
+ end
+
+ context 'when `from` and `to` are within a day' do
+ it 'returns the number of deployments made on that day' do
+ freeze_time do
+ create_deployment(project: project, finished_at: Time.current)
+ options[:from] = Time.current.yesterday.beginning_of_day
+ options[:to] = Time.current.end_of_day
+
+ expect(subject).to eq('0.5')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
index 89b793d5e16..708bc71ae96 100644
--- a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
@@ -39,6 +39,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
allow(fake_duplicate_job).to receive(:scheduled?).and_return(false)
allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
+ allow(fake_duplicate_job).to receive(:update_latest_wal_location!)
allow(fake_duplicate_job).to receive(:options).and_return({})
job_hash = {}
@@ -63,6 +64,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
.with(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob::DUPLICATE_KEY_TTL)
.and_return('the jid'))
allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
+ allow(fake_duplicate_job).to receive(:update_latest_wal_location!)
job_hash = {}
expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
@@ -83,6 +85,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
allow(fake_duplicate_job).to(
receive(:check!).with(time_diff.to_i).and_return('the jid'))
allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
+ allow(fake_duplicate_job).to receive(:update_latest_wal_location!)
job_hash = {}
expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
@@ -105,6 +108,13 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
allow(fake_duplicate_job).to receive(:options).and_return({})
allow(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
+ allow(fake_duplicate_job).to receive(:update_latest_wal_location!)
+ end
+
+ it 'updates latest wal location' do
+ expect(fake_duplicate_job).to receive(:update_latest_wal_location!)
+
+ strategy.schedule({ 'jid' => 'new jid' }) {}
end
it 'drops the job' do
@@ -136,4 +146,46 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
end
end
end
+
+ describe '#perform' do
+ let(:proc) { -> {} }
+ let(:job) { { 'jid' => 'new jid', 'wal_locations' => { 'main' => '0/1234', 'ci' => '0/1234' } } }
+ let(:wal_locations) do
+ {
+ main: '0/D525E3A8',
+ ci: 'AB/12345'
+ }
+ end
+
+ before do
+ allow(fake_duplicate_job).to receive(:delete!)
+ allow(fake_duplicate_job).to receive(:latest_wal_locations).and_return( wal_locations )
+ end
+
+ it 'updates job hash with dedup_wal_locations' do
+ strategy.perform(job) do
+ proc.call
+ end
+
+ expect(job['dedup_wal_locations']).to eq(wal_locations)
+ end
+
+ shared_examples 'does not update job hash' do
+ it 'does not update job hash with dedup_wal_locations' do
+ strategy.perform(job) do
+ proc.call
+ end
+
+ expect(job).not_to include('dedup_wal_locations')
+ end
+ end
+
+ context 'when latest_wal_location is empty' do
+ before do
+ allow(fake_duplicate_job).to receive(:latest_wal_locations).and_return( {} )
+ end
+
+ include_examples 'does not update job hash'
+ end
+ end
end
diff --git a/spec/support/shared_examples/mailers/notify_shared_examples.rb b/spec/support/shared_examples/mailers/notify_shared_examples.rb
index b10ebb4d2a3..e1f7a9030e2 100644
--- a/spec/support/shared_examples/mailers/notify_shared_examples.rb
+++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'a multiple recipients email' do
it 'is sent to the given recipient' do
- is_expected.to deliver_to recipient.notification_email
+ is_expected.to deliver_to recipient.notification_email_or_default
end
end
@@ -21,7 +21,7 @@ end
RSpec.shared_examples 'an email sent to a user' do
it 'is sent to user\'s global notification email address' do
- expect(subject).to deliver_to(recipient.notification_email)
+ expect(subject).to deliver_to(recipient.notification_email_or_default)
end
context 'with group notification email' do
@@ -227,7 +227,7 @@ RSpec.shared_examples 'a note email' do
aggregate_failures do
expect(sender.display_name).to eq("#{note_author.name} (@#{note_author.username})")
expect(sender.address).to eq(gitlab_sender)
- expect(subject).to deliver_to(recipient.notification_email)
+ expect(subject).to deliver_to(recipient.notification_email_or_default)
end
end
diff --git a/spec/support/shared_examples/models/concerns/featurable_shared_examples.rb b/spec/support/shared_examples/models/concerns/featurable_shared_examples.rb
new file mode 100644
index 00000000000..2f165ef604f
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/featurable_shared_examples.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'access level validation' do |features|
+ features.each do |feature|
+ it "does not allow public access level for #{feature}" do
+ field = "#{feature}_access_level".to_sym
+ container_features.update_attribute(field, ProjectFeature::PUBLIC)
+
+ expect(container_features.valid?).to be_falsy, "#{field} failed"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/sanitizable_shared_examples.rb b/spec/support/shared_examples/models/concerns/sanitizable_shared_examples.rb
new file mode 100644
index 00000000000..ed94a71892d
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/sanitizable_shared_examples.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'sanitizable' do |factory, fields|
+ let(:attributes) { fields.to_h { |field| [field, input] } }
+
+ it 'includes Sanitizable' do
+ expect(described_class).to include(Sanitizable)
+ end
+
+ fields.each do |field|
+ subject do
+ record = build(factory, attributes)
+ record.valid?
+
+ record.public_send(field)
+ end
+
+ describe "##{field}" do
+ context 'when input includes javascript tags' do
+ let(:input) { 'hello<script>alert(1)</script>' }
+
+ it 'gets sanitized' do
+ expect(subject).to eq('hello')
+ end
+ end
+ end
+
+ describe "##{field} validation" do
+ context 'when input contains pre-escaped html entities' do
+ let_it_be(:input) { '&lt;script&gt;alert(1)&lt;/script&gt;' }
+
+ subject { build(factory, attributes) }
+
+ it 'is not valid', :aggregate_failures do
+ expect(subject).not_to be_valid
+ expect(subject.errors.details[field].flat_map(&:values)).to include('cannot contain escaped HTML entities')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index c111d250d34..56c202cb228 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -300,8 +300,21 @@ RSpec.shared_examples_for "member creation" do
end
end
end
+end
+
+RSpec.shared_examples_for "bulk member creation" do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:admin) { create(:admin) }
+
+ describe '#execute' do
+ it 'raises an error when exiting_members is not passed in the args hash' do
+ expect do
+ described_class.new(source, user, :maintainer, current_user: user).execute
+ end.to raise_error(ArgumentError, 'existing_members must be included in the args hash')
+ end
+ end
- describe '.add_users' do
+ describe '.add_users', :aggregate_failures do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -310,8 +323,8 @@ RSpec.shared_examples_for "member creation" do
expect(members).to be_a Array
expect(members.size).to eq(2)
- expect(members.first).to be_a member_type
- expect(members.first).to be_persisted
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
end
it 'returns an empty array' do
@@ -329,5 +342,42 @@ RSpec.shared_examples_for "member creation" do
expect(members.size).to eq(4)
expect(members.first).to be_invite
end
+
+ context 'with de-duplication' do
+ it 'with the same user by id and user' do
+ members = described_class.add_users(source, [user1.id, user1, user1.id, user2, user2.id, user2], :maintainer)
+
+ expect(members).to be_a Array
+ expect(members.size).to eq(2)
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
+ end
+
+ it 'with the same user sent more than once' do
+ members = described_class.add_users(source, [user1, user1], :maintainer)
+
+ expect(members).to be_a Array
+ expect(members.size).to eq(1)
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
+ end
+ end
+
+ context 'when a member already exists' do
+ before do
+ source.add_user(user1, :developer)
+ end
+
+ it 'supports existing users as expected' do
+ user3 = create(:user)
+
+ members = described_class.add_users(source, [user1.id, user2, user3.id], :maintainer)
+
+ expect(members).to be_a Array
+ expect(members.size).to eq(3)
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/models/mentionable_shared_examples.rb b/spec/support/shared_examples/models/mentionable_shared_examples.rb
index 07c5f730e95..e23658d1774 100644
--- a/spec/support/shared_examples/models/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/models/mentionable_shared_examples.rb
@@ -207,7 +207,7 @@ RSpec.shared_examples 'an editable mentionable' do
end
RSpec.shared_examples 'mentions in description' do |mentionable_type|
- shared_examples 'when storing user mentions' do
+ context 'when storing user mentions' do
before do
mentionable.store_mentions!
end
@@ -238,26 +238,10 @@ RSpec.shared_examples 'mentions in description' do |mentionable_type|
end
end
end
-
- context 'when store_mentions_without_subtransaction is enabled' do
- before do
- stub_feature_flags(store_mentions_without_subtransaction: true)
- end
-
- it_behaves_like 'when storing user mentions'
- end
-
- context 'when store_mentions_without_subtransaction is disabled' do
- before do
- stub_feature_flags(store_mentions_without_subtransaction: false)
- end
-
- it_behaves_like 'when storing user mentions'
- end
end
RSpec.shared_examples 'mentions in notes' do |mentionable_type|
- shared_examples 'when mentionable notes contain mentions' do
+ context 'when mentionable notes contain mentions' do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:group) { create(:group) }
@@ -277,22 +261,6 @@ RSpec.shared_examples 'mentions in notes' do |mentionable_type|
expect(mentionable.referenced_groups(user)).to eq [group]
end
end
-
- context 'when store_mentions_without_subtransaction is enabled' do
- before do
- stub_feature_flags(store_mentions_without_subtransaction: true)
- end
-
- it_behaves_like 'when mentionable notes contain mentions'
- end
-
- context 'when store_mentions_without_subtransaction is disabled' do
- before do
- stub_feature_flags(store_mentions_without_subtransaction: false)
- end
-
- it_behaves_like 'when mentionable notes contain mentions'
- end
end
RSpec.shared_examples 'load mentions from DB' do |mentionable_type|
diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
index 4d328c03641..74b1bacc560 100644
--- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
@@ -31,6 +31,131 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { expect(subject.where_values_hash).not_to have_key(:type) }
end
+ describe '.order_by_depth' do
+ subject { described_class.where(id: [group_1, nested_group_1, deep_nested_group_1]).order_by_depth(direction) }
+
+ context 'ascending' do
+ let(:direction) { :asc }
+
+ it { is_expected.to eq [deep_nested_group_1, nested_group_1, group_1] }
+ end
+
+ context 'descending' do
+ let(:direction) { :desc }
+
+ it { is_expected.to eq [group_1, nested_group_1, deep_nested_group_1] }
+ end
+ end
+
+ describe '.normal_select' do
+ let(:query_result) { described_class.where(id: group_1).normal_select }
+
+ subject { query_result.column_names }
+
+ it { is_expected.to eq described_class.column_names }
+ end
+
+ shared_examples '.self_and_ancestors' do
+ subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_ancestors }
+
+ it { is_expected.to contain_exactly(group_1, nested_group_1, group_2, nested_group_2) }
+
+ context 'when include_self is false' do
+ subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_ancestors(include_self: false) }
+
+ it { is_expected.to contain_exactly(group_1, group_2) }
+ end
+
+ context 'when hierarchy_order is ascending' do
+ subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_ancestors(hierarchy_order: :asc) }
+
+ # Recursive order per level is not defined.
+ it { is_expected.to contain_exactly(nested_group_1, nested_group_2, group_1, group_2) }
+ it { expect(subject[0, 2]).to contain_exactly(nested_group_1, nested_group_2) }
+ it { expect(subject[2, 2]).to contain_exactly(group_1, group_2) }
+ end
+
+ context 'when hierarchy_order is descending' do
+ subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_ancestors(hierarchy_order: :desc) }
+
+ # Recursive order per level is not defined.
+ it { is_expected.to contain_exactly(nested_group_1, nested_group_2, group_1, group_2) }
+ it { expect(subject[0, 2]).to contain_exactly(group_1, group_2) }
+ it { expect(subject[2, 2]).to contain_exactly(nested_group_1, nested_group_2) }
+ end
+ end
+
+ describe '.self_and_ancestors' do
+ context "use_traversal_ids_ancestor_scopes feature flag is true" do
+ before do
+ stub_feature_flags(use_traversal_ids: true)
+ stub_feature_flags(use_traversal_ids_for_ancestor_scopes: true)
+ end
+
+ it_behaves_like '.self_and_ancestors'
+
+ it 'not make recursive queries' do
+ expect { described_class.where(id: [nested_group_1]).self_and_ancestors.load }.not_to make_queries_matching(/WITH RECURSIVE/)
+ end
+ end
+
+ context "use_traversal_ids_ancestor_scopes feature flag is false" do
+ before do
+ stub_feature_flags(use_traversal_ids_for_ancestor_scopes: false)
+ end
+
+ it_behaves_like '.self_and_ancestors'
+
+ it 'make recursive queries' do
+ expect { described_class.where(id: [nested_group_1]).self_and_ancestors.load }.to make_queries_matching(/WITH RECURSIVE/)
+ end
+ end
+ end
+
+ shared_examples '.self_and_ancestor_ids' do
+ subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_ancestor_ids.pluck(:id) }
+
+ it { is_expected.to contain_exactly(group_1.id, nested_group_1.id, group_2.id, nested_group_2.id) }
+
+ context 'when include_self is false' do
+ subject do
+ described_class
+ .where(id: [nested_group_1, nested_group_2])
+ .self_and_ancestor_ids(include_self: false)
+ .pluck(:id)
+ end
+
+ it { is_expected.to contain_exactly(group_1.id, group_2.id) }
+ end
+ end
+
+ describe '.self_and_ancestor_ids' do
+ context "use_traversal_ids_ancestor_scopes feature flag is true" do
+ before do
+ stub_feature_flags(use_traversal_ids: true)
+ stub_feature_flags(use_traversal_ids_for_ancestor_scopes: true)
+ end
+
+ it_behaves_like '.self_and_ancestor_ids'
+
+ it 'make recursive queries' do
+ expect { described_class.where(id: [nested_group_1]).self_and_ancestor_ids.load }.not_to make_queries_matching(/WITH RECURSIVE/)
+ end
+ end
+
+ context "use_traversal_ids_ancestor_scopes feature flag is false" do
+ before do
+ stub_feature_flags(use_traversal_ids_for_ancestor_scopes: false)
+ end
+
+ it_behaves_like '.self_and_ancestor_ids'
+
+ it 'make recursive queries' do
+ expect { described_class.where(id: [nested_group_1]).self_and_ancestor_ids.load }.to make_queries_matching(/WITH RECURSIVE/)
+ end
+ end
+ end
+
describe '.self_and_descendants' do
subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_descendants }
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 1ad38a17f9c..acbcf4f7f3d 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
@@ -36,8 +36,8 @@ RSpec.shared_examples 'process helm service index request' do |user_type, status
expect(yaml_response.keys).to contain_exactly('apiVersion', 'entries', 'generated', 'serverInfo')
expect(yaml_response['entries']).to be_a(Hash)
- expect(yaml_response['entries'].keys).to contain_exactly(package.name)
- expect(yaml_response['serverInfo']).to eq({ 'contextPath' => "/api/v4/projects/#{project.id}/packages/helm" })
+ expect(yaml_response['entries'].keys).to contain_exactly(package.name, package2.name)
+ expect(yaml_response['serverInfo']).to eq({ 'contextPath' => "/api/v4/projects/#{project_id}/packages/helm" })
package_entry = yaml_response['entries'][package.name]
@@ -45,6 +45,14 @@ RSpec.shared_examples 'process helm service index request' do |user_type, status
expect(package_entry.first.keys).to contain_exactly('name', 'version', 'apiVersion', 'created', 'digest', 'urls')
expect(package_entry.first['digest']).to eq('fd2b2fa0329e80a2a602c2bb3b40608bcd6ee5cf96cf46fd0d2800a4c129c9db')
expect(package_entry.first['urls']).to eq(["charts/#{package.name}-#{package.version}.tgz"])
+
+ package_entry = yaml_response['entries'][package2.name]
+
+ expect(package_entry.length).to eq(1)
+ expect(package_entry.first.keys).to contain_exactly('name', 'version', 'apiVersion', 'created', 'digest', 'urls', 'description')
+ expect(package_entry.first['digest']).to eq('file2')
+ expect(package_entry.first['description']).to eq('hello from stable channel')
+ expect(package_entry.first['urls']).to eq(['charts/filename2.tgz'])
end
end
end
@@ -174,6 +182,13 @@ RSpec.shared_examples 'process helm download content request' do |user_type, sta
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if user_type != :anonymous && user_type != :not_a_member
+
+ expect_next_found_instance_of(::Packages::PackageFile) do |package_file|
+ expect(package_file).to receive(:file).and_wrap_original do |m, *args|
+ expect(package_file.id).to eq(package_file2.id)
+ m.call(*args)
+ end
+ end
end
it_behaves_like 'a package tracking event', 'API::HelmPackages', 'pull_package'
@@ -189,7 +204,7 @@ end
RSpec.shared_examples 'rejects helm access with unknown project id' do
context 'with an unknown project' do
- let(:project) { OpenStruct.new(id: 1234567890) }
+ let(:project_id) { 1234567890 }
context 'as anonymous' do
it_behaves_like 'rejects helm packages access', :anonymous, :unauthorized
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 0390e60747f..2af7b616659 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
@@ -21,11 +21,24 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
expect(response).to match_response_schema('public_api/v4/packages/npm_package')
expect(json_response['name']).to eq(package.name)
expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version')
- ::Packages::Npm::PackagePresenter::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type|
expect(json_response.dig('versions', package.version, dependency_type.to_s)).to be_any
end
expect(json_response['dist-tags']).to match_schema('public_api/v4/packages/npm_package_tags')
end
+
+ it 'avoids N+1 database queries' do
+ control = ActiveRecord::QueryRecorder.new { get(url, headers: headers) }
+
+ create_list(:npm_package, 5, project: project, name: package_name).each do |npm_package|
+ ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type|
+ create(:packages_dependency_link, package: package, dependency_type: dependency_type)
+ end
+ end
+
+ # query count can slightly change between the examples so we're using a custom threshold
+ expect { get(url, headers: headers) }.not_to exceed_query_limit(control).with_threshold(4)
+ end
end
shared_examples 'reject metadata request' do |status:|
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
index ecde4ee8565..eb650b7a09f 100644
--- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -153,3 +153,15 @@ RSpec.shared_examples 'a package tracking event' do |category, action|
expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context)
end
end
+
+RSpec.shared_examples 'not a package tracking event' do
+ before do
+ stub_feature_flags(collect_package_events: true)
+ end
+
+ it 'does not create a gitlab tracking event', :snowplow, :aggregate_failures do
+ expect { subject }.not_to change { Packages::Event.count }
+
+ expect_no_snowplow_event
+ end
+end
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
index 95817624658..2a19ff6f590 100644
--- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#
# Requires let variables:
-# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_packages_api"
+# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_packages_api", "throttle_authenticated_git_lfs", "throttle_authenticated_files_api"
# * request_method
# * request_args
# * other_user_request_args
@@ -14,7 +14,9 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
"throttle_protected_paths" => "throttle_authenticated_protected_paths_api",
"throttle_authenticated_api" => "throttle_authenticated_api",
"throttle_authenticated_web" => "throttle_authenticated_web",
- "throttle_authenticated_packages_api" => "throttle_authenticated_packages_api"
+ "throttle_authenticated_packages_api" => "throttle_authenticated_packages_api",
+ "throttle_authenticated_git_lfs" => "throttle_authenticated_git_lfs",
+ "throttle_authenticated_files_api" => "throttle_authenticated_files_api"
}
end
@@ -165,7 +167,7 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
end
# Requires let variables:
-# * throttle_setting_prefix: "throttle_authenticated_web" or "throttle_protected_paths"
+# * throttle_setting_prefix: "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_git_lfs"
# * user
# * url_that_requires_authentication
# * request_method
@@ -176,7 +178,8 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
let(:throttle_types) do
{
"throttle_protected_paths" => "throttle_authenticated_protected_paths_web",
- "throttle_authenticated_web" => "throttle_authenticated_web"
+ "throttle_authenticated_web" => "throttle_authenticated_web",
+ "throttle_authenticated_git_lfs" => "throttle_authenticated_git_lfs"
}
end
@@ -385,3 +388,194 @@ RSpec.shared_examples 'tracking when dry-run mode is set' do
end
end
end
+
+# Requires let variables:
+# * throttle_name: "throttle_unauthenticated_api", "throttle_unauthenticated_web"
+# * throttle_setting_prefix: "throttle_unauthenticated_api", "throttle_unauthenticated"
+# * url_that_does_not_require_authentication
+# * url_that_is_not_matched
+# * requests_per_period
+# * period_in_seconds
+# * period
+RSpec.shared_examples 'rate-limited unauthenticated requests' do
+ before do
+ # Set low limits
+ settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
+ settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
+ end
+
+ context 'when the throttle is enabled' do
+ before do
+ settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'rejects requests over the rate limit' do
+ # At first, allow requests under the rate limit.
+ requests_per_period.times do
+ get url_that_does_not_require_authentication
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ # the last straw
+ expect_rejection { get url_that_does_not_require_authentication }
+ end
+
+ context 'with custom response text' do
+ before do
+ stub_application_setting(rate_limiting_response_text: 'Custom response')
+ end
+
+ it 'rejects requests over the rate limit' do
+ # At first, allow requests under the rate limit.
+ requests_per_period.times do
+ get url_that_does_not_require_authentication
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ # the last straw
+ expect_rejection { get url_that_does_not_require_authentication }
+ expect(response.body).to eq("Custom response\n")
+ end
+ end
+
+ it 'allows requests after throttling and then waiting for the next period' do
+ requests_per_period.times do
+ get url_that_does_not_require_authentication
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { get url_that_does_not_require_authentication }
+
+ travel_to(period.from_now) do
+ requests_per_period.times do
+ get url_that_does_not_require_authentication
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { get url_that_does_not_require_authentication }
+ end
+ end
+
+ it 'counts requests from different IPs separately' do
+ requests_per_period.times do
+ get url_that_does_not_require_authentication
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_next_instance_of(Rack::Attack::Request) do |instance|
+ expect(instance).to receive(:ip).at_least(:once).and_return('1.2.3.4')
+ end
+
+ # would be over limit for the same IP
+ get url_that_does_not_require_authentication
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'when the request is not matched by the throttle' do
+ it 'does not throttle the requests' do
+ (1 + requests_per_period).times do
+ get url_that_is_not_matched
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'when the request is to the api internal endpoints' do
+ it 'allows requests over the rate limit' do
+ (1 + requests_per_period).times do
+ get '/api/v4/internal/check', params: { secret_token: Gitlab::Shell.secret_token }
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'when the request is authenticated by a runner token' do
+ let(:request_jobs_url) { '/api/v4/jobs/request' }
+ let(:runner) { create(:ci_runner) }
+
+ it 'does not count as unauthenticated' do
+ (1 + requests_per_period).times do
+ post request_jobs_url, params: { token: runner.token }
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+ end
+
+ context 'when the request is to a health endpoint' do
+ let(:health_endpoint) { '/-/metrics' }
+
+ it 'does not throttle the requests' do
+ (1 + requests_per_period).times do
+ get health_endpoint
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'when the request is to a container registry notification endpoint' do
+ let(:secret_token) { 'secret_token' }
+ let(:events) { [{ action: 'push' }] }
+ let(:registry_endpoint) { '/api/v4/container_registry_event/events' }
+ let(:registry_headers) { { 'Content-Type' => ::API::ContainerRegistryEvent::DOCKER_DISTRIBUTION_EVENTS_V1_JSON } }
+
+ before do
+ allow(Gitlab.config.registry).to receive(:notification_secret) { secret_token }
+
+ event = spy(:event)
+ allow(::ContainerRegistry::Event).to receive(:new).and_return(event)
+ allow(event).to receive(:supported?).and_return(true)
+ end
+
+ it 'does not throttle the requests' do
+ (1 + requests_per_period).times do
+ post registry_endpoint,
+ params: { events: events }.to_json,
+ headers: registry_headers.merge('Authorization' => secret_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ it 'logs RackAttack info into structured logs' do
+ requests_per_period.times do
+ get url_that_does_not_require_authentication
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ arguments = a_hash_including({
+ message: 'Rack_Attack',
+ env: :throttle,
+ remote_ip: '127.0.0.1',
+ request_method: 'GET',
+ path: url_that_does_not_require_authentication,
+ matched: throttle_name
+ })
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(arguments)
+
+ get url_that_does_not_require_authentication
+ end
+
+ it_behaves_like 'tracking when dry-run mode is set' do
+ def do_request
+ get url_that_does_not_require_authentication
+ end
+ end
+ end
+
+ context 'when the throttle is disabled' do
+ before do
+ settings_to_set[:"#{throttle_setting_prefix}_enabled"] = false
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'allows requests over the rate limit' do
+ (1 + requests_per_period).times do
+ get url_that_does_not_require_authentication
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb b/spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb
new file mode 100644
index 00000000000..f6692646ca8
--- /dev/null
+++ b/spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'updating the dependency proxy image ttl policy attributes' do |from: {}, to:|
+ it_behaves_like 'not creating the dependency proxy image ttl policy'
+
+ it 'updates the dependency proxy image ttl policy' do
+ expect { subject }
+ .to change { group.dependency_proxy_image_ttl_policy.reload.enabled }.from(from[:enabled]).to(to[:enabled])
+ .and change { group.dependency_proxy_image_ttl_policy.reload.ttl }.from(from[:ttl]).to(to[:ttl])
+ end
+end
+
+RSpec.shared_examples 'not creating the dependency proxy image ttl policy' do
+ it "doesn't create the dependency proxy image ttl policy" do
+ expect { subject }.not_to change { DependencyProxy::ImageTtlGroupPolicy.count }
+ end
+end
+
+RSpec.shared_examples 'creating the dependency proxy image ttl policy' do
+ it 'creates a new package setting' do
+ expect { subject }.to change { DependencyProxy::ImageTtlGroupPolicy.count }.by(1)
+ end
+
+ it 'saves the settings' do
+ subject
+
+ expect(group.dependency_proxy_image_ttl_policy).to have_attributes(
+ enabled: ttl_policy[:enabled],
+ ttl: ttl_policy[:ttl]
+ )
+ end
+
+ it_behaves_like 'returning a success'
+end
diff --git a/spec/support/shared_examples/services/incident_shared_examples.rb b/spec/support/shared_examples/services/incident_shared_examples.rb
index 9fced12b543..0277cce975a 100644
--- a/spec/support/shared_examples/services/incident_shared_examples.rb
+++ b/spec/support/shared_examples/services/incident_shared_examples.rb
@@ -13,6 +13,7 @@
RSpec.shared_examples 'incident issue' do
it 'has incident as issue type' do
expect(issue.issue_type).to eq('incident')
+ expect(issue.work_item_type.base_type).to eq('incident')
end
end
@@ -41,6 +42,7 @@ RSpec.shared_examples 'not an incident issue' do
it 'has not incident as issue type' do
expect(issue.issue_type).not_to eq('incident')
+ expect(issue.work_item_type.base_type).not_to eq('incident')
end
it 'has not an incident label' do
diff --git a/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb b/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb
new file mode 100644
index 00000000000..09820593cdb
--- /dev/null
+++ b/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'dismissing user callout' do |model|
+ it 'creates a new user callout' do
+ expect { execute }.to change { model.count }.by(1)
+ end
+
+ it 'returns a user callout' do
+ expect(execute).to be_an_instance_of(model)
+ end
+
+ it 'sets the dismissed_at attribute to current time' do
+ freeze_time do
+ expect(execute).to have_attributes(dismissed_at: Time.current)
+ end
+ end
+
+ it 'updates an existing callout dismissed_at time' do
+ freeze_time do
+ old_time = 1.day.ago
+ new_time = Time.current
+ attributes = params.merge(dismissed_at: old_time, user: user)
+ existing_callout = create("#{model.name.split('::').last.underscore}".to_sym, attributes)
+
+ expect { execute }.to change { existing_callout.reload.dismissed_at }.from(old_time).to(new_time)
+ end
+ end
+
+ it 'does not update an invalid record with dismissed_at time', :aggregate_failures do
+ callout = described_class.new(
+ container: nil, current_user: user, params: { feature_name: nil }
+ ).execute
+
+ expect(callout.dismissed_at).to be_nil
+ expect(callout).to be_invalid
+ end
+end
diff --git a/spec/support/shared_examples/work_item_base_types_importer.rb b/spec/support/shared_examples/work_item_base_types_importer.rb
new file mode 100644
index 00000000000..7d652be8d05
--- /dev/null
+++ b/spec/support/shared_examples/work_item_base_types_importer.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'work item base types importer' do
+ it 'creates all base work item types' do
+ # Fixtures need to run on a pristine DB, but the test suite preloads the base types before(:suite)
+ WorkItem::Type.delete_all
+
+ expect { subject }.to change(WorkItem::Type, :count).from(0).to(WorkItem::Type::BASE_TYPES.count)
+ end
+end