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:
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/ability_check_todo.yml2
-rw-r--r--spec/support/capybara.rb9
-rw-r--r--spec/support/database/prevent_cross_joins.rb2
-rw-r--r--spec/support/db_cleaner.rb5
-rw-r--r--spec/support/finder_collection_allowlist.yml1
-rw-r--r--spec/support/helpers/content_editor_helpers.rb8
-rw-r--r--spec/support/helpers/content_security_policy_helpers.rb4
-rw-r--r--spec/support/helpers/dns_helpers.rb16
-rw-r--r--spec/support/helpers/fake_migration_classes.rb13
-rw-r--r--spec/support/helpers/features/dom_helpers.rb8
-rw-r--r--spec/support/helpers/graphql_helpers.rb5
-rw-r--r--spec/support/helpers/integrations/test_helpers.rb11
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb2
-rw-r--r--spec/support/helpers/listbox_helpers.rb4
-rw-r--r--spec/support/helpers/migrations_helpers/work_item_types_helper.rb4
-rw-r--r--spec/support/helpers/navbar_structure_helper.rb13
-rw-r--r--spec/support/helpers/prometheus/metric_builders.rb29
-rw-r--r--spec/support/helpers/stub_configuration.rb5
-rw-r--r--spec/support/helpers/stub_feature_flags.rb9
-rw-r--r--spec/support/helpers/stub_saas_features.rb20
-rw-r--r--spec/support/helpers/test_env.rb3
-rw-r--r--spec/support/helpers/unlock_pipelines_helpers.rb23
-rw-r--r--spec/support/helpers/usage_data_helpers.rb1
-rw-r--r--spec/support/matchers/pushed_licensed_features_matcher.rb29
-rw-r--r--spec/support/protected_branch_helpers.rb2
-rw-r--r--spec/support/rake.rb20
-rw-r--r--spec/support/rspec.rb22
-rw-r--r--spec/support/rspec_order_todo.yml49
-rw-r--r--spec/support/shared_contexts/bulk_imports_requests_shared_context.rb1
-rw-r--r--spec/support/shared_contexts/features/integrations/integrations_shared_context.rb67
-rw-r--r--spec/support/shared_contexts/merge_request_create_shared_context.rb4
-rw-r--r--spec/support/shared_contexts/merge_request_edit_shared_context.rb3
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/policies/project_policy_table_shared_context.rb25
-rw-r--r--spec/support/shared_contexts/requests/api/nuget_packages_shared_context.rb2
-rw-r--r--spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb32
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb7
-rw-r--r--spec/support/shared_examples/ci/deployable_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb26
-rw-r--r--spec/support/shared_examples/controllers/concerns/onboarding/redirectable_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/internal_event_tracking_examples.rb6
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb6
-rw-r--r--spec/support/shared_examples/features/variable_list_drawer_shared_examples.rb230
-rw-r--r--spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/features/work_items_shared_examples.rb106
-rw-r--r--spec/support/shared_examples/graphql/design_fields_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb57
-rw-r--r--spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/integrations/integration_settings_form.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb95
-rw-r--r--spec/support/shared_examples/metrics_instrumentation_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/migrations/swap_conversion_columns_shared_examples.rb117
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb54
-rw-r--r--spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb45
-rw-r--r--spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/models/issuable_link_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/namespaces/traversal_examples.rb8
-rw-r--r--spec/support/shared_examples/namespaces/traversal_scope_examples.rb43
-rw-r--r--spec/support/shared_examples/observability/embed_observabilities_examples.rb61
-rw-r--r--spec/support/shared_examples/prometheus/additional_metrics_shared_examples.rb161
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/redis/redis_shared_examples.rb64
-rw-r--r--spec/support/shared_examples/ref_extraction_shared_examples.rb165
-rw-r--r--spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb102
-rw-r--r--spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/requests/organizations_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb48
-rw-r--r--spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb55
-rw-r--r--spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/services/protected_branches_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb15
-rw-r--r--spec/support/shared_examples/work_item_related_link_restrictions_importer.rb39
-rw-r--r--spec/support/shared_examples/workers/gitlab/github_import/stage_methods_shared_examples.rb17
84 files changed, 1667 insertions, 787 deletions
diff --git a/spec/support/ability_check_todo.yml b/spec/support/ability_check_todo.yml
index eafd595b137..a317f49ea94 100644
--- a/spec/support/ability_check_todo.yml
+++ b/spec/support/ability_check_todo.yml
@@ -66,8 +66,6 @@ ProjectPolicy:
- create_test_case
- read_group_saml_identity
UserPolicy:
-- admin_observability
- admin_terraform_state
-- read_observability
# Permanent excludes (please provide a reason):
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 65abbe12621..78d7e57c208 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -51,15 +51,6 @@ Capybara.register_server :puma_via_workhorse do |app, port, host, **options|
file.close! # We just want the filename
TestEnv.with_workhorse(host, port, socket_path) do
- # In cases of multiple installations of chromedriver, prioritize the version installed by SeleniumManager
- # selenium-manager doesn't work with Linux arm64 yet:
- # https://github.com/SeleniumHQ/selenium/issues/11357
- if RUBY_PLATFORM.include?('x86_64-linux') || RUBY_PLATFORM.include?('darwin')
- chrome_options = Selenium::WebDriver::Chrome::Options.chrome
- chromedriver_path = File.dirname(Selenium::WebDriver::SeleniumManager.driver_path(chrome_options))
- ENV['PATH'] = "#{chromedriver_path}:#{ENV['PATH']}" # rubocop:disable RSpec/EnvAssignment
- end
-
Capybara.servers[:puma].call(app, nil, socket_path, **options)
end
end
diff --git a/spec/support/database/prevent_cross_joins.rb b/spec/support/database/prevent_cross_joins.rb
index 443216ba9df..3ff83e685ba 100644
--- a/spec/support/database/prevent_cross_joins.rb
+++ b/spec/support/database/prevent_cross_joins.rb
@@ -41,7 +41,7 @@ module Database
schemas = ::Gitlab::Database::GitlabSchema.table_schemas!(tables)
- unless ::Gitlab::Database::GitlabSchema.cross_joins_allowed?(schemas)
+ unless ::Gitlab::Database::GitlabSchema.cross_joins_allowed?(schemas, tables)
Thread.current[:has_cross_join_exception] = true
raise CrossJoinAcrossUnsupportedTablesError,
"Unsupported cross-join across '#{tables.join(", ")}' querying '#{schemas.to_a.join(", ")}' discovered " \
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 3131a22a20b..a1579ad1685 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -12,7 +12,10 @@ module DbCleaner
end
def deletion_except_tables
- %w[work_item_types work_item_hierarchy_restrictions work_item_widget_definitions]
+ %w[
+ work_item_types work_item_hierarchy_restrictions
+ work_item_widget_definitions work_item_related_link_restrictions
+ ]
end
def setup_database_cleaner
diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml
index 860045c6ce6..0af4de11d51 100644
--- a/spec/support/finder_collection_allowlist.yml
+++ b/spec/support/finder_collection_allowlist.yml
@@ -58,6 +58,7 @@
- Repositories::BranchNamesFinder
- Repositories::ChangelogTagFinder
- Repositories::TreeFinder
+- Sbom::DependencyLicensesFinder
- Security::FindingsFinder
- Security::PipelineVulnerabilitiesFinder
- Security::ScanExecutionPoliciesFinder
diff --git a/spec/support/helpers/content_editor_helpers.rb b/spec/support/helpers/content_editor_helpers.rb
index 7597a13e475..7e7ecc197fc 100644
--- a/spec/support/helpers/content_editor_helpers.rb
+++ b/spec/support/helpers/content_editor_helpers.rb
@@ -1,14 +1,6 @@
# frozen_string_literal: true
module ContentEditorHelpers
- def close_rich_text_promo_popover_if_present
- return unless page.has_css?("[data-testid='rich-text-promo-popover']")
-
- page.within("[data-testid='rich-text-promo-popover']") do
- click_button "Close"
- end
- end
-
def switch_to_markdown_editor
click_button("Switch to plain text editing")
end
diff --git a/spec/support/helpers/content_security_policy_helpers.rb b/spec/support/helpers/content_security_policy_helpers.rb
index 50a1bb62bc5..b12ebcbd4b9 100644
--- a/spec/support/helpers/content_security_policy_helpers.rb
+++ b/spec/support/helpers/content_security_policy_helpers.rb
@@ -24,8 +24,8 @@ any_time: false)
# ```
# find_csp_directive('connect-src')
# ```
- def find_csp_directive(key)
- csp = response.headers['Content-Security-Policy']
+ def find_csp_directive(key, header: nil)
+ csp = header || response.headers['Content-Security-Policy']
# Transform "default-src foo bar; connect-src foo bar; script-src ..."
# into array of values for a single directive based on the given key
diff --git a/spec/support/helpers/dns_helpers.rb b/spec/support/helpers/dns_helpers.rb
index c60c14f10a3..be26c80d217 100644
--- a/spec/support/helpers/dns_helpers.rb
+++ b/spec/support/helpers/dns_helpers.rb
@@ -52,4 +52,20 @@ module DnsHelpers
def db_hosts
ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).map(&:host).compact.uniq
end
+
+ def stub_resolver(stubbed_lookups = {})
+ resolver = instance_double('Resolv::DNS')
+ allow(resolver).to receive(:timeouts=)
+
+ expect(Resolv::DNS).to receive(:open).and_yield(resolver)
+
+ allow(resolver).to receive(:getresources).and_return([])
+ stubbed_lookups.each do |domain, records|
+ records = Array(records).map { |txt| Resolv::DNS::Resource::IN::TXT.new(txt) }
+ # Append '.' to domain_name, indicating absolute FQDN
+ allow(resolver).to receive(:getresources).with("#{domain}.", Resolv::DNS::Resource::IN::TXT) { records }
+ end
+
+ resolver
+ end
end
diff --git a/spec/support/helpers/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb
deleted file mode 100644
index 6c066b3b199..00000000000
--- a/spec/support/helpers/fake_migration_classes.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration[4.2]
- include Gitlab::Database::RenameReservedPathsMigration::V1
-
- def version
- '20170316163845'
- end
-
- def name
- "FakeRenameReservedPathMigrationV1"
- end
-end
diff --git a/spec/support/helpers/features/dom_helpers.rb b/spec/support/helpers/features/dom_helpers.rb
index ac6523f3360..cbbb80dde36 100644
--- a/spec/support/helpers/features/dom_helpers.rb
+++ b/spec/support/helpers/features/dom_helpers.rb
@@ -2,12 +2,12 @@
module Features
module DomHelpers
- def find_by_testid(testid)
- page.find("[data-testid='#{testid}']")
+ def find_by_testid(testid, **kwargs)
+ page.find("[data-testid='#{testid}']", **kwargs)
end
- def within_testid(testid, &block)
- page.within("[data-testid='#{testid}']", &block)
+ def within_testid(testid, **kwargs, &block)
+ page.within("[data-testid='#{testid}']", **kwargs, &block)
end
end
end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 19a637d4893..5eba982e3da 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -80,11 +80,11 @@ module GraphqlHelpers
# All resolution goes through fields, so we need to create one here that
# uses our resolver. Thankfully, apart from the field name, resolvers
# contain all the configuration needed to define one.
- field_options = resolver_class.field_options.merge(
+ field = ::Types::BaseField.new(
+ resolver_class: resolver_class,
owner: resolver_parent,
name: 'field_value'
)
- field = ::Types::BaseField.new(**field_options)
# All mutations accept a single `:input` argument. Wrap arguments here.
args = { input: args } if resolver_class <= ::Mutations::BaseMutation && !args.key?(:input)
@@ -221,6 +221,7 @@ module GraphqlHelpers
def resolver_instance(resolver_class, obj: nil, ctx: {}, field: nil, schema: GitlabSchema, subscription_update: false)
if ctx.is_a?(Hash)
q = double('Query', schema: schema, subscription_update?: subscription_update, warden: GraphQL::Schema::Warden::PassThruWarden)
+ allow(q).to receive(:after_lazy) { |value, &block| schema.after_lazy(value, &block) }
ctx = GraphQL::Query::Context.new(query: q, object: obj, values: ctx)
end
diff --git a/spec/support/helpers/integrations/test_helpers.rb b/spec/support/helpers/integrations/test_helpers.rb
new file mode 100644
index 00000000000..c7fde957316
--- /dev/null
+++ b/spec/support/helpers/integrations/test_helpers.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Integrations
+ module TestHelpers
+ def factory_for(integration)
+ return :integrations_slack if integration.is_a?(Integrations::Slack)
+
+ "#{integration.to_param}_integration".to_sym
+ end
+ end
+end
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index 417bf4366c5..191defc09ef 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -39,7 +39,7 @@ module JavaScriptFixturesHelpers
end
def remove_repository(project)
- Gitlab::Shell.new.remove_repository(project.repository_storage, project.disk_path)
+ project.repository.remove
end
# Public: Reads a GraphQL query from the filesystem as a string
diff --git a/spec/support/helpers/listbox_helpers.rb b/spec/support/helpers/listbox_helpers.rb
index e943790fc65..7a734d2b097 100644
--- a/spec/support/helpers/listbox_helpers.rb
+++ b/spec/support/helpers/listbox_helpers.rb
@@ -10,6 +10,10 @@ module ListboxHelpers
find('.gl-new-dropdown-item[role="option"]', text: text, exact_text: exact_text).click
end
+ def select_disclosure_dropdown_item(text, exact_text: false)
+ find('.gl-new-dropdown-item', text: text, exact_text: exact_text).click
+ end
+
def expect_listbox_item(text)
expect(page).to have_css('.gl-new-dropdown-item[role="option"]', text: text)
end
diff --git a/spec/support/helpers/migrations_helpers/work_item_types_helper.rb b/spec/support/helpers/migrations_helpers/work_item_types_helper.rb
index 40f84486537..9d114ae82b1 100644
--- a/spec/support/helpers/migrations_helpers/work_item_types_helper.rb
+++ b/spec/support/helpers/migrations_helpers/work_item_types_helper.rb
@@ -4,7 +4,11 @@ module MigrationHelpers
module WorkItemTypesHelper
def reset_work_item_types
Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types
+ WorkItems::HierarchyRestriction.reset_column_information
Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions
+ return unless WorkItems::RelatedLinkRestriction.table_exists?
+
+ Gitlab::DatabaseImporters::WorkItems::RelatedLinksRestrictionsImporter.upsert_restrictions
end
end
end
diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb
index 9a6af5fb8ae..fe39968b002 100644
--- a/spec/support/helpers/navbar_structure_helper.rb
+++ b/spec/support/helpers/navbar_structure_helper.rb
@@ -85,19 +85,6 @@ module NavbarStructureHelper
)
end
- def insert_observability_nav
- insert_after_nav_item(
- _('Kubernetes'),
- new_nav_item: {
- nav_item: _('Observability'),
- nav_sub_items: [
- _('Explore telemetry data'),
- _('Data sources')
- ]
- }
- )
- end
-
def insert_infrastructure_google_cloud_nav
insert_after_sub_nav_item(
s_('Terraform|Terraform states'),
diff --git a/spec/support/helpers/prometheus/metric_builders.rb b/spec/support/helpers/prometheus/metric_builders.rb
deleted file mode 100644
index 53329ee8dce..00000000000
--- a/spec/support/helpers/prometheus/metric_builders.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-module Prometheus
- module MetricBuilders
- def simple_query(suffix = 'a', **opts)
- { query_range: "query_range_#{suffix}" }.merge(opts)
- end
-
- def simple_queries
- [simple_query, simple_query('b', label: 'label', unit: 'unit')]
- end
-
- def simple_metric(title: 'title', required_metrics: [], queries: [simple_query])
- Gitlab::Prometheus::Metric.new(title: title, required_metrics: required_metrics, weight: 1, queries: queries)
- end
-
- def simple_metrics(added_metric_name: 'metric_a')
- [
- simple_metric(required_metrics: %W[#{added_metric_name} metric_b], queries: simple_queries),
- simple_metric(required_metrics: [added_metric_name], queries: [simple_query('empty')]),
- simple_metric(required_metrics: %w[metric_c])
- ]
- end
-
- def simple_metric_group(name: 'name', metrics: simple_metrics)
- Gitlab::Prometheus::MetricGroup.new(name: name, priority: 1, metrics: metrics)
- end
- end
-end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 4c997aceeee..562805cec3d 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -154,6 +154,11 @@ module StubConfiguration
stub_application_setting(maintenance_mode: value)
end
+ def stub_usage_ping_features(value)
+ stub_application_setting(usage_ping_enabled: value)
+ stub_application_setting(usage_ping_features_enabled: value)
+ end
+
private
# Modifies stubbed messages to also stub possible predicate versions
diff --git a/spec/support/helpers/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb
index 7cebda700d3..42bb9982144 100644
--- a/spec/support/helpers/stub_feature_flags.rb
+++ b/spec/support/helpers/stub_feature_flags.rb
@@ -25,6 +25,15 @@ module StubFeatureFlags
Feature.reset_flipper
end
+ def stub_with_new_feature_current_request
+ return unless Gitlab::SafeRequestStore.active?
+
+ new_request = Feature::FlipperRequest.new
+ allow(new_request).to receive(:id).and_return(SecureRandom.uuid)
+
+ allow(Feature).to receive(:current_request).and_return(new_request)
+ end
+
# Stub Feature flags with `flag_name: true/false`
#
# @param [Hash] features where key is feature name and value is boolean whether enabled or not.
diff --git a/spec/support/helpers/stub_saas_features.rb b/spec/support/helpers/stub_saas_features.rb
new file mode 100644
index 00000000000..e344888cb8c
--- /dev/null
+++ b/spec/support/helpers/stub_saas_features.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module StubSaasFeatures
+ # Stub SaaS feature with `feature_name: true/false`
+ #
+ # @param [Hash] features where key is feature name and value is boolean whether enabled or not.
+ #
+ # Examples
+ # - `stub_saas_features('onboarding' => false)` ... Disable `onboarding`
+ # SaaS feature globally.
+ # - `stub_saas_features('onboarding' => true)` ... Enable `onboarding`
+ # SaaS feature globally.
+ def stub_saas_features(features)
+ features.each do |feature_name, value|
+ raise ArgumentError, 'value must be boolean' unless value.in? [true, false]
+
+ allow(::Gitlab::Saas).to receive(:feature_available?).with(feature_name).and_return(value)
+ end
+ end
+end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index b95adb3fe4d..740abdb6cfa 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -394,8 +394,11 @@ module TestEnv
end
def seed_db
+ # Adjust `deletion_except_tables` method to exclude seeded tables from
+ # record deletions.
Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types
Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions
+ Gitlab::DatabaseImporters::WorkItems::RelatedLinksRestrictionsImporter.upsert_restrictions
end
private
diff --git a/spec/support/helpers/unlock_pipelines_helpers.rb b/spec/support/helpers/unlock_pipelines_helpers.rb
new file mode 100644
index 00000000000..342c2d72980
--- /dev/null
+++ b/spec/support/helpers/unlock_pipelines_helpers.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module UnlockPipelinesHelpers
+ def pipeline_ids_waiting_to_be_unlocked
+ Ci::UnlockPipelineRequest.with_redis do |redis|
+ redis.zrange(Ci::UnlockPipelineRequest::QUEUE_REDIS_KEY, 0, -1).map(&:to_i)
+ end
+ end
+
+ def expect_to_have_pending_unlock_pipeline_request(pipeline_id, timestamp)
+ Ci::UnlockPipelineRequest.with_redis do |redis|
+ timestamp_stored = redis.zscore(Ci::UnlockPipelineRequest::QUEUE_REDIS_KEY, pipeline_id)
+ expect(timestamp_stored).not_to be_nil
+ expect(timestamp_stored.to_i).to eq(timestamp)
+ end
+ end
+
+ def timestamp_of_pending_unlock_pipeline_request(pipeline_id)
+ Ci::UnlockPipelineRequest.with_redis do |redis|
+ redis.zscore(Ci::UnlockPipelineRequest::QUEUE_REDIS_KEY, pipeline_id).to_i
+ end
+ end
+end
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index 8ac3b0c134b..42e599c7510 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -76,7 +76,6 @@ module UsageDataHelpers
USAGE_DATA_KEYS = %i(
counts
- counts_monthly
recorded_at
mattermost_enabled
signup_enabled
diff --git a/spec/support/matchers/pushed_licensed_features_matcher.rb b/spec/support/matchers/pushed_licensed_features_matcher.rb
new file mode 100644
index 00000000000..b02863983bc
--- /dev/null
+++ b/spec/support/matchers/pushed_licensed_features_matcher.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :have_pushed_licensed_features do |expected|
+ def to_js(key, value)
+ "\"#{key}\":#{value}"
+ end
+
+ def html(actual)
+ actual.try(:html) || actual
+ end
+
+ match do |actual|
+ expected.all? do |licensed_feature_name, enabled|
+ html(actual).include?(to_js(licensed_feature_name, enabled))
+ end
+ end
+
+ failure_message do |actual|
+ missing = expected.select do |licensed_feature_name, enabled|
+ html(actual).exclude?(to_js(licensed_feature_name, enabled))
+ end
+
+ missing_licensed_features = missing.map do |licensed_feature_name, enabled|
+ to_js(licensed_feature_name, enabled)
+ end.join("\n")
+
+ "The following licensed feature(s) cannot be found in the frontend HTML source: #{missing_licensed_features}"
+ end
+end
diff --git a/spec/support/protected_branch_helpers.rb b/spec/support/protected_branch_helpers.rb
index db5118d6f88..49ede865876 100644
--- a/spec/support/protected_branch_helpers.rb
+++ b/spec/support/protected_branch_helpers.rb
@@ -3,7 +3,7 @@
module ProtectedBranchHelpers
def set_allowed_to(operation, option = 'Maintainers', form: '.js-new-protected-branch')
within(form) do
- within_select(".js-allowed-to-#{operation}") do
+ within_select(".js-allowed-to-#{operation}:not([disabled])") do
Array(option).each { |opt| click_on(opt) }
end
end
diff --git a/spec/support/rake.rb b/spec/support/rake.rb
new file mode 100644
index 00000000000..73590046f13
--- /dev/null
+++ b/spec/support/rake.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require_relative 'helpers/rake_helpers'
+
+RSpec.configure do |config|
+ config.include RakeHelpers, type: :task
+
+ config.before(:all, type: :task) do
+ require 'rake'
+
+ Rake.application.rake_require 'tasks/gitlab/helpers'
+ Rake::Task.define_task :environment
+ end
+
+ config.after(:all, type: :task) do
+ # Fast specs cannot load `spec/support/database_cleaner` and its RSpec
+ # helper DbCleaner.
+ delete_from_all_tables!(except: deletion_except_tables) if defined?(DbCleaner)
+ end
+end
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index 7f3aa55fb1d..f2f93fff07e 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -1,11 +1,12 @@
# frozen_string_literal: true
-require_relative "rspec_order"
-require_relative "system_exit_detected"
-require_relative "helpers/stub_configuration"
-require_relative "helpers/stub_metrics"
-require_relative "helpers/stub_object_storage"
-require_relative "helpers/fast_rails_root"
+require_relative 'rake'
+require_relative 'rspec_order'
+require_relative 'system_exit_detected'
+require_relative 'helpers/stub_configuration'
+require_relative 'helpers/stub_metrics'
+require_relative 'helpers/stub_object_storage'
+require_relative 'helpers/fast_rails_root'
require 'gitlab/rspec/all'
require 'gitlab/utils/all'
@@ -19,6 +20,15 @@ RSpec.configure do |config|
# Re-run failures locally with `--only-failures`
config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', './spec/examples.txt')
+ config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata|
+ # Infer metadata tag `type` if not already inferred by
+ # `infer_spec_type_from_file_location!`.
+ unless metadata.key?(:type)
+ match = %r{/spec/([^/]+)/}.match(metadata[:location])
+ metadata[:type] = match[1].singularize.to_sym if match
+ end
+ end
+
# Makes diffs show entire non-truncated values.
config.around(:each, :unlimited_max_formatted_output_length) do |example|
old_max_formatted_output_length = RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 298f4006c3b..51f3ff2c077 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -26,7 +26,6 @@
- './ee/spec/controllers/admin/licenses/usage_exports_controller_spec.rb'
- './ee/spec/controllers/admin/projects_controller_spec.rb'
- './ee/spec/controllers/admin/push_rules_controller_spec.rb'
-- './ee/spec/controllers/admin/runners_controller_spec.rb'
- './ee/spec/controllers/admin/users_controller_spec.rb'
- './ee/spec/controllers/autocomplete_controller_spec.rb'
- './ee/spec/controllers/concerns/ee/routable_actions/sso_enforcement_redirect_spec.rb'
@@ -87,7 +86,6 @@
- './ee/spec/controllers/groups/omniauth_callbacks_controller_spec.rb'
- './ee/spec/controllers/groups/push_rules_controller_spec.rb'
- './ee/spec/controllers/groups/roadmap_controller_spec.rb'
-- './ee/spec/controllers/groups/runners_controller_spec.rb'
- './ee/spec/controllers/groups/saml_group_links_controller_spec.rb'
- './ee/spec/controllers/groups/saml_providers_controller_spec.rb'
- './ee/spec/controllers/groups/scim_oauth_controller_spec.rb'
@@ -142,7 +140,6 @@
- './ee/spec/controllers/projects/quality/test_cases_controller_spec.rb'
- './ee/spec/controllers/projects/repositories_controller_spec.rb'
- './ee/spec/controllers/projects/requirements_management/requirements_controller_spec.rb'
-- './ee/spec/controllers/projects/runners_controller_spec.rb'
- './ee/spec/controllers/projects/security/api_fuzzing_configuration_controller_spec.rb'
- './ee/spec/controllers/projects/security/configuration_controller_spec.rb'
- './ee/spec/controllers/projects/security/dashboard_controller_spec.rb'
@@ -2355,7 +2352,6 @@
- './ee/spec/serializers/evidences/build_artifact_entity_spec.rb'
- './ee/spec/serializers/evidences/evidence_entity_spec.rb'
- './ee/spec/serializers/fork_namespace_entity_spec.rb'
-- './ee/spec/serializers/geo_project_registry_entity_spec.rb'
- './ee/spec/serializers/group_vulnerability_autocomplete_entity_spec.rb'
- './ee/spec/serializers/integrations/field_entity_spec.rb'
- './ee/spec/serializers/integrations/jira_serializers/issue_detail_entity_spec.rb'
@@ -2742,7 +2738,7 @@
- './ee/spec/services/gitlab_subscriptions/activate_service_spec.rb'
- './ee/spec/services/gitlab_subscriptions/check_future_renewal_service_spec.rb'
- './ee/spec/services/gitlab_subscriptions/create_service_spec.rb'
-- './ee/spec/services/gitlab_subscriptions/create_trial_or_lead_service_spec.rb'
+- './ee/spec/services/gitlab_subscriptions/create_company_lead_service_spec.rb'
- './ee/spec/services/gitlab_subscriptions/fetch_purchase_eligible_namespaces_service_spec.rb'
- './ee/spec/services/gitlab_subscriptions/fetch_subscription_plans_service_spec.rb'
- './ee/spec/services/gitlab_subscriptions/plan_upgrade_service_spec.rb'
@@ -3154,18 +3150,7 @@
- './ee/spec/workers/geo/prune_event_log_worker_spec.rb'
- './ee/spec/workers/geo/registry_sync_worker_spec.rb'
- './ee/spec/workers/geo/repositories_clean_up_worker_spec.rb'
-- './ee/spec/workers/geo/repository_cleanup_worker_spec.rb'
-- './ee/spec/workers/geo_repository_destroy_worker_spec.rb'
-- './ee/spec/workers/geo/repository_shard_sync_worker_spec.rb'
-- './ee/spec/workers/geo/repository_sync_worker_spec.rb'
-- './ee/spec/workers/geo/repository_verification/primary/batch_worker_spec.rb'
-- './ee/spec/workers/geo/repository_verification/primary/shard_worker_spec.rb'
-- './ee/spec/workers/geo/repository_verification/primary/single_worker_spec.rb'
-- './ee/spec/workers/geo/repository_verification/secondary/scheduler_worker_spec.rb'
-- './ee/spec/workers/geo/repository_verification/secondary/shard_worker_spec.rb'
-- './ee/spec/workers/geo/repository_verification/secondary/single_worker_spec.rb'
- './ee/spec/workers/geo/reverification_batch_worker_spec.rb'
-- './ee/spec/workers/geo/scheduler/per_shard_scheduler_worker_spec.rb'
- './ee/spec/workers/geo/scheduler/scheduler_worker_spec.rb'
- './ee/spec/workers/geo/secondary/registry_consistency_worker_spec.rb'
- './ee/spec/workers/geo/secondary_usage_data_cron_worker_spec.rb'
@@ -3277,7 +3262,6 @@
- './spec/controllers/admin/jobs_controller_spec.rb'
- './spec/controllers/admin/plan_limits_controller_spec.rb'
- './spec/controllers/admin/projects_controller_spec.rb'
-- './spec/controllers/admin/runners_controller_spec.rb'
- './spec/controllers/admin/sessions_controller_spec.rb'
- './spec/controllers/admin/spam_logs_controller_spec.rb'
- './spec/controllers/admin/topics/avatars_controller_spec.rb'
@@ -3348,7 +3332,6 @@
- './spec/controllers/groups/packages_controller_spec.rb'
- './spec/controllers/groups/registry/repositories_controller_spec.rb'
- './spec/controllers/groups/releases_controller_spec.rb'
-- './spec/controllers/groups/runners_controller_spec.rb'
- './spec/controllers/groups/settings/applications_controller_spec.rb'
- './spec/controllers/groups/settings/ci_cd_controller_spec.rb'
- './spec/controllers/groups/settings/integrations_controller_spec.rb'
@@ -3474,7 +3457,6 @@
- './spec/controllers/projects/releases_controller_spec.rb'
- './spec/controllers/projects/releases/evidences_controller_spec.rb'
- './spec/controllers/projects/repositories_controller_spec.rb'
-- './spec/controllers/projects/runners_controller_spec.rb'
- './spec/controllers/projects/security/configuration_controller_spec.rb'
- './spec/controllers/projects/service_desk_controller_spec.rb'
- './spec/controllers/projects/service_ping_controller_spec.rb'
@@ -4989,8 +4971,6 @@
- './spec/helpers/breadcrumbs_helper_spec.rb'
- './spec/helpers/button_helper_spec.rb'
- './spec/helpers/calendar_helper_spec.rb'
-- './spec/helpers/ci/builds_helper_spec.rb'
-- './spec/helpers/ci/jobs_helper_spec.rb'
- './spec/helpers/ci/pipeline_editor_helper_spec.rb'
- './spec/helpers/ci/pipelines_helper_spec.rb'
- './spec/helpers/ci/secure_files_helper_spec.rb'
@@ -6250,17 +6230,6 @@
- './spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb'
- './spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb'
- './spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb'
-- './spec/lib/gitlab/email/message/build_ios_app_guide_spec.rb'
-- './spec/lib/gitlab/email/message/in_product_marketing/admin_verify_spec.rb'
-- './spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb'
-- './spec/lib/gitlab/email/message/in_product_marketing/create_spec.rb'
-- './spec/lib/gitlab/email/message/in_product_marketing/helper_spec.rb'
-- './spec/lib/gitlab/email/message/in_product_marketing_spec.rb'
-- './spec/lib/gitlab/email/message/in_product_marketing/team_short_spec.rb'
-- './spec/lib/gitlab/email/message/in_product_marketing/team_spec.rb'
-- './spec/lib/gitlab/email/message/in_product_marketing/trial_short_spec.rb'
-- './spec/lib/gitlab/email/message/in_product_marketing/trial_spec.rb'
-- './spec/lib/gitlab/email/message/in_product_marketing/verify_spec.rb'
- './spec/lib/gitlab/email/message/repository_push_spec.rb'
- './spec/lib/gitlab/email/receiver_spec.rb'
- './spec/lib/gitlab/email/reply_parser_spec.rb'
@@ -7274,7 +7243,6 @@
- './spec/mailers/emails/admin_notification_spec.rb'
- './spec/mailers/emails/auto_devops_spec.rb'
- './spec/mailers/emails/groups_spec.rb'
-- './spec/mailers/emails/in_product_marketing_spec.rb'
- './spec/mailers/emails/issues_spec.rb'
- './spec/mailers/emails/merge_requests_spec.rb'
- './spec/mailers/emails/pages_domains_spec.rb'
@@ -7763,7 +7731,6 @@
- './spec/models/loose_foreign_keys/modification_tracker_spec.rb'
- './spec/models/members/group_member_spec.rb'
- './spec/models/members/last_group_owner_assigner_spec.rb'
-- './spec/models/members/member_task_spec.rb'
- './spec/models/member_spec.rb'
- './spec/models/members/project_member_spec.rb'
- './spec/models/merge_request/approval_removal_settings_spec.rb'
@@ -7975,7 +7942,6 @@
- './spec/models/users/callout_spec.rb'
- './spec/models/users/credit_card_validation_spec.rb'
- './spec/models/users/group_callout_spec.rb'
-- './spec/models/users/in_product_marketing_email_spec.rb'
- './spec/models/users/merge_request_interaction_spec.rb'
- './spec/models/user_spec.rb'
- './spec/models/users/project_callout_spec.rb'
@@ -9387,8 +9353,6 @@
- './spec/services/pages_domains/create_acme_order_service_spec.rb'
- './spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb'
- './spec/services/pages_domains/retry_acme_order_service_spec.rb'
-- './spec/services/pages/migrate_from_legacy_storage_service_spec.rb'
-- './spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb'
- './spec/services/pages/zip_directory_service_spec.rb'
- './spec/services/personal_access_tokens/create_service_spec.rb'
- './spec/services/personal_access_tokens/last_used_service_spec.rb'
@@ -9433,14 +9397,10 @@
- './spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb'
- './spec/services/projects/hashed_storage/migrate_repository_service_spec.rb'
- './spec/services/projects/hashed_storage/migration_service_spec.rb'
-- './spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb'
-- './spec/services/projects/hashed_storage/rollback_repository_service_spec.rb'
-- './spec/services/projects/hashed_storage/rollback_service_spec.rb'
- './spec/services/projects/import_error_filter_spec.rb'
- './spec/services/projects/import_export/export_service_spec.rb'
- './spec/services/projects/import_export/relation_export_service_spec.rb'
- './spec/services/projects/import_service_spec.rb'
-- './spec/services/projects/in_product_marketing_campaign_emails_service_spec.rb'
- './spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb'
- './spec/services/projects/lfs_pointers/lfs_download_service_spec.rb'
- './spec/services/projects/lfs_pointers/lfs_import_service_spec.rb'
@@ -9550,7 +9510,6 @@
- './spec/services/tags/create_service_spec.rb'
- './spec/services/tags/destroy_service_spec.rb'
- './spec/services/task_list_toggle_service_spec.rb'
-- './spec/services/tasks_to_be_done/base_service_spec.rb'
- './spec/services/terraform/remote_state_handler_spec.rb'
- './spec/services/terraform/states/destroy_service_spec.rb'
- './spec/services/terraform/states/trigger_destroy_service_spec.rb'
@@ -9590,7 +9549,6 @@
- './spec/services/users/dismiss_project_callout_service_spec.rb'
- './spec/services/users/email_verification/generate_token_service_spec.rb'
- './spec/services/users/email_verification/validate_token_service_spec.rb'
-- './spec/services/users/in_product_marketing_email_records_spec.rb'
- './spec/services/users/keys_count_service_spec.rb'
- './spec/services/users/last_push_event_service_spec.rb'
- './spec/services/users/refresh_authorized_projects_service_spec.rb'
@@ -9690,7 +9648,6 @@
- './spec/tasks/gitlab/sidekiq_rake_spec.rb'
- './spec/tasks/gitlab/smtp_rake_spec.rb'
- './spec/tasks/gitlab/snippets_rake_spec.rb'
-- './spec/tasks/gitlab/storage_rake_spec.rb'
- './spec/tasks/gitlab/task_helpers_spec.rb'
- './spec/tasks/gitlab/terraform/migrate_rake_spec.rb'
- './spec/tasks/gitlab/update_templates_rake_spec.rb'
@@ -10066,10 +10023,6 @@
- './spec/workers/group_export_worker_spec.rb'
- './spec/workers/group_import_worker_spec.rb'
- './spec/workers/groups/update_statistics_worker_spec.rb'
-- './spec/workers/hashed_storage/migrator_worker_spec.rb'
-- './spec/workers/hashed_storage/project_migrate_worker_spec.rb'
-- './spec/workers/hashed_storage/project_rollback_worker_spec.rb'
-- './spec/workers/hashed_storage/rollbacker_worker_spec.rb'
- './spec/workers/import_issues_csv_worker_spec.rb'
- './spec/workers/integrations/create_external_cross_reference_worker_spec.rb'
- './spec/workers/integrations/execute_worker_spec.rb'
diff --git a/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb b/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb
index 7074b073a0c..997ed448d4d 100644
--- a/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb
+++ b/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb
@@ -23,7 +23,6 @@ RSpec.shared_context 'bulk imports requests context' do |url|
headers: { 'Content-Type' => 'application/json' })
stub_request(:get, "https://gitlab.example.com/api/v4/groups?min_access_level=50&page=1&per_page=20&private_token=demo-pat&search=test&top_level_only=true")
- .with(headers: request_headers)
.to_return(
status: 200,
body: [{
diff --git a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
index 848e333d88b..e4c97fa1143 100644
--- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
+++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
@@ -5,12 +5,14 @@
# The following let binding should be defined:
# - `integration`: Integration name. See `Integration.available_integration_names`.
RSpec.shared_context 'with integration' do
+ include Integrations::TestHelpers
include JiraIntegrationHelpers
let(:dashed_integration) { integration.dasherize }
let(:integration_method) { Project.integration_association_name(integration) }
let(:integration_klass) { Integration.integration_name_to_model(integration) }
let(:integration_instance) { integration_klass.new }
+ let(:integration_factory) { factory_for(integration_instance) }
# Build a list of all attributes that an integration supports.
let(:integration_attrs_list) do
@@ -44,62 +46,6 @@ RSpec.shared_context 'with integration' do
}
end
- let(:integration_attrs) do
- integration_attrs_list.inject({}) do |hash, k|
- if k =~ /^(token*|.*_token|.*_key)/ && !integration.in?(%w[apple_app_store google_play])
- hash.merge!(k => 'secrettoken')
- elsif integration == 'confluence' && k == :confluence_url
- hash.merge!(k => 'https://example.atlassian.net/wiki')
- elsif integration == 'datadog' && k == :datadog_site
- hash.merge!(k => 'datadoghq.com')
- elsif integration == 'datadog' && k == :datadog_tags
- hash.merge!(k => 'key:value')
- elsif integration == 'packagist' && k == :server
- hash.merge!(k => 'https://packagist.example.com')
- elsif /^(.*_url|url|webhook)/.match?(k)
- hash.merge!(k => "http://example.com")
- elsif integration_klass.method_defined?("#{k}?")
- hash.merge!(k => true)
- elsif integration == 'irker' && k == :recipients
- hash.merge!(k => 'irc://irc.network.net:666/#channel')
- elsif integration == 'irker' && k == :server_port
- hash.merge!(k => 1234)
- elsif integration == 'jira' && k == :jira_issue_transition_id
- hash.merge!(k => '1,2,3')
- elsif integration == 'jira' && k == :jira_issue_transition_automatic # rubocop:disable Lint/DuplicateBranch
- hash.merge!(k => true)
- elsif integration == 'jira' && k == :jira_auth_type # rubocop:disable Lint/DuplicateBranch
- hash.merge!(k => 0)
- elsif integration == 'emails_on_push' && k == :recipients
- hash.merge!(k => 'foo@bar.com')
- elsif (integration == 'slack' || integration == 'mattermost') && k == :labels_to_be_notified_behavior
- hash.merge!(k => "match_any")
- elsif integration == 'campfire' && k == :room
- hash.merge!(k => '1234')
- elsif integration == 'apple_app_store' && k == :app_store_issuer_id
- hash.merge!(k => 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee')
- elsif integration == 'apple_app_store' && k == :app_store_private_key
- hash.merge!(k => File.read('spec/fixtures/ssl_key.pem'))
- elsif integration == 'apple_app_store' && k == :app_store_key_id
- hash.merge!(k => 'ABC1')
- elsif integration == 'apple_app_store' && k == :app_store_private_key_file_name
- hash.merge!(k => 'ssl_key.pem')
- elsif integration == 'apple_app_store' && k == :app_store_protected_refs # rubocop:disable Lint/DuplicateBranch
- hash.merge!(k => true)
- elsif integration == 'google_play' && k == :package_name
- hash.merge!(k => 'com.gitlab.foo.bar')
- elsif integration == 'google_play' && k == :service_account_key
- hash.merge!(k => File.read('spec/fixtures/service_account.json'))
- elsif integration == 'google_play' && k == :service_account_key_file_name
- hash.merge!(k => 'service_account.json')
- elsif integration == 'google_play' && k == :google_play_protected_refs # rubocop:disable Lint/DuplicateBranch
- hash.merge!(k => true)
- else
- hash.merge!(k => "someword")
- end
- end
- end
-
let(:licensed_features) do
{
'github' => :github_integration
@@ -111,15 +57,6 @@ RSpec.shared_context 'with integration' do
stub_jira_integration_test if integration == 'jira'
end
- def initialize_integration(integration, attrs = {})
- record = project.find_or_initialize_integration(integration)
- record.reset_updated_properties if integration == 'datadog'
- record.attributes = attrs
- record.properties = integration_attrs
- record.save!
- record
- end
-
private
def enable_license_for_integration(integration)
diff --git a/spec/support/shared_contexts/merge_request_create_shared_context.rb b/spec/support/shared_contexts/merge_request_create_shared_context.rb
index bf8eeeb7ab6..fc9a3767365 100644
--- a/spec/support/shared_contexts/merge_request_create_shared_context.rb
+++ b/spec/support/shared_contexts/merge_request_create_shared_context.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
RSpec.shared_context 'merge request create context' do
- include ContentEditorHelpers
-
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:target_project) { create(:project, :public, :repository) }
@@ -25,7 +23,5 @@ RSpec.shared_context 'merge request create context' do
source_branch: 'fix',
target_branch: 'master'
})
-
- close_rich_text_promo_popover_if_present
end
end
diff --git a/spec/support/shared_contexts/merge_request_edit_shared_context.rb b/spec/support/shared_contexts/merge_request_edit_shared_context.rb
index 8fe0174b13e..f0e89b0c5f9 100644
--- a/spec/support/shared_contexts/merge_request_edit_shared_context.rb
+++ b/spec/support/shared_contexts/merge_request_edit_shared_context.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
RSpec.shared_context 'merge request edit context' do
- include ContentEditorHelpers
-
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:milestone) { create(:milestone, project: target_project) }
@@ -27,6 +25,5 @@ RSpec.shared_context 'merge request edit context' do
sign_in(user)
visit edit_project_merge_request_path(target_project, merge_request)
- close_rich_text_promo_popover_if_present
end
end
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index 70b48322efd..4564fa23236 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -19,7 +19,7 @@ RSpec.shared_context 'GroupPolicy context' do
let(:guest_permissions) do
%i[
- read_label read_group upload_file read_namespace read_group_activity
+ read_label read_group upload_file read_namespace read_namespace_via_membership read_group_activity
read_group_issues read_group_boards read_group_labels read_group_milestones
read_group_merge_requests
]
diff --git a/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
index d9ea7bc7f82..11f6d816fc1 100644
--- a/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
@@ -72,6 +72,31 @@ RSpec.shared_context 'ProjectPolicyTable context' do
:private | :disabled | :anonymous | nil | 0
end
+ # group_level, :membership, :admin_mode, :expected_count
+ # We need a new table because epics are at a group level only.
+ def permission_table_for_epics_access
+ :public | :admin | true | 1
+ :public | :admin | false | 1
+ :public | :reporter | nil | 1
+ :public | :guest | nil | 1
+ :public | :non_member | nil | 1
+ :public | :anonymous | nil | 1
+
+ :internal | :admin | true | 1
+ :internal | :admin | false | 0
+ :internal | :reporter | nil | 0
+ :internal | :guest | nil | 0
+ :internal | :non_member | nil | 0
+ :internal | :anonymous | nil | 0
+
+ :private | :admin | true | 1
+ :private | :admin | false | 0
+ :private | :reporter | nil | 0
+ :private | :guest | nil | 0
+ :private | :non_member | nil | 0
+ :private | :anonymous | nil | 0
+ end
+
# project_level, :feature_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_guest_feature_access
:public | :enabled | :admin | true | 1
diff --git a/spec/support/shared_contexts/requests/api/nuget_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/nuget_packages_shared_context.rb
index f877d6299bd..2543195e779 100644
--- a/spec/support/shared_contexts/requests/api/nuget_packages_shared_context.rb
+++ b/spec/support/shared_contexts/requests/api/nuget_packages_shared_context.rb
@@ -6,5 +6,7 @@ RSpec.shared_context 'nuget api setup' do
include HttpBasicAuthHelpers
let_it_be(:user) { create(:user) }
+ let_it_be_with_reload(:project) { create(:project, :public) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be_with_reload(:job) { create(:ci_build, user: user, status: :running, project: project) }
end
diff --git a/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb b/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb
index ef9830fbce8..0e7b909fce9 100644
--- a/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb
+++ b/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb
@@ -11,7 +11,9 @@ RSpec.shared_examples 'unlicensed cycle analytics request params' do
}
end
- subject { described_class.new(params) }
+ let(:request_params) { described_class.new(params) }
+
+ subject { request_params }
before do
root_group.add_owner(user)
@@ -114,13 +116,13 @@ RSpec.shared_examples 'unlicensed cycle analytics request params' do
end
describe 'use_aggregated_data_collector param' do
- subject(:value) { described_class.new(params).to_data_collector_params[:use_aggregated_data_collector] }
+ subject(:value) { request_params.to_data_collector_params[:use_aggregated_data_collector] }
it { is_expected.to eq(false) }
end
describe 'feature availablity data attributes' do
- subject(:value) { described_class.new(params).to_data_attributes }
+ subject(:value) { request_params.to_data_attributes }
it 'disables all paid features' do
is_expected.to match(a_hash_including(enable_tasks_by_type_chart: 'false',
@@ -128,4 +130,28 @@ RSpec.shared_examples 'unlicensed cycle analytics request params' do
enable_projects_filter: 'false'))
end
end
+
+ describe '#to_data_collector_params' do
+ context 'when adding licensed parameters' do
+ subject(:data_collector_params) { request_params.to_data_collector_params }
+
+ before do
+ params.merge!(
+ weight: 1,
+ epic_id: 2,
+ iteration_id: 3,
+ my_reaction_emoji: 'thumbsup',
+ not: { assignee_username: 'test' }
+ )
+ end
+
+ it 'excludes the attributes from the data collector params' do
+ expect(data_collector_params).to exclude(:weight)
+ expect(data_collector_params).to exclude(:epic_id)
+ expect(data_collector_params).to exclude(:iteration_id)
+ expect(data_collector_params).to exclude(:my_reaction_emoji)
+ expect(data_collector_params).to exclude(:not)
+ end
+ end
+ 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 ddd3bbd636a..c86fcf5ae20 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
@@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.shared_examples 'multiple issue boards' do
+ include ListboxHelpers
+
context 'authorized user' do
before do
stub_feature_flags(apollo_boards: false)
@@ -27,7 +29,7 @@ RSpec.shared_examples 'multiple issue boards' do
it 'switches current board' do
in_boards_switcher_dropdown do
- click_button board2.name
+ select_listbox_item(board2.name)
end
wait_for_requests
@@ -67,7 +69,7 @@ RSpec.shared_examples 'multiple issue boards' do
it 'adds a list to the none default board' do
in_boards_switcher_dropdown do
- click_button board2.name
+ select_listbox_item(board2.name)
end
wait_for_requests
@@ -89,7 +91,7 @@ RSpec.shared_examples 'multiple issue boards' do
expect(page).to have_selector('.board', count: 3)
in_boards_switcher_dropdown do
- click_button board.name
+ select_listbox_item(board.name)
end
wait_for_requests
@@ -101,7 +103,7 @@ RSpec.shared_examples 'multiple issue boards' do
assert_boards_nav_active
in_boards_switcher_dropdown do
- click_button board2.name
+ select_listbox_item(board2.name)
end
assert_boards_nav_active
@@ -109,7 +111,7 @@ RSpec.shared_examples 'multiple issue boards' do
it 'switches current board back' do
in_boards_switcher_dropdown do
- click_button board.name
+ select_listbox_item(board.name)
end
wait_for_requests
@@ -142,7 +144,7 @@ RSpec.shared_examples 'multiple issue boards' do
it 'switches current board' do
in_boards_switcher_dropdown do
- click_button board2.name
+ select_listbox_item(board2.name)
end
wait_for_requests
@@ -165,7 +167,7 @@ RSpec.shared_examples 'multiple issue boards' do
wait_for_requests
- dropdown_selector = '[data-testid="boards-selector"] .dropdown-menu'
+ dropdown_selector = '[data-testid="boards-selector"] .gl-new-dropdown'
page.within(dropdown_selector) do
yield
end
diff --git a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
index a9edf18d562..5c1f505d300 100644
--- a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
+++ b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
@@ -52,7 +52,6 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
subject.run
- expect(tracker.failed?).to eq(true)
expect(tracker.entity.failures.first).to be_present
expect(tracker.entity.failures.first.exception_message).to eq('Only allowed schemes are http, https')
end
@@ -97,11 +96,7 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
context 'when response is not 403' do
let(:response_double) { instance_double(HTTParty::Response, forbidden?: false, not_found?: false, code: 301) }
- it 'marks tracker as failed' do
- subject.run
-
- expect(tracker.failed?).to eq(true)
- end
+ include_examples 'does not raise an error'
end
end
end
diff --git a/spec/support/shared_examples/ci/deployable_shared_examples.rb b/spec/support/shared_examples/ci/deployable_shared_examples.rb
index 4f43d38e604..0781eec1b4b 100644
--- a/spec/support/shared_examples/ci/deployable_shared_examples.rb
+++ b/spec/support/shared_examples/ci/deployable_shared_examples.rb
@@ -166,6 +166,28 @@ RSpec.shared_examples 'a deployable job' do
expect(deployment).to be_failed
end
+
+ context 'when the job is a stop job' do
+ before do
+ job.update!(environment: 'review', options: { environment: { action: 'stop' } })
+ end
+
+ it 'enqueues Environments::StopJobFailedWorker' do
+ expect(Environments::StopJobFailedWorker)
+ .to receive(:perform_async)
+
+ subject
+ end
+ end
+
+ context 'when the job is not a stop job' do
+ it 'does not enqueue Environments::StopJobFailedWorker' do
+ expect(Environments::StopJobFailedWorker)
+ .not_to receive(:perform_async)
+
+ subject
+ end
+ end
end
context 'when transits to skipped' do
diff --git a/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb b/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
index 14d0ac81250..53d80c64827 100644
--- a/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
+++ b/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
@@ -79,7 +79,7 @@ RSpec.shared_examples 'every metric definition' do
end
it 'is included in the Usage Ping hash structure' do
- msg = "see https://docs.gitlab.com/ee/development/service_ping/metrics_dictionary.html#metrics-added-dynamic-to-service-ping-payload"
+ msg = "see https://docs.gitlab.com/ee/development/internal_analytics/metrics/metrics_dictionary.html#metrics-added-dynamic-to-service-ping-payload"
expect(expected_metric_files_key_paths).to match_array(usage_ping_key_paths), msg
end
@@ -114,7 +114,8 @@ RSpec.shared_examples 'every metric definition' do
Gitlab::Usage::Metrics::Instrumentations::RedisMetric,
Gitlab::Usage::Metrics::Instrumentations::RedisHLLMetric,
Gitlab::Usage::Metrics::Instrumentations::NumbersMetric,
- Gitlab::Usage::Metrics::Instrumentations::PrometheusMetric
+ Gitlab::Usage::Metrics::Instrumentations::PrometheusMetric,
+ Gitlab::Usage::Metrics::Instrumentations::TotalCountMetric
]
end
@@ -125,10 +126,23 @@ RSpec.shared_examples 'every metric definition' do
].freeze
end
+ let(:removed_classes) do
+ [
+ Gitlab::Usage::Metrics::Instrumentations::InProductMarketingEmailCtaClickedMetric,
+ Gitlab::Usage::Metrics::Instrumentations::InProductMarketingEmailSentMetric
+ ].freeze
+ end
+
+ def metric_not_used?(constant)
+ parent_metric_classes.include?(constant) ||
+ ignored_classes.include?(constant) ||
+ removed_classes.include?(constant)
+ end
+
def assert_uses_all_nested_classes(parent_module)
parent_module.constants(false).each do |const_name|
constant = parent_module.const_get(const_name, false)
- next if parent_metric_classes.include?(constant) || ignored_classes.include?(constant)
+ next if metric_not_used?(constant)
case constant
when Class
diff --git a/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb b/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb
new file mode 100644
index 00000000000..5f236f25d35
--- /dev/null
+++ b/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+# Requires `request` subject to be defined
+#
+# subject(:request) { get root_path }
+RSpec.shared_examples 'Base action controller' do
+ describe 'security headers' do
+ describe 'Cross-Origin-Opener-Policy' do
+ it 'sets the header' do
+ request
+
+ expect(response.headers['Cross-Origin-Opener-Policy']).to eq('same-origin')
+ end
+
+ context 'when coop_header feature flag is disabled' do
+ it 'does not set the header' do
+ stub_feature_flags(coop_header: false)
+
+ request
+
+ expect(response.headers['Cross-Origin-Opener-Policy']).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/concerns/onboarding/redirectable_shared_examples.rb b/spec/support/shared_examples/controllers/concerns/onboarding/redirectable_shared_examples.rb
new file mode 100644
index 00000000000..b448ea16128
--- /dev/null
+++ b/spec/support/shared_examples/controllers/concerns/onboarding/redirectable_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Onboarding::Redirectable do
+ it { is_expected.to redirect_to dashboard_projects_path }
+
+ context 'when the new user already has any accepted group membership' do
+ let!(:single_member) { create(:group_member, invite_email: email) }
+
+ it 'redirects to activity group path with a flash message' do
+ post_create
+
+ expect(response).to redirect_to activity_group_path(single_member.source)
+ expect(controller).to set_flash[:notice].to(/You have been granted/)
+ end
+
+ context 'when the new user already has more than 1 accepted group membership' do
+ let!(:last_member) { create(:group_member, invite_email: email) }
+
+ it 'redirects to the last member activity group path without a flash message' do
+ post_create
+
+ expect(response).to redirect_to activity_group_path(last_member.source)
+ expect(controller).not_to set_flash[:notice].to(/You have been granted/)
+ end
+ end
+
+ context 'when the member has an orphaned source at the time of registering' do
+ before do
+ single_member.source.delete
+ end
+
+ it { is_expected.to redirect_to dashboard_projects_path }
+ 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 af1843bae28..c921da10347 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
@@ -161,8 +161,6 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
group.add_owner(user)
client = stub_client(repos: repos, orgs: [org], org_repos: [org_repo])
allow(client).to receive(:each_page).and_return([double('client', objects: repos)].to_enum)
- # GitHub controller has filtering done using GitHub Search API
- stub_feature_flags(remove_legacy_github_client: false)
end
it 'filters list of repositories by name' do
diff --git a/spec/support/shared_examples/controllers/internal_event_tracking_examples.rb b/spec/support/shared_examples/controllers/internal_event_tracking_examples.rb
index 05068cd60af..0c19865999f 100644
--- a/spec/support/shared_examples/controllers/internal_event_tracking_examples.rb
+++ b/spec/support/shared_examples/controllers/internal_event_tracking_examples.rb
@@ -2,7 +2,7 @@
# Requires a context containing:
# - subject
-# - action
+# - event
# - user
# Optionally, the context can contain:
# - project
@@ -36,13 +36,13 @@ RSpec.shared_examples 'internal event tracking' do
expect(Gitlab::Tracking::ServicePingContext)
.to have_received(:new)
- .with(data_source: :redis_hll, event: action)
+ .with(data_source: :redis_hll, event: event)
.at_least(:once)
expect(fake_tracker).to have_received(:event)
.with(
'InternalEventTracking',
- action,
+ event,
context: [
an_instance_of(SnowplowTracker::SelfDescribingJson),
an_instance_of(SnowplowTracker::SelfDescribingJson)
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 82bddb9f5a4..867981297ab 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -150,8 +150,6 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
end
RSpec.shared_examples 'thread comments for issue, epic and merge request' do |resource_name|
- include ContentEditorHelpers
-
let(:form_selector) { '.js-main-target-form' }
let(:dropdown_selector) { "#{form_selector} .comment-type-dropdown" }
let(:toggle_selector) { "#{dropdown_selector} .gl-new-dropdown-toggle" }
@@ -161,10 +159,6 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
let(:comments_selector) { '.timeline > .note.timeline-entry:not(.being-posted)' }
let(:comment) { 'My comment' }
- before do
- close_rich_text_promo_popover_if_present
- end
-
it 'clicking "Comment" will post a comment' do
expect(page).to have_selector toggle_selector
diff --git a/spec/support/shared_examples/features/variable_list_drawer_shared_examples.rb b/spec/support/shared_examples/features/variable_list_drawer_shared_examples.rb
index 9f01c69608d..b438a23aafd 100644
--- a/spec/support/shared_examples/features/variable_list_drawer_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_drawer_shared_examples.rb
@@ -1,23 +1,237 @@
# frozen_string_literal: true
RSpec.shared_examples 'variable list drawer' do
- it 'adds a new CI variable' do
- click_button('Add variable')
+ it 'renders the list drawer' do
+ open_drawer
- # For now, we just check that the drawer is displayed
expect(page).to have_selector('[data-testid="ci-variable-drawer"]')
+ end
+
+ it 'adds a new CI variable' do
+ open_drawer
+
+ fill_variable('NEW_KEY', 'NEW_VALUE')
+ click_add_variable
+
+ wait_for_requests
+
+ page.within('[data-testid="ci-variable-table"]') do
+ expect(first(".js-ci-variable-row td[data-label='#{s_('CiVariables|Key')}']")).to have_content('NEW_KEY')
+
+ click_button('Reveal values')
+
+ expect(first(".js-ci-variable-row td[data-label='#{s_('CiVariables|Value')}']")).to have_content('NEW_VALUE')
+ end
+ end
+
+ it 'allows variable with empty value to be created' do
+ open_drawer
+
+ fill_variable('NEW_KEY')
+
+ page.within('[data-testid="ci-variable-drawer"]') do
+ expect(find_button('Add variable', disabled: false)).to be_present
+ end
+ end
+
+ it 'defaults to unmasked, expanded' do
+ open_drawer
+
+ fill_variable('NEW_KEY')
+ click_add_variable
+
+ wait_for_requests
+
+ page.within('[data-testid="ci-variable-table"]') do
+ key_column = first(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")
+
+ expect(key_column).not_to have_content(s_('CiVariables|Masked'))
+ expect(key_column).to have_content(s_('CiVariables|Expanded'))
+ end
+ end
+
+ context 'with application setting for protected attribute' do
+ context 'when application setting is true' do
+ before do
+ stub_application_setting(protected_ci_variables: true)
+
+ visit page_path
+ end
+
+ it 'defaults to protected' do
+ open_drawer
+
+ page.within('[data-testid="ci-variable-drawer"]') do
+ expect(find('[data-testid="ci-variable-protected-checkbox"]')).to be_checked
+ end
+ end
+ end
+
+ context 'when application setting is false' do
+ before do
+ stub_application_setting(protected_ci_variables: false)
+
+ visit page_path
+ end
- # TODO: Add tests for ADDING a variable via drawer when feature is available
+ it 'defaults to unprotected' do
+ open_drawer
+
+ page.within('[data-testid="ci-variable-drawer"]') do
+ expect(find('[data-testid="ci-variable-protected-checkbox"]')).not_to be_checked
+ end
+ end
+ end
end
it 'edits a variable' do
+ key_column = first(".js-ci-variable-row td[data-label='#{s_('CiVariables|Key')}']")
+ value_column = first(".js-ci-variable-row td[data-label='#{s_('CiVariables|Value')}']")
+
+ expect(key_column).to have_content('test_key')
+ expect(key_column).not_to have_content(s_('CiVariables|Protected'))
+ expect(key_column).to have_content(s_('CiVariables|Masked'))
+ expect(key_column).to have_content(s_('CiVariables|Expanded'))
+
+ click_button('Edit')
+
+ fill_variable('EDITED_KEY', 'EDITED_VALUE')
+ toggle_protected
+ toggle_masked
+ toggle_expanded
+ click_button('Edit variable')
+
+ wait_for_requests
+
page.within('[data-testid="ci-variable-table"]') do
- click_button('Edit')
+ expect(key_column).to have_content('EDITED_KEY')
+ expect(key_column).to have_content(s_('CiVariables|Protected'))
+ expect(key_column).not_to have_content(s_('CiVariables|Masked'))
+ expect(key_column).not_to have_content(s_('CiVariables|Expanded'))
+
+ click_button('Reveal values')
+
+ expect(value_column).to have_content('EDITED_VALUE')
end
+ end
- # For now, we just check that the drawer is displayed
- expect(page).to have_selector('[data-testid="ci-variable-drawer"]')
+ it 'shows validation error for duplicate keys' do
+ open_drawer
+
+ fill_variable('NEW_KEY', 'NEW_VALUE')
+ click_add_variable
+
+ wait_for_requests
+
+ open_drawer
- # TODO: Add tests for EDITING a variable via drawer when feature is available
+ fill_variable('NEW_KEY', 'NEW_VALUE')
+ click_add_variable
+
+ wait_for_requests
+
+ expect(find('.flash-container')).to be_present
+ expect(find('[data-testid="alert-danger"]').text).to have_content('(NEW_KEY) has already been taken')
+ end
+
+ it 'shows validation error for unmaskable values' do
+ open_drawer
+
+ toggle_masked
+ fill_variable('EMPTY_MASK_KEY', '???')
+
+ expect(page).to have_content('This variable value does not meet the masking requirements.')
+ page.within('[data-testid="ci-variable-drawer"]') do
+ expect(find_button('Add variable', disabled: true)).to be_present
+ end
+ end
+
+ it 'handles multiple edits and a deletion' do
+ # Create two variables
+ open_drawer
+ fill_variable('akey', 'akeyvalue')
+ click_add_variable
+
+ wait_for_requests
+
+ open_drawer
+ fill_variable('zkey', 'zkeyvalue')
+ click_add_variable
+
+ wait_for_requests
+
+ expect(page).to have_selector('.js-ci-variable-row', count: 3)
+
+ # Remove the `akey` variable
+ page.within('[data-testid="ci-variable-table"]') do
+ page.within('.js-ci-variable-row:first-child') do
+ click_button('Edit')
+ end
+ end
+
+ page.within('[data-testid="ci-variable-drawer"]') do
+ click_button('Delete variable') # opens confirmation modal
+ end
+
+ page.within('[data-testid="ci-variable-drawer-confirm-delete-modal"]') do
+ click_button('Delete')
+ end
+
+ wait_for_requests
+
+ # Add another variable
+ open_drawer
+ fill_variable('ckey', 'ckeyvalue')
+ click_add_variable
+
+ wait_for_requests
+
+ # expect to find 3 rows of variables in alphabetical order
+ expect(page).to have_selector('.js-ci-variable-row', count: 3)
+ rows = all('.js-ci-variable-row')
+ expect(rows[0].find('td[data-label="Key"]')).to have_content('ckey')
+ expect(rows[1].find('td[data-label="Key"]')).to have_content('test_key')
+ expect(rows[2].find('td[data-label="Key"]')).to have_content('zkey')
+ end
+
+ private
+
+ def open_drawer
+ page.within('[data-testid="ci-variable-table"]') do
+ click_button('Add variable')
+ end
+ end
+
+ def click_add_variable
+ page.within('[data-testid="ci-variable-drawer"]') do
+ click_button('Add variable')
+ end
+ end
+
+ def fill_variable(key, value = '')
+ wait_for_requests
+
+ page.within('[data-testid="ci-variable-drawer"]') do
+ find('[data-testid="ci-variable-key"] input').set(key)
+ find('[data-testid="ci-variable-value"]').set(value) if value.present?
+ end
+ end
+
+ def toggle_protected
+ page.within('[data-testid="ci-variable-drawer"]') do
+ find('[data-testid="ci-variable-protected-checkbox"]').click
+ end
+ end
+
+ def toggle_masked
+ page.within('[data-testid="ci-variable-drawer"]') do
+ find('[data-testid="ci-variable-masked-checkbox"]').click
+ end
+ end
+
+ def toggle_expanded
+ page.within('[data-testid="ci-variable-drawer"]') do
+ find('[data-testid="ci-variable-expanded-checkbox"]').click
+ end
end
end
diff --git a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
index c3df89c8002..ed885d7a226 100644
--- a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
@@ -6,7 +6,6 @@
RSpec.shared_examples 'User creates wiki page' do
include WikiHelpers
- include ContentEditorHelpers
before do
sign_in(user)
@@ -19,7 +18,6 @@ RSpec.shared_examples 'User creates wiki page' do
wait_for_svg_to_be_loaded(example)
click_link "Create your first page"
- close_rich_text_promo_popover_if_present
end
it 'shows all available formats in the dropdown' do
@@ -192,7 +190,6 @@ RSpec.shared_examples 'User creates wiki page' do
context "via the `new wiki page` page", :js do
it "creates a page with a single word" do
click_link("New page")
- close_rich_text_promo_popover_if_present
page.within(".wiki-form") do
fill_in(:wiki_title, with: "foo")
@@ -211,7 +208,6 @@ RSpec.shared_examples 'User creates wiki page' do
it "creates a page with spaces in the name", :js do
click_link("New page")
- close_rich_text_promo_popover_if_present
page.within(".wiki-form") do
fill_in(:wiki_title, with: "Spaces in the name")
@@ -230,7 +226,6 @@ RSpec.shared_examples 'User creates wiki page' do
it "creates a page with hyphens in the name", :js do
click_link("New page")
- close_rich_text_promo_popover_if_present
page.within(".wiki-form") do
fill_in(:wiki_title, with: "hyphens-in-the-name")
@@ -254,7 +249,6 @@ RSpec.shared_examples 'User creates wiki page' do
context 'when a server side validation error is returned' do
it "still displays edit form", :js do
click_link("New page")
- close_rich_text_promo_popover_if_present
page.within(".wiki-form") do
fill_in(:wiki_title, with: "home")
@@ -272,7 +266,6 @@ RSpec.shared_examples 'User creates wiki page' do
it "shows the emoji autocompletion dropdown", :js do
click_link("New page")
- close_rich_text_promo_popover_if_present
page.within(".wiki-form") do
find("#wiki_content").native.send_keys("")
diff --git a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
index 827c875494a..ca68df9a89b 100644
--- a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb
@@ -5,8 +5,6 @@
# user
RSpec.shared_examples 'User previews wiki changes' do
- include ContentEditorHelpers
-
let(:wiki_page) { build(:wiki_page, wiki: wiki) }
before do
@@ -76,7 +74,6 @@ RSpec.shared_examples 'User previews wiki changes' do
before do
wiki_page.create # rubocop:disable Rails/SaveBang
visit wiki_page_path(wiki, wiki_page, action: :edit)
- close_rich_text_promo_popover_if_present
end
it_behaves_like 'relative links' do
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 d06f04db1ce..784de102f4f 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
@@ -6,7 +6,6 @@
RSpec.shared_examples 'User updates wiki page' do
include WikiHelpers
- include ContentEditorHelpers
let(:diagramsnet_url) { 'https://embed.diagrams.net' }
@@ -23,7 +22,6 @@ RSpec.shared_examples 'User updates wiki page' do
wait_for_svg_to_be_loaded(example)
click_link "Create your first page"
- close_rich_text_promo_popover_if_present
end
it 'redirects back to the home edit page' do
@@ -47,7 +45,7 @@ RSpec.shared_examples 'User updates wiki page' do
first(:link, text: 'three').click
- expect(find('[data-testid="wiki_page_title"]')).to have_content('three')
+ expect(find('[data-testid="wiki-page-title"]')).to have_content('three')
click_on('Edit')
@@ -70,7 +68,6 @@ RSpec.shared_examples 'User updates wiki page' do
visit(wiki_path(wiki))
click_link('Edit')
- close_rich_text_promo_popover_if_present
end
it 'updates a page', :js do
@@ -164,7 +161,6 @@ RSpec.shared_examples 'User updates wiki page' do
before do
visit wiki_page_path(wiki, wiki_page, action: :edit)
- close_rich_text_promo_popover_if_present
end
it 'moves the page to the root folder', :js do
@@ -235,7 +231,6 @@ RSpec.shared_examples 'User updates wiki page' do
stub_application_setting(wiki_page_max_content_bytes: 10)
visit wiki_page_path(wiki_page.wiki, wiki_page, action: :edit)
- close_rich_text_promo_popover_if_present
end
it 'allows changing the title if the content does not change', :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 3ee7725305e..254682e1a3a 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
@@ -6,7 +6,6 @@
RSpec.shared_examples 'User views a wiki page' do
include WikiHelpers
- include ContentEditorHelpers
let(:path) { 'image.png' }
let(:wiki_page) do
@@ -58,7 +57,7 @@ RSpec.shared_examples 'User views a wiki page' do
first(:link, text: 'three').click
- expect(find('[data-testid="wiki_page_title"]')).to have_content('three')
+ expect(find('[data-testid="wiki-page-title"]')).to have_content('three')
click_on('Edit')
@@ -123,7 +122,7 @@ RSpec.shared_examples 'User views a wiki page' do
it 'shows the page history' do
visit(wiki_page_path(wiki, wiki_page))
- expect(page).to have_selector('[data-testid="wiki_edit_button"]')
+ expect(page).to have_selector('[data-testid="wiki-edit-button"]')
click_on('Page history')
@@ -135,7 +134,7 @@ RSpec.shared_examples 'User views a wiki page' do
it 'does not show the "Edit" button' do
visit(wiki_page_path(wiki, wiki_page, version_id: wiki_page.versions.last.id))
- expect(page).not_to have_selector('[data-testid="wiki_edit_button"]')
+ expect(page).not_to have_selector('[data-testid="wiki-edit-button"]')
end
context 'show the diff' do
@@ -210,7 +209,7 @@ RSpec.shared_examples 'User views a wiki page' do
it 'preserves the special characters' do
visit(wiki_page_path(wiki, wiki_page))
- expect(page).to have_css('[data-testid="wiki_page_title"]', text: title)
+ expect(page).to have_css('[data-testid="wiki-page-title"]', text: title)
expect(page).to have_css('.wiki-pages li', text: title)
end
end
@@ -225,7 +224,7 @@ RSpec.shared_examples 'User views a wiki page' do
it 'safely displays the page' do
visit(wiki_page_path(wiki, wiki_page))
- expect(page).to have_selector('[data-testid="wiki_page_title"]', text: title)
+ expect(page).to have_selector('[data-testid="wiki-page-title"]', text: title)
expect(page).to have_content('foo bar')
end
end
@@ -252,7 +251,7 @@ RSpec.shared_examples 'User views a wiki page' do
end
it 'does not show "Edit" button' do
- expect(page).not_to have_selector('[data-testid="wiki_edit_button"]')
+ expect(page).not_to have_selector('[data-testid="wiki-edit-button"]')
end
it 'shows error' do
@@ -270,7 +269,6 @@ RSpec.shared_examples 'User views a wiki page' do
wait_for_svg_to_be_loaded
click_link "Create your first page"
- close_rich_text_promo_popover_if_present
expect(page).to have_content('Create New Page')
end
diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb
index 18e0cfdad00..ff79f180c64 100644
--- a/spec/support/shared_examples/features/work_items_shared_examples.rb
+++ b/spec/support/shared_examples/features/work_items_shared_examples.rb
@@ -218,15 +218,33 @@ RSpec.shared_examples 'work items assignees' do
expect(work_item.reload.assignees).not_to include(user)
end
+
+ it 'updates the assignee in real-time' do
+ Capybara::Session.new(:other_session)
+
+ using_session :other_session do
+ visit work_items_path
+ expect(work_item.reload.assignees).not_to include(user)
+ end
+
+ find('[data-testid="work-item-assignees-input"]').hover
+ find('[data-testid="assign-self"]').click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).to include(user)
+
+ using_session :other_session do
+ expect(work_item.reload.assignees).to include(user)
+ end
+ end
end
RSpec.shared_examples 'work items labels' do
let(:label_title_selector) { '[data-testid="labels-title"]' }
+ let(:labels_input_selector) { '[data-testid="work-item-labels-input"]' }
it 'successfully assigns a label' do
- label = create(:label, project: work_item.project, title: "testing-label")
-
- find('[data-testid="work-item-labels-input"]').fill_in(with: label.title)
+ find(labels_input_selector).fill_in(with: label.title)
wait_for_requests
# submit and simulate blur to save
@@ -236,6 +254,88 @@ RSpec.shared_examples 'work items labels' do
expect(work_item.labels).to include(label)
end
+
+ it 'successfully assigns multiple labels' do
+ label2 = create(:label, project: project, title: "testing-label-2")
+
+ find(labels_input_selector).fill_in(with: label.title)
+ wait_for_requests
+ send_keys(:enter)
+
+ find(labels_input_selector).fill_in(with: label2.title)
+ wait_for_requests
+ send_keys(:enter)
+
+ find(label_title_selector).click
+ wait_for_requests
+
+ expect(work_item.labels).to include(label)
+ expect(work_item.labels).to include(label2)
+ end
+
+ it 'removes all labels on clear all button click' do
+ find(labels_input_selector).fill_in(with: label.title)
+ wait_for_requests
+
+ send_keys(:enter)
+ find(label_title_selector).click
+ wait_for_requests
+
+ expect(work_item.labels).to include(label)
+
+ within(labels_input_selector) do
+ find('input').click
+ find('[data-testid="clear-all-button"]').click
+ end
+
+ find(label_title_selector).click
+ wait_for_requests
+
+ expect(work_item.labels).not_to include(label)
+ end
+
+ it 'removes label on clicking badge cross button' do
+ find(labels_input_selector).fill_in(with: label.title)
+ wait_for_requests
+
+ send_keys(:enter)
+ find(label_title_selector).click
+ wait_for_requests
+
+ expect(page).to have_text(label.title)
+
+ within(labels_input_selector) do
+ find('[data-testid="close-icon"]').click
+ end
+
+ find(label_title_selector).click
+ wait_for_requests
+
+ expect(work_item.labels).not_to include(label)
+ end
+
+ it 'updates the labels in real-time' do
+ Capybara::Session.new(:other_session)
+
+ using_session :other_session do
+ visit work_items_path
+ expect(page).not_to have_text(label.title)
+ end
+
+ find(labels_input_selector).fill_in(with: label.title)
+ wait_for_requests
+
+ send_keys(:enter)
+ find(label_title_selector).click
+ wait_for_requests
+
+ expect(page).to have_text(label.title)
+
+ using_session :other_session do
+ wait_for_requests
+ expect(page).to have_text(label.title)
+ end
+ end
end
RSpec.shared_examples 'work items description' do
diff --git a/spec/support/shared_examples/graphql/design_fields_shared_examples.rb b/spec/support/shared_examples/graphql/design_fields_shared_examples.rb
index efbcfaf0e91..aa3a1d78df8 100644
--- a/spec/support/shared_examples/graphql/design_fields_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/design_fields_shared_examples.rb
@@ -32,7 +32,7 @@ RSpec.shared_examples 'a GraphQL type with design fields' do
let(:query) { GraphQL::Query.new(schema) }
let(:context) { query.context }
let(:field) { described_class.fields['image'] }
- let(:args) { GraphQL::Query::Arguments::NO_ARGS }
+ let(:args) { { parent: nil } }
let(:instance) { instantiate(object_id) }
let(:instance_b) { instantiate(object_id_b) }
@@ -42,13 +42,12 @@ RSpec.shared_examples 'a GraphQL type with design fields' do
end
def resolve_image(instance)
- field.resolve_field(instance, args, context)
+ field.resolve(instance, args, context)
end
before do
context[:current_user] = current_user
allow(Ability).to receive(:allowed?).with(current_user, :read_design, anything).and_return(true)
- allow(context).to receive(:parent).and_return(nil)
end
it 'resolves to the design image URL' do
diff --git a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb
index 64f811771ec..799f82a9ec5 100644
--- a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb
@@ -14,9 +14,21 @@ RSpec.shared_context 'exposing regular notes on a noteable in GraphQL' do
let(:user) { note.author }
context 'for regular notes' do
+ let!(:system_note) do
+ create(
+ :note,
+ system: true,
+ noteable: noteable,
+ project: (noteable.project if noteable.respond_to?(:project))
+ )
+ end
+
+ let(:filters) { "" }
+
let(:query) do
note_fields = <<~NOTES
- notes {
+ notes #{filters} {
+ count
edges {
node {
#{all_graphql_fields_for('Note', max_depth: 1)}
@@ -42,11 +54,12 @@ RSpec.shared_context 'exposing regular notes on a noteable in GraphQL' do
end
end
- it 'includes the note' do
+ it 'includes all notes' do
post_graphql(query, current_user: user)
- expect(noteable_data['notes']['edges'].first['node']['body'])
- .to eq(note.note)
+ expect(noteable_data['notes']['count']).to eq(2)
+ expect(noteable_data['notes']['edges'][0]['node']['body']).to eq(system_note.note)
+ expect(noteable_data['notes']['edges'][1]['node']['body']).to eq(note.note)
end
it 'avoids N+1 queries' do
@@ -69,6 +82,42 @@ RSpec.shared_context 'exposing regular notes on a noteable in GraphQL' do
expect { post_graphql(query, current_user: user) }.not_to exceed_query_limit(control)
expect_graphql_errors_to_be_empty
end
+
+ context 'when filter is provided' do
+ context 'when filter is set to ALL_NOTES' do
+ let(:filters) { "(filter: ALL_NOTES)" }
+
+ it 'returns all the notes' do
+ post_graphql(query, current_user: user)
+
+ expect(noteable_data['notes']['count']).to eq(2)
+ expect(noteable_data['notes']['edges'][0]['node']['body']).to eq(system_note.note)
+ expect(noteable_data['notes']['edges'][1]['node']['body']).to eq(note.note)
+ end
+ end
+
+ context 'when filter is set to ONLY_COMMENTS' do
+ let(:filters) { "(filter: ONLY_COMMENTS)" }
+
+ it 'returns only the comments' do
+ post_graphql(query, current_user: user)
+
+ expect(noteable_data['notes']['count']).to eq(1)
+ expect(noteable_data['notes']['edges'][0]['node']['body']).to eq(note.note)
+ end
+ end
+
+ context 'when filter is set to ONLY_ACTIVITY' do
+ let(:filters) { "(filter: ONLY_ACTIVITY)" }
+
+ it 'returns only the activity notes' do
+ post_graphql(query, current_user: user)
+
+ expect(noteable_data['notes']['count']).to eq(1)
+ expect(noteable_data['notes']['edges'][0]['node']['body']).to eq(system_note.note)
+ end
+ end
+ end
end
context "for discussions" do
diff --git a/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb b/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb
index cef76bd4356..3119a03b1cb 100644
--- a/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb
+++ b/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb
@@ -31,29 +31,33 @@ RSpec.shared_examples 'a request using Gitlab::UrlBlocker' do
it 'raises error when it is a request that resolves to a local address' do
stub_full_request('https://example.com', method: http_method, ip_address: '172.16.0.0')
- expect { make_request('https://example.com') }
- .to raise_error(url_blocked_error_class,
- "URL is blocked: Requests to the local network are not allowed")
+ expect { make_request('https://example.com') }.to raise_error(
+ url_blocked_error_class,
+ "URL is blocked: Requests to the local network are not allowed"
+ )
end
it 'raises error when it is a request that resolves to a localhost address' do
stub_full_request('https://example.com', method: http_method, ip_address: '127.0.0.1')
- expect { make_request('https://example.com') }
- .to raise_error(url_blocked_error_class,
- "URL is blocked: Requests to localhost are not allowed")
+ expect { make_request('https://example.com') }.to raise_error(
+ url_blocked_error_class,
+ "URL is blocked: Requests to localhost are not allowed"
+ )
end
it 'raises error when it is a request to local address' do
- expect { make_request('http://172.16.0.0') }
- .to raise_error(url_blocked_error_class,
- "URL is blocked: Requests to the local network are not allowed")
+ expect { make_request('http://172.16.0.0') }.to raise_error(
+ url_blocked_error_class,
+ "URL is blocked: Requests to the local network are not allowed"
+ )
end
it 'raises error when it is a request to localhost address' do
- expect { make_request('http://127.0.0.1') }
- .to raise_error(url_blocked_error_class,
- "URL is blocked: Requests to localhost are not allowed")
+ expect { make_request('http://127.0.0.1') }.to raise_error(
+ url_blocked_error_class,
+ "URL is blocked: Requests to localhost are not allowed"
+ )
end
end
@@ -67,15 +71,17 @@ RSpec.shared_examples 'a request using Gitlab::UrlBlocker' do
end
it 'raises error when it is a request to local address' do
- expect { make_request('https://172.16.0.0:8080') }
- .to raise_error(url_blocked_error_class,
- "URL is blocked: Requests to the local network are not allowed")
+ expect { make_request('https://172.16.0.0:8080') }.to raise_error(
+ url_blocked_error_class,
+ "URL is blocked: Requests to the local network are not allowed"
+ )
end
it 'raises error when it is a request to localhost address' do
- expect { make_request('https://127.0.0.1:8080') }
- .to raise_error(url_blocked_error_class,
- "URL is blocked: Requests to localhost are not allowed")
+ expect { make_request('https://127.0.0.1:8080') }.to raise_error(
+ url_blocked_error_class,
+ "URL is blocked: Requests to localhost are not allowed"
+ )
end
end
diff --git a/spec/support/shared_examples/integrations/integration_settings_form.rb b/spec/support/shared_examples/integrations/integration_settings_form.rb
index 1d7f74837f2..c665f6a57f1 100644
--- a/spec/support/shared_examples/integrations/integration_settings_form.rb
+++ b/spec/support/shared_examples/integrations/integration_settings_form.rb
@@ -16,7 +16,7 @@ RSpec.shared_examples 'integration settings form' do
page.within('form.integration-settings-form') do
expect(page).to have_field('Active', type: 'checkbox', wait: 0),
- "#{integration.title} active field not present"
+ "#{integration.title} active field not present"
fields = parse_json(fields_for_integration(integration))
fields.each do |field|
@@ -24,7 +24,7 @@ RSpec.shared_examples 'integration settings form' do
field_name = field[:name]
expect(page).to have_field(field[:title], wait: 0),
- "#{integration.title} field #{field_name} not present"
+ "#{integration.title} field #{field_name} not present"
end
api_only_fields = integration.fields.select { _1[:api_only] }
@@ -43,7 +43,7 @@ RSpec.shared_examples 'integration settings form' do
end
expect(page).to have_field(trigger_title, type: 'checkbox', wait: 0),
- "#{integration.title} field #{trigger_title} checkbox not present"
+ "#{integration.title} field #{trigger_title} checkbox not present"
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
index 7cfab5c8295..0cc525d0575 100644
--- a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
@@ -113,7 +113,7 @@ RSpec.shared_examples 'common trace features' do
it "returns valid sections" do
expect(sections).not_to be_empty
expect(sections.size).to eq(sections_data.size),
- "expected #{sections_data.size} sections, got #{sections.size}"
+ "expected #{sections_data.size} sections, got #{sections.size}"
buff = StringIO.new(log)
sections.each_with_index do |s, i|
diff --git a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
index 286f10a186d..d1367bbe144 100644
--- a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb
@@ -2,8 +2,11 @@
RSpec.shared_examples 'marks background migration job records' do
it 'marks each job record as succeeded after processing' do
- create(:background_migration_job, class_name: "::#{described_class.name.demodulize}",
- arguments: arguments)
+ create(
+ :background_migration_job,
+ class_name: "::#{described_class.name.demodulize}",
+ arguments: arguments
+ )
expect(::Gitlab::Database::BackgroundMigrationJob).to receive(:mark_all_as_succeeded).and_call_original
@@ -13,8 +16,11 @@ RSpec.shared_examples 'marks background migration job records' do
end
it 'returns the number of job records marked as succeeded' do
- create(:background_migration_job, class_name: "::#{described_class.name.demodulize}",
- arguments: arguments)
+ create(
+ :background_migration_job,
+ class_name: "::#{described_class.name.demodulize}",
+ arguments: arguments
+ )
jobs_updated = subject.perform(*arguments)
diff --git a/spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb
index 0fef5269ab6..effa6a6f6f0 100644
--- a/spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb
@@ -17,16 +17,18 @@ RSpec.shared_examples Gitlab::Import::AdvanceStage do |factory:|
context 'when there are remaining jobs' do
it 'reschedules itself' do
- expect(worker)
- .to receive(:wait_for_jobs)
- .with({ '123' => 2 })
- .and_return({ '123' => 1 })
+ freeze_time do
+ expect(worker)
+ .to receive(:wait_for_jobs)
+ .with({ '123' => 2 })
+ .and_return({ '123' => 1 })
- expect(described_class)
- .to receive(:perform_in)
- .with(described_class::INTERVAL, project.id, { '123' => 1 }, next_stage)
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(described_class::INTERVAL, project.id, { '123' => 1 }, next_stage, Time.zone.now, 1)
- worker.perform(project.id, { '123' => 2 }, next_stage)
+ worker.perform(project.id, { '123' => 2 }, next_stage)
+ end
end
context 'when the project import is not running' do
@@ -74,6 +76,83 @@ RSpec.shared_examples Gitlab::Import::AdvanceStage do |factory:|
.to raise_error(KeyError)
end
end
+
+ context 'on worker timeouts' do
+ it 'refreshes timeout and updates counter if jobs have been processed' do
+ freeze_time do
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(described_class::INTERVAL, project.id, { '123' => 2 }, next_stage, Time.zone.now, 2)
+
+ worker.perform(project.id, { '123' => 2 }, next_stage, 3.hours.ago, 5)
+ end
+ end
+
+ it 'converts string timeout argument to time' do
+ freeze_time do
+ expect_next_instance_of(described_class) do |klass|
+ expect(klass).to receive(:handle_timeout)
+ end
+
+ worker.perform(project.id, { '123' => 2 }, next_stage, 3.hours.ago.to_s, 2)
+ end
+ end
+
+ context 'with an optimistic strategy' do
+ before do
+ project.build_or_assign_import_data(data: { timeout_strategy: "optimistic" })
+ project.save!
+ end
+
+ it 'advances to next stage' do
+ freeze_time do
+ next_worker = described_class::STAGES[next_stage]
+
+ expect(next_worker).to receive(:perform_async).with(project.id)
+
+ stuck_start_time = 3.hours.ago
+
+ worker.perform(project.id, { '123' => 2 }, next_stage, stuck_start_time, 2)
+ end
+ end
+ end
+
+ context 'with a pessimistic strategy' do
+ let(:expected_error_message) { "Failing advance stage, timeout reached with pessimistic strategy" }
+
+ it 'logs error and fails import' do
+ freeze_time do
+ next_worker = described_class::STAGES[next_stage]
+
+ expect(next_worker).not_to receive(:perform_async).with(project.id)
+ expect_next_instance_of(described_class) do |klass|
+ expect(klass).to receive(:find_import_state).and_call_original
+ end
+ expect(Gitlab::Import::ImportFailureService)
+ .to receive(:track)
+ .with(
+ import_state: import_state,
+ exception: Gitlab::Import::AdvanceStage::AdvanceStageTimeoutError,
+ error_source: described_class.name,
+ fail_import: true
+ )
+ .and_call_original
+
+ stuck_start_time = 3.hours.ago
+
+ worker.perform(project.id, { '123' => 2 }, next_stage, stuck_start_time, 2)
+
+ expect(import_state.reload.status).to eq("failed")
+
+ if import_state.is_a?(ProjectImportState)
+ expect(import_state.reload.last_error).to eq(expected_error_message)
+ else
+ expect(import_state.reload.error_message).to eq(expected_error_message)
+ end
+ end
+ end
+ end
+ end
end
describe '#wait_for_jobs' do
diff --git a/spec/support/shared_examples/metrics_instrumentation_shared_examples.rb b/spec/support/shared_examples/metrics_instrumentation_shared_examples.rb
index cef9860fe25..5c2f66e08db 100644
--- a/spec/support/shared_examples/metrics_instrumentation_shared_examples.rb
+++ b/spec/support/shared_examples/metrics_instrumentation_shared_examples.rb
@@ -3,7 +3,8 @@
RSpec.shared_examples 'a correct instrumented metric value' do |params|
let(:time_frame) { params[:time_frame] }
let(:options) { params[:options] }
- let(:metric) { described_class.new(time_frame: time_frame, options: options) }
+ let(:events) { params[:events] }
+ let(:metric) { described_class.new(time_frame: time_frame, options: options, events: events) }
around do |example|
freeze_time { example.run }
diff --git a/spec/support/shared_examples/migrations/swap_conversion_columns_shared_examples.rb b/spec/support/shared_examples/migrations/swap_conversion_columns_shared_examples.rb
new file mode 100644
index 00000000000..d333641b764
--- /dev/null
+++ b/spec/support/shared_examples/migrations/swap_conversion_columns_shared_examples.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+COLUMN_OPTIONS_TO_REMAIN =
+ %i[
+ null
+ serial?
+ collation
+ default
+ default_function
+ ].freeze
+
+SQL_TYPE_OPTIONS_TO_REMAIN =
+ %i[
+ precision
+ scale
+ ].freeze
+
+SQL_TYPE_OPTIONS_TO_CHANGE =
+ %i[
+ type
+ sql_type
+ limit
+ ].freeze
+
+RSpec.shared_examples 'swap conversion columns' do |table_name:, from:, to:|
+ it 'correctly swaps conversion columns' do
+ before_from_column = before_to_column = before_indexes = before_foreign_keys = nil
+ after_from_column = after_to_column = after_indexes = after_foreign_keys = nil
+
+ expect_column_type_is_changed_but_others_remain_unchanged = -> do
+ # SQL type is changed
+ SQL_TYPE_OPTIONS_TO_CHANGE.each do |sql_type_option|
+ expect(
+ after_from_column.sql_type_metadata.public_send(sql_type_option)
+ ).to eq(
+ before_to_column.sql_type_metadata.public_send(sql_type_option)
+ )
+
+ expect(
+ after_to_column.sql_type_metadata.public_send(sql_type_option)
+ ).to eq(
+ before_from_column.sql_type_metadata.public_send(sql_type_option)
+ )
+ end
+
+ # column metadata remains unchanged
+ COLUMN_OPTIONS_TO_REMAIN.each do |column_option|
+ expect(
+ after_from_column.public_send(column_option)
+ ).to eq(
+ before_from_column.public_send(column_option)
+ )
+
+ expect(
+ after_to_column.public_send(column_option)
+ ).to eq(
+ before_to_column.public_send(column_option)
+ )
+ end
+
+ SQL_TYPE_OPTIONS_TO_REMAIN.each do |sql_type_option|
+ expect(
+ after_from_column.sql_type_metadata.public_send(sql_type_option)
+ ).to eq(
+ before_from_column.sql_type_metadata.public_send(sql_type_option)
+ )
+
+ expect(
+ after_to_column.sql_type_metadata.public_send(sql_type_option)
+ ).to eq(
+ before_to_column.sql_type_metadata.public_send(sql_type_option)
+ )
+ end
+
+ # indexes remain unchanged
+ expect(before_indexes).to eq(after_indexes)
+
+ # foreign keys remain unchanged
+ expect(before_foreign_keys).to eq(after_foreign_keys)
+ end
+
+ find_column_by = ->(name) do
+ active_record_base.connection.columns(table_name).find { |c| c.name == name.to_s }
+ end
+
+ find_indexes = -> do
+ active_record_base.connection.indexes(table_name)
+ end
+
+ find_foreign_keys = -> do
+ Gitlab::Database::PostgresForeignKey.by_constrained_table_name(table_name)
+ end
+
+ reversible_migration do |migration|
+ migration.before -> {
+ before_from_column = find_column_by.call(from)
+ before_to_column = find_column_by.call(to)
+ before_indexes = find_indexes
+ before_foreign_keys = find_foreign_keys
+
+ next if after_from_column.nil?
+
+ # For migrate down
+ expect_column_type_is_changed_but_others_remain_unchanged.call
+ }
+
+ migration.after -> {
+ after_from_column = find_column_by.call(from)
+ after_to_column = find_column_by.call(to)
+ after_indexes = find_indexes
+ after_foreign_keys = find_foreign_keys
+
+ expect_column_type_is_changed_but_others_remain_unchanged.call
+ }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
index 0ce54fbc31f..0ff2c135972 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples "chat integration" do |integration_name|
+RSpec.shared_examples "chat integration" do |integration_name, supports_deployments: false|
describe "Associations" do
it { is_expected.to belong_to :project }
end
@@ -26,8 +26,14 @@ RSpec.shared_examples "chat integration" do |integration_name|
end
describe '.supported_events' do
- it 'does not support deployment_events' do
- expect(described_class.supported_events).not_to include('deployment')
+ if supports_deployments
+ it 'supports deployment_events' do
+ expect(described_class.supported_events).to include('deployment')
+ end
+ else
+ it 'does not support deployment_events' do
+ expect(described_class.supported_events).not_to include('deployment')
+ end
end
end
@@ -375,7 +381,47 @@ RSpec.shared_examples "chat integration" do |integration_name|
let(:sample_data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.now) }
- it_behaves_like "untriggered #{integration_name} integration"
+ if supports_deployments
+ it_behaves_like "triggered #{integration_name} integration"
+ else
+ it_behaves_like "untriggered #{integration_name} integration"
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'supports group mentions' do |integration_factory|
+ it 'supports group mentions' do
+ allow(subject).to receive(:webhook).and_return('http://example.com')
+ allow(subject).to receive(:group_id).and_return(1)
+ expect(subject).to receive(:notify).with(an_instance_of(Integrations::ChatMessage::GroupMentionMessage), {})
+
+ subject.execute(
+ object_kind: 'group_mention',
+ object_attributes: { action: 'new', object_kind: 'issue' },
+ mentioned: { name: 'John Doe', url: 'http://example.com' }
+ )
+ end
+
+ describe '#supported_events' do
+ context 'when used in a project' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:integration) { build(integration_factory, project: project) }
+
+ it 'does not support group mentions', :aggregate_failures do
+ expect(integration.supported_events).not_to include('group_mention')
+ expect(integration.supported_events).not_to include('group_confidential_mention')
+ end
+ end
+
+ context 'when used in a group' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:integration) { build(integration_factory, group: group) }
+
+ it 'supports group mentions', :aggregate_failures do
+ expect(integration.supported_events).to include('group_mention')
+ expect(integration.supported_events).to include('group_confidential_mention')
+ end
end
end
end
diff --git a/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb b/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
index 0e9200f1fd9..bb438b0082f 100644
--- a/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
+++ b/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
@@ -52,7 +52,11 @@ RSpec.shared_examples 'protected ref access' do |association|
end
describe '#check_access' do
+ let_it_be(:group) { create(:group) }
+ # Making a project public to avoid false positives tests
+ let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:current_user) { create(:user) }
+ let_it_be(:protected_ref) { create(association, project: project) }
let(:access_level) { ::Gitlab::Access::DEVELOPER }
@@ -71,6 +75,47 @@ RSpec.shared_examples 'protected ref access' do |association|
it { expect(subject.check_access(nil)).to eq(false) }
end
+ context 'when current_user access exists without membership' do
+ let(:other_user) { create(:user) }
+ let(:user_access) do
+ described_class.new(association => protected_ref, access_level: access_level, user_id: other_user.id)
+ end
+
+ let(:enable_ff) { false }
+
+ before do
+ stub_feature_flags(check_membership_in_protected_ref_access: enable_ff)
+ end
+
+ it 'does not check membership if check_membership_in_protected_ref_access FF is disabled' do
+ expect(project).not_to receive(:member?).with(other_user)
+
+ user_access.check_access(other_user)
+ end
+
+ context 'when check_membership_in_protected_ref_access FF is enabled' do
+ let(:enable_ff) { true }
+
+ it 'does check membership' do
+ expect(project).to receive(:member?).with(other_user)
+
+ user_access.check_access(other_user)
+ end
+
+ it 'returns false' do
+ expect(user_access.check_access(other_user)).to be_falsey
+ end
+
+ context 'when user has inherited membership' do
+ let!(:inherited_membership) { create(:group_member, group: group, user: other_user) }
+
+ it do
+ expect(user_access.check_access(other_user)).to be_truthy
+ end
+ end
+ end
+ end
+
context 'when access_level is NO_ACCESS' do
let(:access_level) { ::Gitlab::Access::NO_ACCESS }
diff --git a/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb b/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
index 3f1588c46b3..a9a13ddcd60 100644
--- a/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
@@ -71,14 +71,21 @@ RSpec.shared_examples 'handles repository moves' do
end
context 'when the transition fails' do
- it 'does not trigger the corresponding repository storage worker and adds an error' do
+ before do
allow(storage_move.container).to receive(:set_repository_read_only!).and_raise(StandardError, 'foobar')
- expect(repository_storage_worker).not_to receive(:perform_async)
+ end
+ it 'does not trigger the corresponding repository storage worker and adds an error' do
+ expect(repository_storage_worker).not_to receive(:perform_async)
storage_move.schedule!
-
expect(storage_move.errors[error_key]).to include('foobar')
end
+
+ it 'sets the state to failed' do
+ expect(storage_move).to receive(:do_fail!).and_call_original
+ storage_move.schedule!
+ expect(storage_move.state_name).to eq(:failed)
+ end
end
end
diff --git a/spec/support/shared_examples/models/issuable_link_shared_examples.rb b/spec/support/shared_examples/models/issuable_link_shared_examples.rb
index af96b77edaf..f28abb35128 100644
--- a/spec/support/shared_examples/models/issuable_link_shared_examples.rb
+++ b/spec/support/shared_examples/models/issuable_link_shared_examples.rb
@@ -52,6 +52,45 @@ RSpec.shared_examples 'issuable link' do
end
end
+ context 'when max number of links is exceeded' do
+ subject(:link) { create_issuable_link(issuable, issuable2) }
+
+ shared_examples 'invalid due to exceeding max number of links' do
+ let(:stubbed_limit) { 1 }
+ let(:issuable_name) { described_class.issuable_name }
+ let(:error_msg) do
+ "This #{issuable_name} would exceed the maximum number of " \
+ "linked #{issuable_name.pluralize} (#{stubbed_limit})."
+ end
+
+ before do
+ create(issuable_link_factory, source: source, target: target)
+ stub_const("IssuableLink::MAX_LINKS_COUNT", stubbed_limit)
+ end
+
+ specify do
+ is_expected.to be_invalid
+ expect(link.errors.messages[error_item]).to include(error_msg)
+ end
+ end
+
+ context 'when source exceeds max' do
+ let(:source) { issuable }
+ let(:target) { issuable3 }
+ let(:error_item) { :source }
+
+ it_behaves_like 'invalid due to exceeding max number of links'
+ end
+
+ context 'when target exceeds max' do
+ let(:source) { issuable2 }
+ let(:target) { issuable3 }
+ let(:error_item) { :target }
+
+ it_behaves_like 'invalid due to exceeding max number of links'
+ end
+ end
+
def create_issuable_link(source, target)
build(issuable_link_factory, source: source, target: target)
end
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index e9e25dee746..731500c4510 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -486,59 +486,6 @@ RSpec.shared_examples_for "bulk member creation" do
end.to change { Member.count }.by(2)
end
end
-
- context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
- let(:task_project) { source.is_a?(Group) ? create(:project, group: source) : source }
-
- it 'creates a member_task with the correct attributes', :aggregate_failures do
- members = described_class.add_members(source, [user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id)
- member = members.last
-
- expect(member.tasks_to_be_done).to match_array([:ci, :code])
- expect(member.member_task.project).to eq(task_project)
- end
-
- context 'with an already existing member' do
- before do
- source.add_member(user1, :developer)
- end
-
- it 'does not update tasks to be done if tasks already exist', :aggregate_failures do
- member = source.members.find_by(user_id: user1.id)
- create(:member_task, member: member, project: task_project, tasks_to_be_done: %w(code ci))
-
- expect do
- described_class.add_members(
- source,
- [user1.id],
- :developer,
- tasks_to_be_done: %w(issues),
- tasks_project_id: task_project.id
- )
- end.not_to change { MemberTask.count }
-
- member.reset
- expect(member.tasks_to_be_done).to match_array([:code, :ci])
- expect(member.member_task.project).to eq(task_project)
- end
-
- it 'adds tasks to be done if they do not exist', :aggregate_failures do
- expect do
- described_class.add_members(
- source,
- [user1.id],
- :developer,
- tasks_to_be_done: %w(issues),
- tasks_project_id: task_project.id
- )
- end.to change { MemberTask.count }.by(1)
-
- member = source.members.find_by(user_id: user1.id)
- expect(member.tasks_to_be_done).to match_array([:issues])
- expect(member.member_task.project).to eq(task_project)
- end
- end
- end
end
RSpec.shared_examples 'owner management' do
diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb
index 4dff4f68995..960160395f8 100644
--- a/spec/support/shared_examples/namespaces/traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_examples.rb
@@ -240,14 +240,6 @@ RSpec.shared_examples 'namespace traversal' do
describe '#ancestors_upto' do
include_examples '#ancestors_upto'
-
- context 'with use_traversal_ids disabled' do
- before do
- stub_feature_flags(use_traversal_ids: false)
- end
-
- include_examples '#ancestors_upto'
- end
end
describe '#descendants' do
diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
index b308295b5fb..637068c5c8a 100644
--- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
@@ -70,28 +70,10 @@ RSpec.shared_examples 'namespace traversal scopes' do
end
describe '.roots' do
- context "use_traversal_ids feature flag is true" do
- before do
- stub_feature_flags(use_traversal_ids: true)
- end
-
- it_behaves_like '.roots'
-
- it 'not make recursive queries' do
- expect { described_class.where(id: [nested_group_1]).roots.load }.not_to make_queries_matching(/WITH RECURSIVE/)
- end
- end
+ it_behaves_like '.roots'
- context "use_traversal_ids feature flag is false" do
- before do
- stub_feature_flags(use_traversal_ids: false)
- end
-
- it_behaves_like '.roots'
-
- it 'makes recursive queries' do
- expect { described_class.where(id: [nested_group_1]).roots.load }.to make_queries_matching(/WITH RECURSIVE/)
- end
+ it 'not make recursive queries' do
+ expect { described_class.where(id: [nested_group_1]).roots.load }.not_to make_queries_matching(/WITH RECURSIVE/)
end
end
@@ -263,7 +245,7 @@ RSpec.shared_examples 'namespace traversal scopes' do
include_examples '.self_and_descendant_ids'
end
- shared_examples '.self_and_hierarchy' do
+ describe '.self_and_hierarchy' do
let(:base_scope) { Group.where(id: base_groups) }
subject { base_scope.self_and_hierarchy }
@@ -292,21 +274,4 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { is_expected.to contain_exactly(group_1, nested_group_1, deep_nested_group_1) }
end
end
-
- describe '.self_and_hierarchy' do
- it_behaves_like '.self_and_hierarchy'
-
- context "use_traversal_ids_for_self_and_hierarchy_scopes feature flag is false" do
- before do
- stub_feature_flags(use_traversal_ids_for_self_and_hierarchy_scopes: false)
- end
-
- it_behaves_like '.self_and_hierarchy'
-
- it 'makes recursive queries' do
- base_groups = Group.where(id: nested_group_1)
- expect { base_groups.self_and_hierarchy.load }.to make_queries_matching(/WITH RECURSIVE/)
- end
- end
- end
end
diff --git a/spec/support/shared_examples/observability/embed_observabilities_examples.rb b/spec/support/shared_examples/observability/embed_observabilities_examples.rb
deleted file mode 100644
index c8d4e9e0d7e..00000000000
--- a/spec/support/shared_examples/observability/embed_observabilities_examples.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'embeds observability' do
- it 'renders iframe in description' do
- page.within('.description') do
- expect_observability_iframe(page.html)
- end
- end
-
- it 'renders iframe in comment' do
- expect(page).not_to have_css('.note-text')
-
- page.within('.js-main-target-form') do
- fill_in('note[note]', with: observable_url)
- click_button('Comment')
- end
-
- wait_for_requests
-
- page.within('.note-text') do
- expect_observability_iframe(page.html)
- end
- end
-end
-
-RSpec.shared_examples 'does not embed observability' do
- it 'does not render iframe in description' do
- page.within('.description') do
- expect_observability_iframe(page.html, to_be_nil: true)
- end
- end
-
- it 'does not render iframe in comment' do
- expect(page).not_to have_css('.note-text')
-
- page.within('.js-main-target-form') do
- fill_in('note[note]', with: observable_url)
- click_button('Comment')
- end
-
- wait_for_requests
-
- page.within('.note-text') do
- expect_observability_iframe(page.html, to_be_nil: true)
- end
- end
-end
-
-def expect_observability_iframe(html, to_be_nil: false)
- iframe = Nokogiri::HTML.parse(html).at_css('#observability-ui-iframe')
-
- expect(html).to include(observable_url)
-
- if to_be_nil
- expect(iframe).to be_nil
- else
- expect(iframe).not_to be_nil
- iframe_src = "#{expected_observable_url}&theme=light&username=#{user.username}&kiosk=inline-embed"
- expect(iframe.attributes['src'].value).to eq(iframe_src)
- end
-end
diff --git a/spec/support/shared_examples/prometheus/additional_metrics_shared_examples.rb b/spec/support/shared_examples/prometheus/additional_metrics_shared_examples.rb
deleted file mode 100644
index d196114b227..00000000000
--- a/spec/support/shared_examples/prometheus/additional_metrics_shared_examples.rb
+++ /dev/null
@@ -1,161 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'additional metrics query' do
- include Prometheus::MetricBuilders
-
- let(:metric_group_class) { Gitlab::Prometheus::MetricGroup }
- let(:metric_class) { Gitlab::Prometheus::Metric }
-
- let(:metric_names) { %w[metric_a metric_b] }
-
- let(:query_range_result) do
- [{ metric: {}, values: [[1488758662.506, '0.00002996364761904785'], [1488758722.506, '0.00003090239047619091']] }]
- end
-
- let(:client) { instance_double('Gitlab::PrometheusClient') }
- let(:query_result) { described_class.new(client).query(*query_params) }
- let(:project) { create(:project, :repository) }
- let(:environment) { create(:environment, slug: 'environment-slug', project: project) }
-
- before do
- allow(client).to receive(:label_values).and_return(metric_names)
- allow(metric_group_class).to receive(:common_metrics).and_return([simple_metric_group(metrics: [simple_metric])])
- end
-
- describe 'metrics query context' do
- subject! { described_class.new(client) }
-
- shared_examples 'query context containing environment slug and filter' do
- it 'contains ci_environment_slug' do
- expect(subject)
- .to receive(:query_metrics).with(project, environment, hash_including(ci_environment_slug: environment.slug))
-
- subject.query(*query_params)
- end
-
- it 'contains environment filter' do
- expect(subject).to receive(:query_metrics).with(
- project,
- environment,
- hash_including(
- environment_filter: "container_name!=\"POD\",environment=\"#{environment.slug}\""
- )
- )
-
- subject.query(*query_params)
- end
- end
-
- describe 'project has Kubernetes service' do
- context 'when user configured kubernetes from CI/CD > Clusters' do
- let!(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) }
- let(:environment) { create(:environment, slug: 'environment-slug', project: project) }
- let(:kube_namespace) { environment.deployment_namespace }
-
- it_behaves_like 'query context containing environment slug and filter'
-
- it 'query context contains kube_namespace' do
- expect(subject)
- .to receive(:query_metrics).with(project, environment, hash_including(kube_namespace: kube_namespace))
-
- subject.query(*query_params)
- end
- end
- end
-
- describe 'project without Kubernetes service' do
- it_behaves_like 'query context containing environment slug and filter'
-
- it 'query context contains empty kube_namespace' do
- expect(subject).to receive(:query_metrics).with(project, environment, hash_including(kube_namespace: ''))
-
- subject.query(*query_params)
- end
- end
- end
-
- context 'with one group where two metrics is found' do
- before do
- allow(metric_group_class).to receive(:common_metrics).and_return([simple_metric_group])
- end
-
- context 'when some queries return results' do
- before do
- allow(client).to receive(:query_range).with('query_range_a', any_args).and_return(query_range_result)
- allow(client).to receive(:query_range).with('query_range_b', any_args).and_return(query_range_result)
- allow(client).to receive(:query_range).with('query_range_empty', any_args).and_return([])
- end
-
- it 'return group data only for queries with results' do
- expected = [
- {
- group: 'name',
- priority: 1,
- metrics: [
- {
- title: 'title', weight: 1, y_label: 'Values', queries: [
- { query_range: 'query_range_a', result: query_range_result },
- { query_range: 'query_range_b', label: 'label', unit: 'unit', result: query_range_result }
- ]
- }
- ]
- }
- ]
-
- expect(query_result.to_json).to match_schema('prometheus/additional_metrics_query_result')
- expect(query_result).to eq(expected)
- end
- end
- end
-
- context 'with two groups with one metric each' do
- let(:metrics) { [simple_metric(queries: [simple_query])] }
-
- before do
- allow(metric_group_class).to receive(:common_metrics).and_return(
- [
- simple_metric_group(name: 'group_a', metrics: [simple_metric(queries: [simple_query])]),
- simple_metric_group(name: 'group_b', metrics: [simple_metric(title: 'title_b', queries: [simple_query('b')])])
- ])
- allow(client).to receive(:label_values).and_return(metric_names)
- end
-
- context 'when both queries return results' do
- before do
- allow(client).to receive(:query_range).with('query_range_a', any_args).and_return(query_range_result)
- allow(client).to receive(:query_range).with('query_range_b', any_args).and_return(query_range_result)
- end
-
- it 'return group data both queries' do
- queries_with_result_a = { queries: [{ query_range: 'query_range_a', result: query_range_result }] }
- queries_with_result_b = { queries: [{ query_range: 'query_range_b', result: query_range_result }] }
-
- expect(query_result.to_json).to match_schema('prometheus/additional_metrics_query_result')
-
- expect(query_result.count).to eq(2)
- expect(query_result).to all(satisfy { |r| r[:metrics].count == 1 })
-
- expect(query_result[0][:metrics].first).to include(queries_with_result_a)
- expect(query_result[1][:metrics].first).to include(queries_with_result_b)
- end
- end
-
- context 'when one query returns result' do
- before do
- allow(client).to receive(:query_range).with('query_range_a', any_args).and_return(query_range_result)
- allow(client).to receive(:query_range).with('query_range_b', any_args).and_return([])
- end
-
- it 'return group data only for query with results' do
- queries_with_result = { queries: [{ query_range: 'query_range_a', result: query_range_result }] }
-
- expect(query_result.to_json).to match_schema('prometheus/additional_metrics_query_result')
-
- expect(query_result.count).to eq(1)
- expect(query_result).to all(satisfy { |r| r[:metrics].count == 1 })
-
- expect(query_result.first[:metrics].first).to include(queries_with_result)
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
index 4b27f1f2520..7cbaf40721a 100644
--- a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
@@ -2,7 +2,6 @@
RSpec.shared_examples 'close quick action' do |issuable_type|
include Features::NotesHelpers
- include ContentEditorHelpers
before do
project.add_maintainer(maintainer)
@@ -77,7 +76,6 @@ RSpec.shared_examples 'close quick action' do |issuable_type|
context "preview of note on #{issuable_type}", :js do
it 'explains close quick action' do
visit public_send("project_#{issuable_type}_path", project, issuable)
- close_rich_text_promo_popover_if_present
preview_note("this is done, close\n/close") do
expect(page).not_to have_content '/close'
diff --git a/spec/support/shared_examples/redis/redis_shared_examples.rb b/spec/support/shared_examples/redis/redis_shared_examples.rb
index 23ec4a632b7..1270efd4701 100644
--- a/spec/support/shared_examples/redis/redis_shared_examples.rb
+++ b/spec/support/shared_examples/redis/redis_shared_examples.rb
@@ -379,6 +379,24 @@ RSpec.shared_examples "redis_shared_examples" do
}
end
+ let(:resque_yaml_config_with_only_cert) do
+ {
+ url: 'rediss://localhost:6380',
+ ssl_params: {
+ cert_file: '/tmp/client.crt'
+ }
+ }
+ end
+
+ let(:resque_yaml_config_with_only_key) do
+ {
+ url: 'rediss://localhost:6380',
+ ssl_params: {
+ key_file: '/tmp/client.key'
+ }
+ }
+ end
+
let(:parsed_config_with_tls) do
{
url: 'rediss://localhost:6380',
@@ -389,6 +407,24 @@ RSpec.shared_examples "redis_shared_examples" do
}
end
+ let(:parsed_config_with_only_cert) do
+ {
+ url: 'rediss://localhost:6380',
+ ssl_params: {
+ cert: dummy_certificate
+ }
+ }
+ end
+
+ let(:parsed_config_with_only_key) do
+ {
+ url: 'rediss://localhost:6380',
+ ssl_params: {
+ key: dummy_key
+ }
+ }
+ end
+
before do
allow(::File).to receive(:exist?).and_call_original
allow(::File).to receive(:read).and_call_original
@@ -433,6 +469,34 @@ RSpec.shared_examples "redis_shared_examples" do
end
end
+ context 'when only certificate file is specified' do
+ before do
+ allow(::File).to receive(:exist?).with("/tmp/client.crt").and_return(true)
+ allow(::File).to receive(:read).with("/tmp/client.crt").and_return("DUMMY_CERTIFICATE")
+ allow(OpenSSL::X509::Certificate).to receive(:new).with("DUMMY_CERTIFICATE").and_return(dummy_certificate)
+ allow(::File).to receive(:exist?).with("/tmp/client.key").and_return(false)
+ end
+
+ it 'renders resque.yml correctly' do
+ expect(subject.send(:parse_client_tls_options,
+ resque_yaml_config_with_only_cert)).to eq(parsed_config_with_only_cert)
+ end
+ end
+
+ context 'when only key file is specified' do
+ before do
+ allow(::File).to receive(:exist?).with("/tmp/client.crt").and_return(false)
+ allow(::File).to receive(:exist?).with("/tmp/client.key").and_return(true)
+ allow(::File).to receive(:read).with("/tmp/client.key").and_return("DUMMY_KEY")
+ allow(OpenSSL::PKey).to receive(:read).with("DUMMY_KEY").and_return(dummy_key)
+ end
+
+ it 'renders resque.yml correctly' do
+ expect(subject.send(:parse_client_tls_options,
+ resque_yaml_config_with_only_key)).to eq(parsed_config_with_only_key)
+ end
+ end
+
context 'when configuration valid TLS related options' do
before do
allow(::File).to receive(:exist?).with("/tmp/client.crt").and_return(true)
diff --git a/spec/support/shared_examples/ref_extraction_shared_examples.rb b/spec/support/shared_examples/ref_extraction_shared_examples.rb
new file mode 100644
index 00000000000..f51c3a16406
--- /dev/null
+++ b/spec/support/shared_examples/ref_extraction_shared_examples.rb
@@ -0,0 +1,165 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'extracts ref vars' do
+ describe '#extract!' do
+ context 'when ref contains %20' do
+ let(:ref) { 'foo%20bar' }
+
+ it 'is not converted to a space in @id' do
+ container.repository.add_branch(owner, 'foo%20bar', 'master')
+
+ ref_extractor.extract!
+
+ expect(ref_extractor.id).to start_with('foo%20bar/')
+ end
+ end
+
+ context 'when ref contains trailing space' do
+ let(:ref) { 'master ' }
+
+ it 'strips surrounding space' do
+ ref_extractor.extract!
+
+ expect(ref_extractor.ref).to eq('master')
+ end
+ end
+
+ context 'when ref contains leading space' do
+ let(:ref) { ' master ' }
+
+ it 'strips surrounding space' do
+ ref_extractor.extract!
+
+ expect(ref_extractor.ref).to eq('master')
+ end
+ end
+
+ context 'when path contains space' do
+ let(:ref) { '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' }
+ let(:path) { 'with space' }
+
+ it 'is not converted to %20 in @path' do
+ ref_extractor.extract!
+
+ expect(ref_extractor.path).to eq(path)
+ end
+ end
+
+ context 'when override_id is given' do
+ let(:ref_extractor) do
+ described_class.new(container, params, override_id: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e')
+ end
+
+ it 'uses override_id' do
+ ref_extractor.extract!
+
+ expect(ref_extractor.id).to eq('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e')
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'extracts ref method' do
+ describe '#extract_ref' do
+ it 'returns an empty pair when no repository_container is set' do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:repository_container).and_return(nil)
+ end
+ expect(ref_extractor.extract_ref('master/CHANGELOG')).to eq(['', ''])
+ end
+
+ context 'without a path' do
+ it 'extracts a valid branch' do
+ expect(ref_extractor.extract_ref('master')).to eq(['master', ''])
+ end
+
+ it 'extracts a valid tag' do
+ expect(ref_extractor.extract_ref('v2.0.0')).to eq(['v2.0.0', ''])
+ end
+
+ it 'extracts a valid commit ref' do
+ expect(ref_extractor.extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062')).to eq(
+ ['f4b14494ef6abf3d144c28e4af0c20143383e062', '']
+ )
+ end
+
+ it 'falls back to a primitive split for an invalid ref' do
+ expect(ref_extractor.extract_ref('stable')).to eq(['stable', ''])
+ end
+
+ it 'does not fetch ref names when there is no slash' do
+ expect(ref_extractor).not_to receive(:ref_names)
+
+ ref_extractor.extract_ref('master')
+ end
+
+ it 'fetches ref names when there is a slash' do
+ expect(ref_extractor).to receive(:ref_names).and_call_original
+
+ ref_extractor.extract_ref('release/app/v1.0.0')
+ end
+ end
+
+ context 'with a path' do
+ it 'extracts a valid branch' do
+ expect(ref_extractor.extract_ref('foo/bar/baz/CHANGELOG')).to eq(
+ ['foo/bar/baz', 'CHANGELOG'])
+ end
+
+ it 'extracts a valid tag' do
+ expect(ref_extractor.extract_ref('v2.0.0/CHANGELOG')).to eq(['v2.0.0', 'CHANGELOG'])
+ end
+
+ it 'extracts a valid commit SHA' do
+ expect(ref_extractor.extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG')).to eq(
+ %w[f4b14494ef6abf3d144c28e4af0c20143383e062 CHANGELOG]
+ )
+ end
+
+ it 'falls back to a primitive split for an invalid ref' do
+ expect(ref_extractor.extract_ref('stable/CHANGELOG')).to eq(%w[stable CHANGELOG])
+ end
+
+ it 'extracts the longest matching ref' do
+ expect(ref_extractor.extract_ref('release/app/v1.0.0/README.md')).to eq(
+ ['release/app/v1.0.0', 'README.md'])
+ end
+
+ context 'when the repository does not have ambiguous refs' do
+ before do
+ allow(container.repository).to receive(:has_ambiguous_refs?).and_return(false)
+ end
+
+ it 'does not fetch all ref names when the first path component is a ref' do
+ expect(ref_extractor).not_to receive(:ref_names)
+ expect(container.repository).to receive(:branch_names_include?).with('v1.0.0').and_return(false)
+ expect(container.repository).to receive(:tag_names_include?).with('v1.0.0').and_return(true)
+
+ expect(ref_extractor.extract_ref('v1.0.0/doc/README.md')).to eq(['v1.0.0', 'doc/README.md'])
+ end
+
+ it 'fetches all ref names when the first path component is not a ref' do
+ expect(ref_extractor).to receive(:ref_names).and_call_original
+ expect(container.repository).to receive(:branch_names_include?).with('release').and_return(false)
+ expect(container.repository).to receive(:tag_names_include?).with('release').and_return(false)
+
+ expect(ref_extractor.extract_ref('release/app/doc/README.md')).to eq(['release/app', 'doc/README.md'])
+ end
+ end
+
+ context 'when the repository has ambiguous refs' do
+ before do
+ allow(container.repository).to receive(:has_ambiguous_refs?).and_return(true)
+ end
+
+ it 'always fetches all ref names' do
+ expect(ref_extractor).to receive(:ref_names).and_call_original
+ expect(container.repository).not_to receive(:branch_names_include?)
+ expect(container.repository).not_to receive(:tag_names_include?)
+
+ expect(ref_extractor.extract_ref('v1.0.0/doc/README.md')).to eq(['v1.0.0', 'doc/README.md'])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
index 6a77de4266f..7e0efd05dd7 100644
--- a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
@@ -1,42 +1,45 @@
# frozen_string_literal: true
-RSpec.shared_context 'Composer user type' do |user_type, add_member|
+RSpec.shared_context 'Composer user type' do |member_role: nil|
before do
- group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
- project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ if member_role
+ group.send("add_#{member_role}", user)
+ project.send("add_#{member_role}", user)
+ end
end
end
-RSpec.shared_examples 'Composer package index with version' do |schema_path|
+RSpec.shared_examples 'Composer package index with version' do |schema_path, expected_status|
it 'returns the package index' do
subject
- expect(response).to have_gitlab_http_status(status)
+ expect(response).to have_gitlab_http_status(expected_status)
- if status == :success
+ if expected_status == :success
expect(response).to match_response_schema(schema_path)
expect(json_response).to eq presenter.root
end
end
end
-RSpec.shared_examples 'Composer package index' do |user_type, status, add_member, include_package|
- include_context 'Composer user type', user_type, add_member do
- let(:expected_packages) { include_package == :include_package ? [package] : [] }
- let(:presenter) { ::Packages::Composer::PackagesPresenter.new(group, expected_packages ) }
+RSpec.shared_examples 'Composer package index' do |member_role:, expected_status:, package_returned:|
+ include_context 'Composer user type', member_role: member_role do
+ let_it_be(:expected_packages) { package_returned ? [package] : [] }
+ let_it_be(:presenter) { ::Packages::Composer::PackagesPresenter.new(group, expected_packages ) }
- it_behaves_like 'Composer package index with version', 'public_api/v4/packages/composer/index'
+ it_behaves_like 'Composer package index with version', 'public_api/v4/packages/composer/index', expected_status
context 'with version 2' do
+ let_it_be(:presenter) { ::Packages::Composer::PackagesPresenter.new(group, expected_packages, true ) }
let(:headers) { super().merge('User-Agent' => 'Composer/2.0.9 (Darwin; 19.6.0; PHP 7.4.8; cURL 7.71.1)') }
- it_behaves_like 'Composer package index with version', 'public_api/v4/packages/composer/index_v2'
+ it_behaves_like 'Composer package index with version', 'public_api/v4/packages/composer/index_v2', expected_status
end
end
end
-RSpec.shared_examples 'Composer empty provider index' do |user_type, status, add_member = true|
- include_context 'Composer user type', user_type, add_member do
+RSpec.shared_examples 'Composer empty provider index' do |member_role:, expected_status:|
+ include_context 'Composer user type', member_role: member_role do
it 'returns the package index' do
subject
@@ -47,24 +50,24 @@ RSpec.shared_examples 'Composer empty provider index' do |user_type, status, add
end
end
-RSpec.shared_examples 'Composer provider index' do |user_type, status, add_member = true|
- include_context 'Composer user type', user_type, add_member do
+RSpec.shared_examples 'Composer provider index' do |member_role:, expected_status:|
+ include_context 'Composer user type', member_role: member_role do
it 'returns the package index' do
subject
- expect(response).to have_gitlab_http_status(status)
+ expect(response).to have_gitlab_http_status(expected_status)
expect(response).to match_response_schema('public_api/v4/packages/composer/provider')
expect(json_response['providers']).to include(package.name)
end
end
end
-RSpec.shared_examples 'Composer package api request' do |user_type, status, add_member = true|
- include_context 'Composer user type', user_type, add_member do
+RSpec.shared_examples 'Composer package api request' do |member_role:, expected_status:|
+ include_context 'Composer user type', member_role: member_role do
it 'returns the package index' do
subject
- expect(response).to have_gitlab_http_status(status)
+ expect(response).to have_gitlab_http_status(expected_status)
expect(response).to match_response_schema('public_api/v4/packages/composer/package')
expect(json_response['packages']).to include(package.name)
expect(json_response['packages'][package.name]).to include(package.version)
@@ -72,18 +75,13 @@ RSpec.shared_examples 'Composer package api request' do |user_type, status, add_
end
end
-RSpec.shared_examples 'Composer package creation' do |user_type, status, add_member = true|
- context "for user type #{user_type}" do
- before do
- group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
- project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
- end
-
+RSpec.shared_examples 'Composer package creation' do |expected_status:, member_role: nil|
+ include_context 'Composer user type', member_role: member_role do
it 'creates package files' do
expect { subject }
.to change { project.packages.composer.count }.by(1)
- expect(response).to have_gitlab_http_status(status)
+ expect(response).to have_gitlab_http_status(expected_status)
end
it_behaves_like 'a package tracking event', described_class.name, 'push_package'
@@ -100,42 +98,38 @@ RSpec.shared_examples 'Composer package creation' do |user_type, status, add_mem
end
end
-RSpec.shared_examples 'process Composer api request' do |user_type, status, add_member = true|
- context "for user type #{user_type}" do
- before do
- group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
- project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
- end
-
- it_behaves_like 'returning response status', status
- it_behaves_like 'bumping the package last downloaded at field' if status == :success
+RSpec.shared_examples 'process Composer api request' do |expected_status:, member_role: nil, **extra|
+ include_context 'Composer user type', member_role: member_role do
+ it_behaves_like 'returning response status', expected_status
+ it_behaves_like 'bumping the package last downloaded at field' if expected_status == :success
end
end
-RSpec.shared_context 'Composer auth headers' do |user_role, user_token, auth_method = :token|
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
-
+RSpec.shared_context 'Composer auth headers' do |token_type:, valid_token:, auth_method: :token|
let(:headers) do
- if user_role == :anonymous
- {}
- elsif auth_method == :token
- { 'Private-Token' => token }
+ if token_type == :user
+ token = valid_token ? personal_access_token.token : 'wrong'
+ auth_method == :token ? { 'Private-Token' => token } : basic_auth_header(user.username, token)
+ elsif token_type == :job && valid_token
+ auth_method == :token ? { 'Job-Token' => job.token } : job_basic_auth_header(job)
else
- basic_auth_header(user.username, token)
+ {} # Anonymous user
end
end
end
-RSpec.shared_context 'Composer api project access' do |project_visibility_level, user_role, user_token, auth_method|
- include_context 'Composer auth headers', user_role, user_token, auth_method do
+RSpec.shared_context 'Composer api project access' do |auth_method:, project_visibility_level:, token_type:,
+ valid_token: true|
+ include_context 'Composer auth headers', auth_method: auth_method, token_type: token_type, valid_token: valid_token do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
end
end
end
-RSpec.shared_context 'Composer api group access' do |project_visibility_level, user_role, user_token|
- include_context 'Composer auth headers', user_role, user_token do
+RSpec.shared_context 'Composer api group access' do |auth_method:, project_visibility_level:, token_type:,
+ valid_token: true|
+ include_context 'Composer auth headers', auth_method: auth_method, token_type: token_type, valid_token: valid_token do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
@@ -148,13 +142,13 @@ RSpec.shared_examples 'rejects Composer access with unknown group id' do
let(:group) { double(id: non_existing_record_id) }
context 'as anonymous' do
- it_behaves_like 'process Composer api request', :anonymous, :not_found
+ it_behaves_like 'process Composer api request', expected_status: :unauthorized
end
context 'as authenticated user' do
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
- it_behaves_like 'process Composer api request', :anonymous, :not_found
+ it_behaves_like 'process Composer api request', expected_status: :not_found
end
end
end
@@ -164,13 +158,13 @@ RSpec.shared_examples 'rejects Composer access with unknown project id' do
let(:project) { double(id: non_existing_record_id) }
context 'as anonymous' do
- it_behaves_like 'process Composer api request', :anonymous, :unauthorized
+ it_behaves_like 'process Composer api request', expected_status: :unauthorized
end
context 'as authenticated user' do
subject { get api(url), params: params, headers: basic_auth_header(user.username, personal_access_token.token) }
- it_behaves_like 'process Composer api request', :anonymous, :not_found
+ it_behaves_like 'process Composer api request', expected_status: :not_found
end
end
end
@@ -191,7 +185,7 @@ RSpec.shared_examples 'Composer access with deploy tokens' do
context 'invalid token' do
let(:headers) { basic_auth_header(deploy_token.username, 'bar') }
- it_behaves_like 'returning response status', :not_found
+ it_behaves_like 'returning response status', :unauthorized
end
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
index 04f340fef37..c6e4aba6968 100644
--- a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
@@ -408,28 +408,6 @@ RSpec.shared_examples 'graphql issue list request spec' do
include_examples 'N+1 query check'
end
- context 'when requesting participants' do
- let(:search_params) { { iids: [issue_a.iid.to_s, issue_c.iid.to_s] } }
- let(:requested_fields) { 'participants { nodes { name } }' }
-
- before do
- create(:award_emoji, :upvote, awardable: issue_a)
- create(:award_emoji, :upvote, awardable: issue_b)
- create(:award_emoji, :upvote, awardable: issue_c)
-
- note_with_emoji_a = create(:note_on_issue, noteable: issue_a, project: issue_a.project)
- note_with_emoji_b = create(:note_on_issue, noteable: issue_b, project: issue_b.project)
- note_with_emoji_c = create(:note_on_issue, noteable: issue_c, project: issue_c.project)
-
- create(:award_emoji, :upvote, awardable: note_with_emoji_a)
- create(:award_emoji, :upvote, awardable: note_with_emoji_b)
- create(:award_emoji, :upvote, awardable: note_with_emoji_c)
- end
-
- # Executes 3 extra queries to fetch participant_attrs
- include_examples 'N+1 query check', threshold: 3
- end
-
context 'when requesting labels', :use_sql_query_cache do
let(:requested_fields) { 'labels { nodes { id } }' }
let(:extra_iid_for_second_query) { same_project_issue2.iid.to_s }
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 5f043cdd996..a4091d6bceb 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
@@ -68,22 +68,22 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
nil | :unscoped | false | :public | nil | :accept | :ok
nil | :non_existing | true | :public | nil | :redirect | :redirected
nil | :non_existing | false | :public | nil | :reject | :not_found
- nil | :scoped_naming_convention | true | :private | nil | :reject | :not_found
- nil | :scoped_naming_convention | false | :private | nil | :reject | :not_found
- nil | :scoped_no_naming_convention | true | :private | nil | :reject | :not_found
- nil | :scoped_no_naming_convention | false | :private | nil | :reject | :not_found
- nil | :unscoped | true | :private | nil | :reject | :not_found
- nil | :unscoped | false | :private | nil | :reject | :not_found
+ nil | :scoped_naming_convention | true | :private | nil | :reject | :unauthorized
+ nil | :scoped_naming_convention | false | :private | nil | :reject | :unauthorized
+ nil | :scoped_no_naming_convention | true | :private | nil | :reject | :unauthorized
+ nil | :scoped_no_naming_convention | false | :private | nil | :reject | :unauthorized
+ nil | :unscoped | true | :private | nil | :reject | :unauthorized
+ nil | :unscoped | false | :private | nil | :reject | :unauthorized
nil | :non_existing | true | :private | nil | :redirect | :redirected
- nil | :non_existing | false | :private | nil | :reject | :not_found
- nil | :scoped_naming_convention | true | :internal | nil | :reject | :not_found
- nil | :scoped_naming_convention | false | :internal | nil | :reject | :not_found
- nil | :scoped_no_naming_convention | true | :internal | nil | :reject | :not_found
- nil | :scoped_no_naming_convention | false | :internal | nil | :reject | :not_found
- nil | :unscoped | true | :internal | nil | :reject | :not_found
- nil | :unscoped | false | :internal | nil | :reject | :not_found
+ nil | :non_existing | false | :private | nil | :reject | :unauthorized
+ nil | :scoped_naming_convention | true | :internal | nil | :reject | :unauthorized
+ nil | :scoped_naming_convention | false | :internal | nil | :reject | :unauthorized
+ nil | :scoped_no_naming_convention | true | :internal | nil | :reject | :unauthorized
+ nil | :scoped_no_naming_convention | false | :internal | nil | :reject | :unauthorized
+ nil | :unscoped | true | :internal | nil | :reject | :unauthorized
+ nil | :unscoped | false | :internal | nil | :reject | :unauthorized
nil | :non_existing | true | :internal | nil | :redirect | :redirected
- nil | :non_existing | false | :internal | nil | :reject | :not_found
+ nil | :non_existing | false | :internal | nil | :reject | :unauthorized
:oauth | :scoped_naming_convention | true | :public | :guest | :accept | :ok
:oauth | :scoped_naming_convention | true | :public | :reporter | :accept | :ok
@@ -280,11 +280,15 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
end
end
- if (scope == :group && params[:package_name_type] == :non_existing) &&
- (!params[:request_forward] || (!params[:auth] && params[:request_forward] && params[:visibility] != :public))
+ if scope == :group && params[:package_name_type] == :non_existing && !params[:request_forward] && params[:auth]
status = :not_found
end
+ if scope == :group && params[:package_name_type] == :non_existing && params[:request_forward] && !params[:auth] && params[:visibility] != :public
+ example_name = 'reject metadata request'
+ status = :unauthorized
+ end
+
# Check the error message for :not_found
example_name = 'returning response status with error' if status == :not_found
@@ -522,14 +526,14 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project|
nil | :scoped_no_naming_convention | :public | nil | :accept | :ok
nil | :unscoped | :public | nil | :accept | :ok
nil | :non_existing | :public | nil | :reject | :not_found
- nil | :scoped_naming_convention | :private | nil | :reject | :not_found
- nil | :scoped_no_naming_convention | :private | nil | :reject | :not_found
- nil | :unscoped | :private | nil | :reject | :not_found
- nil | :non_existing | :private | nil | :reject | :not_found
- nil | :scoped_naming_convention | :internal | nil | :reject | :not_found
- nil | :scoped_no_naming_convention | :internal | nil | :reject | :not_found
- nil | :unscoped | :internal | nil | :reject | :not_found
- nil | :non_existing | :internal | nil | :reject | :not_found
+ nil | :scoped_naming_convention | :private | nil | :reject | :unauthorized
+ nil | :scoped_no_naming_convention | :private | nil | :reject | :unauthorized
+ nil | :unscoped | :private | nil | :reject | :unauthorized
+ nil | :non_existing | :private | nil | :reject | :unauthorized
+ nil | :scoped_naming_convention | :internal | nil | :reject | :unauthorized
+ nil | :scoped_no_naming_convention | :internal | nil | :reject | :unauthorized
+ nil | :unscoped | :internal | nil | :reject | :unauthorized
+ nil | :non_existing | :internal | nil | :reject | :unauthorized
:oauth | :scoped_naming_convention | :public | :guest | :accept | :ok
:oauth | :scoped_naming_convention | :public | :reporter | :accept | :ok
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
index 1be99040ae5..f8e78c8c9b1 100644
--- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -357,12 +357,7 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st
end
context 'with normalized package version' do
- let(:normalized_version) { '0.1.0' }
- let(:url) { "/projects/#{target.id}/packages/nuget/download/#{package.name}/#{normalized_version}/#{package.name}.#{package.version}.#{format}" }
-
- before do
- package.nuget_metadatum.update_column(:normalized_version, normalized_version)
- end
+ let(:package_version) { '0.1.0' }
it_behaves_like 'returning response status', status
@@ -737,3 +732,19 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
end
end
end
+
+RSpec.shared_examples 'process nuget delete request' do |user_type, status|
+ context "for user type #{user_type}" do
+ before do
+ target.send("add_#{user_type}", user) if user_type
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'a package tracking event', 'API::NugetPackages', 'delete_package'
+
+ it 'marks package for deletion' do
+ expect { subject }.to change { package.reset.status }.from('default').to('pending_destruction')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/organizations_shared_examples.rb b/spec/support/shared_examples/requests/organizations_shared_examples.rb
new file mode 100644
index 00000000000..78e7c3c6bde
--- /dev/null
+++ b/spec/support/shared_examples/requests/organizations_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'organization - successful response' do
+ it 'renders 200 OK' do
+ gitlab_request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
+
+RSpec.shared_examples 'organization - not found response' do
+ it 'renders 404 NOT_FOUND' do
+ gitlab_request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+end
+
+RSpec.shared_examples 'organization - redirects to sign in page' do
+ it 'redirects to sign in page' do
+ gitlab_request
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+end
+
+RSpec.shared_examples 'organization - action disabled by `ui_for_organizations` feature flag' do
+ context 'when `ui_for_organizations` feature flag is disabled' do
+ before do
+ stub_feature_flags(ui_for_organizations: false)
+ end
+
+ it_behaves_like 'organization - not found response'
+ end
+end
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
index 34188a8d18a..6abf8b242f1 100644
--- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -284,6 +284,48 @@ RSpec.shared_examples 'a container registry auth service' do
end
end
+ describe '.push_pull_nested_repositories_access_token' do
+ let_it_be(:project) { create(:project) }
+
+ let(:token) { described_class.push_pull_nested_repositories_access_token(project.full_path) }
+ let(:access) do
+ [
+ {
+ 'type' => 'repository',
+ 'name' => project.full_path,
+ 'actions' => %w[pull push],
+ 'meta' => { 'project_path' => project.full_path }
+ },
+ {
+ 'type' => 'repository',
+ 'name' => "#{project.full_path}/*",
+ 'actions' => %w[pull],
+ 'meta' => { 'project_path' => project.full_path }
+ }
+ ]
+ end
+
+ subject { { token: token } }
+
+ it 'has the correct scope' do
+ expect(payload).to include('access' => access)
+ end
+
+ it_behaves_like 'a valid token'
+ it_behaves_like 'not a container repository factory'
+
+ context 'with path ending with a slash' do
+ let(:token) { described_class.push_pull_nested_repositories_access_token("#{project.full_path}/") }
+
+ it 'has the correct scope' do
+ expect(payload).to include('access' => access)
+ end
+
+ it_behaves_like 'a valid token'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
context 'user authorization' do
let_it_be(:current_user) { create(:user) }
@@ -780,12 +822,12 @@ RSpec.shared_examples 'a container registry auth service' do
context 'for project that disables repository' do
let_it_be(:project) { create(:project, :public, :repository_disabled) }
- context 'disallow when pulling' do
+ context 'allow when pulling' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull"] }
end
- it_behaves_like 'an inaccessible'
+ it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
end
@@ -1301,7 +1343,7 @@ RSpec.shared_examples 'a container registry auth service' do
end
describe '#access_token' do
- let(:token) { described_class.access_token(['pull'], [bad_project.full_path]) }
+ let(:token) { described_class.access_token({ bad_project.full_path => ['pull'] }) }
let(:access) do
[{ 'type' => 'repository',
'name' => bad_project.full_path,
diff --git a/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb b/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb
index 83a2f3136b4..10dc185157c 100644
--- a/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb
@@ -3,6 +3,7 @@
RSpec.shared_examples 'issuable link creation' do |use_references: true|
let(:items_param) { use_references ? :issuable_references : :target_issuable }
let(:response_keys) { [:status, :created_references] }
+ let(:async_notes) { false }
let(:already_assigned_error_msg) { "#{issuable_type.capitalize}(s) already assigned" }
let(:permission_error_status) { issuable_type == :issue ? 403 : 404 }
let(:permission_error_msg) do
@@ -85,17 +86,27 @@ RSpec.shared_examples 'issuable link creation' do |use_references: true|
end
it 'creates notes' do
- # First two-way relation notes
- expect(SystemNoteService).to receive(:relate_issuable)
- .with(issuable, issuable2, user)
- expect(SystemNoteService).to receive(:relate_issuable)
- .with(issuable2, issuable, user)
-
- # Second two-way relation notes
- expect(SystemNoteService).to receive(:relate_issuable)
- .with(issuable, issuable3, user)
- expect(SystemNoteService).to receive(:relate_issuable)
- .with(issuable3, issuable, user)
+ if async_notes
+ expect(Issuable::RelatedLinksCreateWorker).to receive(:perform_async) do |args|
+ expect(args).to eq(
+ {
+ issuable_class: issuable.class.name,
+ issuable_id: issuable.id,
+ link_ids: issuable_link_class.where(source: issuable).last(2).pluck(:id),
+ link_type: 'relates_to',
+ user_id: user.id
+ }
+ )
+ end
+ else
+ # First two-way relation notes
+ expect(SystemNoteService).to receive(:relate_issuable).with(issuable, issuable2, user)
+ expect(SystemNoteService).to receive(:relate_issuable).with(issuable2, issuable, user)
+
+ # Second two-way relation notes
+ expect(SystemNoteService).to receive(:relate_issuable).with(issuable, issuable3, user)
+ expect(SystemNoteService).to receive(:relate_issuable).with(issuable3, issuable, user)
+ end
subject
end
@@ -105,10 +116,24 @@ RSpec.shared_examples 'issuable link creation' do |use_references: true|
let(:params) { set_params([issuable_a, issuable_b]) }
it 'creates notes only for new relations' do
- expect(SystemNoteService).to receive(:relate_issuable).with(issuable, issuable_a, anything)
- expect(SystemNoteService).to receive(:relate_issuable).with(issuable_a, issuable, anything)
- expect(SystemNoteService).not_to receive(:relate_issuable).with(issuable, issuable_b, anything)
- expect(SystemNoteService).not_to receive(:relate_issuable).with(issuable_b, issuable, anything)
+ if async_notes
+ expect(Issuable::RelatedLinksCreateWorker).to receive(:perform_async) do |args|
+ expect(args).to eq(
+ {
+ issuable_class: issuable.class.name,
+ issuable_id: issuable.id,
+ link_ids: issuable_link_class.where(source: issuable).last(1).pluck(:id),
+ link_type: 'relates_to',
+ user_id: user.id
+ }
+ )
+ end
+ else
+ expect(SystemNoteService).to receive(:relate_issuable).with(issuable, issuable_a, anything)
+ expect(SystemNoteService).to receive(:relate_issuable).with(issuable_a, issuable, anything)
+ expect(SystemNoteService).not_to receive(:relate_issuable).with(issuable, issuable_b, anything)
+ expect(SystemNoteService).not_to receive(:relate_issuable).with(issuable_b, issuable, anything)
+ end
subject
end
diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
index 2070cac24b0..7d786dbeb87 100644
--- a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
@@ -85,14 +85,15 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
it 'unmarks the repository as read-only without updating the repository storage' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- allow(project_repository_double).to receive(:replicate)
+ expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
- allow(project_repository_double).to receive(:checksum)
+ expect(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:replicate)
+ expect(repository_double).to receive(:replicate)
.with(repository.raw)
.and_raise(Gitlab::Git::CommandError)
+ expect(repository_double).to receive(:remove)
expect do
subject.execute
@@ -138,14 +139,15 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:replicate)
+ expect(repository_double).to receive(:replicate)
.with(repository.raw)
- allow(repository_double).to receive(:checksum)
+ expect(repository_double).to receive(:checksum)
.and_return('not matching checksum')
+ expect(repository_double).to receive(:remove)
expect do
subject.execute
- end.to raise_error(UpdateRepositoryStorageMethods::Error, /Failed to verify \w+ repository checksum from \w+ to not matching checksum/)
+ end.to raise_error(Repositories::ReplicateService::Error, /Failed to verify \w+ repository checksum from \w+ to not matching checksum/)
expect(project).not_to be_repository_read_only
expect(project.repository_storage).to eq('default')
diff --git a/spec/support/shared_examples/services/protected_branches_shared_examples.rb b/spec/support/shared_examples/services/protected_branches_shared_examples.rb
index ce607a6b956..15c63865720 100644
--- a/spec/support/shared_examples/services/protected_branches_shared_examples.rb
+++ b/spec/support/shared_examples/services/protected_branches_shared_examples.rb
@@ -1,11 +1,24 @@
# frozen_string_literal: true
RSpec.shared_context 'with scan result policy blocking protected branches' do
+ include RepoHelpers
+
+ let(:policy_path) { Security::OrchestrationPolicyConfiguration::POLICY_PATH }
+ let_it_be(:policy_project) { create(:project, :repository) }
+ let(:default_branch) { policy_project.default_branch }
+
+ let(:policy_yaml) do
+ build(:orchestration_policy_yaml, scan_execution_policy: [], scan_result_policy: [scan_result_policy])
+ end
+
+ let(:scan_result_policy) do
+ build(:scan_result_policy, branches: [branch_name], approval_settings: { block_unprotecting_branches: true })
+ end
+
before do
- create(
- :scan_result_policy_read,
- :blocking_protected_branches,
- project: project)
+ policy_configuration.update_attribute(:security_policy_management_project, policy_project)
+
+ create_file_in_repo(policy_project, default_branch, default_branch, policy_path, policy_yaml)
stub_licensed_features(security_orchestration_policies: true)
end
diff --git a/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb b/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb
index d61458db3b3..0545be7c741 100644
--- a/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb
+++ b/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb
@@ -56,4 +56,19 @@ RSpec.shared_examples 'work item hierarchy restrictions importer' do
expect(WorkItems::HierarchyRestriction.count).to eq(7)
end
end
+
+ context 'when restrictions contain attributes not present in the table' do
+ before do
+ allow(WorkItems::HierarchyRestriction)
+ .to receive(:column_names).and_return(%w[parent_type_id child_type_id])
+ end
+
+ it 'filters out missing columns' do
+ expect(WorkItems::HierarchyRestriction).to receive(:upsert_all) do |args|
+ expect(args[0].keys).to eq(%i[parent_type_id child_type_id])
+ end
+
+ subject
+ end
+ end
end
diff --git a/spec/support/shared_examples/work_item_related_link_restrictions_importer.rb b/spec/support/shared_examples/work_item_related_link_restrictions_importer.rb
new file mode 100644
index 00000000000..935ad2ba472
--- /dev/null
+++ b/spec/support/shared_examples/work_item_related_link_restrictions_importer.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'work item related links restrictions importer' do
+ shared_examples_for 'adds restrictions' do
+ it "adds all restrictions if they don't exist" do
+ expect { subject }.to change { WorkItems::RelatedLinkRestriction.count }.from(0).to(34)
+ end
+ end
+
+ context 'when restrictions are missing' do
+ before do
+ WorkItems::RelatedLinkRestriction.delete_all
+ end
+
+ it_behaves_like 'adds restrictions'
+ end
+
+ context 'when base types are missing' do
+ before do
+ WorkItems::Type.delete_all
+ end
+
+ it_behaves_like 'adds restrictions'
+ end
+
+ context 'when some restrictions are missing' do
+ before do
+ Gitlab::DatabaseImporters::WorkItems::RelatedLinksRestrictionsImporter.upsert_restrictions
+ WorkItems::RelatedLinkRestriction.limit(1).delete_all
+ end
+
+ it 'inserts missing restrictions and does nothing if some already existed' do
+ expect { subject }.to make_queries_matching(/INSERT/, 1).and(
+ change { WorkItems::RelatedLinkRestriction.count }.by(1)
+ )
+ expect(WorkItems::RelatedLinkRestriction.count).to eq(34)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/workers/gitlab/github_import/stage_methods_shared_examples.rb b/spec/support/shared_examples/workers/gitlab/github_import/stage_methods_shared_examples.rb
new file mode 100644
index 00000000000..af5bf33a9a6
--- /dev/null
+++ b/spec/support/shared_examples/workers/gitlab/github_import/stage_methods_shared_examples.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Gitlab::GithubImport::StageMethods do
+ describe '.sidekiq_retries_exhausted' do
+ it 'tracks the exception and marks the import as failed' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track)
+ .with(
+ project_id: 1,
+ exception: StandardError,
+ fail_import: true,
+ error_source: anything
+ )
+
+ described_class.sidekiq_retries_exhausted_block.call({ 'args' => [1] }, StandardError.new)
+ end
+ end
+end