diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-19 15:57:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-19 15:57:54 +0300 |
commit | 419c53ec62de6e97a517abd5fdd4cbde3a942a34 (patch) | |
tree | 1f43a548b46bca8a5fb8fe0c31cef1883d49c5b6 /spec/support | |
parent | 1da20d9135b3ad9e75e65b028bffc921aaf8deb7 (diff) |
Add latest changes from gitlab-org/gitlab@16-5-stable-eev16.5.0-rc42
Diffstat (limited to 'spec/support')
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 |