diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 16:49:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 16:49:51 +0300 |
commit | 71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e (patch) | |
tree | 6a2d93ef3fb2d353bb7739e4b57e6541f51cdd71 /spec/support | |
parent | a7253423e3403b8c08f8a161e5937e1488f5f407 (diff) |
Add latest changes from gitlab-org/gitlab@15-9-stable-eev15.9.0-rc42
Diffstat (limited to 'spec/support')
108 files changed, 2477 insertions, 683 deletions
diff --git a/spec/support/database/prevent_cross_database_modification.rb b/spec/support/database/prevent_cross_database_modification.rb index 759e8316cc5..cd0cbe733d1 100644 --- a/spec/support/database/prevent_cross_database_modification.rb +++ b/spec/support/database/prevent_cross_database_modification.rb @@ -28,8 +28,6 @@ RSpec.configure do |config| config.after do |example_file| ::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.suppress_in_rspec = true - [::ApplicationRecord, ::Ci::ApplicationRecord].each do |base_class| - base_class.gitlab_transactions_stack.clear if base_class.respond_to?(:gitlab_transactions_stack) - end + ::ApplicationRecord.gitlab_transactions_stack.clear end end diff --git a/spec/support/database/prevent_cross_joins.rb b/spec/support/database/prevent_cross_joins.rb index 42c69a26788..8e08824c464 100644 --- a/spec/support/database/prevent_cross_joins.rb +++ b/spec/support/database/prevent_cross_joins.rb @@ -23,6 +23,7 @@ module Database ALLOW_THREAD_KEY = :allow_cross_joins_across_databases ALLOW_ANNOTATE_KEY = ALLOW_THREAD_KEY.to_s.freeze + IGNORED_SCHEMAS = %i[gitlab_shared gitlab_internal].freeze def self.validate_cross_joins!(sql) return if Thread.current[ALLOW_THREAD_KEY] || sql.include?(ALLOW_ANNOTATE_KEY) @@ -40,8 +41,9 @@ module Database end schemas = ::Gitlab::Database::GitlabSchema.table_schemas(tables) + schemas.subtract(IGNORED_SCHEMAS) - if schemas.include?(:gitlab_ci) && schemas.include?(:gitlab_main) + if schemas.many? 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 588fe466a42..b9a99eff413 100644 --- a/spec/support/db_cleaner.rb +++ b/spec/support/db_cleaner.rb @@ -12,7 +12,7 @@ module DbCleaner end def deletion_except_tables - %w[work_item_types work_item_hierarchy_restrictions] + %w[work_item_types work_item_hierarchy_restrictions work_item_widget_definitions] end def setup_database_cleaner diff --git a/spec/support/graphql/fake_tracer.rb b/spec/support/graphql/fake_tracer.rb index c2fb7ed12d8..58688c9abd0 100644 --- a/spec/support/graphql/fake_tracer.rb +++ b/spec/support/graphql/fake_tracer.rb @@ -6,8 +6,8 @@ module Graphql @trace_callback = trace_callback end - def trace(*args) - @trace_callback.call(*args) + def trace(...) + @trace_callback.call(...) yield end diff --git a/spec/support/graphql/resolver_factories.rb b/spec/support/graphql/resolver_factories.rb index 3c5aad34e8b..76df4b58943 100644 --- a/spec/support/graphql/resolver_factories.rb +++ b/spec/support/graphql/resolver_factories.rb @@ -27,8 +27,8 @@ module Graphql Class.new(Resolvers::BaseResolver) do include ::Gitlab::Graphql::Authorize::AuthorizeResource - def resolve(**args) - authorized_find!(**args) + def resolve(...) + authorized_find!(...) end define_method :find_object do |**_args| diff --git a/spec/support/graphql/subscriptions/action_cable/mock_action_cable.rb b/spec/support/graphql/subscriptions/action_cable/mock_action_cable.rb new file mode 100644 index 00000000000..5467564a79e --- /dev/null +++ b/spec/support/graphql/subscriptions/action_cable/mock_action_cable.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +# A stub implementation of ActionCable. +# Any methods to support the mock backend have `mock` in the name. +module Graphql + module Subscriptions + module ActionCable + class MockActionCable + class MockChannel + def initialize + @mock_broadcasted_messages = [] + end + + attr_reader :mock_broadcasted_messages + + def stream_from(stream_name, coder: nil, &block) + # Rails uses `coder`, we don't + block ||= ->(msg) { @mock_broadcasted_messages << msg } + MockActionCable.mock_stream_for(stream_name).add_mock_channel(self, block) + end + end + + class MockStream + def initialize + @mock_channels = {} + end + + def add_mock_channel(channel, handler) + @mock_channels[channel] = handler + end + + def mock_broadcast(message) + @mock_channels.each do |channel, handler| + handler && handler.call(message) + end + end + end + + class << self + def clear_mocks + @mock_streams = {} + end + + def server + self + end + + def broadcast(stream_name, message) + stream = @mock_streams[stream_name] + stream && stream.mock_broadcast(message) + end + + def mock_stream_for(stream_name) + @mock_streams[stream_name] ||= MockStream.new + end + + def get_mock_channel + MockChannel.new + end + + def mock_stream_names + @mock_streams.keys + end + end + end + + class MockSchema < GraphQL::Schema + class << self + def find_by_gid(gid) + return unless gid + + if gid.model_class < ApplicationRecord + Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find + elsif gid.model_class.respond_to?(:lazy_find) + gid.model_class.lazy_find(gid.model_id) + else + gid.find + end + end + + def id_from_object(object, _type = nil, _ctx = nil) + unless object.respond_to?(:to_global_id) + # This is an error in our schema and needs to be solved. So raise a + # more meaningful error message + raise "#{object} does not implement `to_global_id`. " \ + "Include `GlobalID::Identification` into `#{object.class}" + end + + object.to_global_id + end + end + + query(::Types::QueryType) + subscription(::Types::SubscriptionType) + + use GraphQL::Subscriptions::ActionCableSubscriptions, action_cable: MockActionCable, action_cable_coder: JSON + end + end + end +end diff --git a/spec/support/graphql/subscriptions/action_cable/mock_gitlab_schema.rb b/spec/support/graphql/subscriptions/action_cable/mock_gitlab_schema.rb new file mode 100644 index 00000000000..cd5d78cc78b --- /dev/null +++ b/spec/support/graphql/subscriptions/action_cable/mock_gitlab_schema.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# A stub implementation of ActionCable. +# Any methods to support the mock backend have `mock` in the name. +module Graphql + module Subscriptions + module ActionCable + class MockGitlabSchema < GraphQL::Schema + class << self + def find_by_gid(gid) + return unless gid + + if gid.model_class < ApplicationRecord + Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find + elsif gid.model_class.respond_to?(:lazy_find) + gid.model_class.lazy_find(gid.model_id) + else + gid.find + end + end + + def id_from_object(object, _type = nil, _ctx = nil) + unless object.respond_to?(:to_global_id) + # This is an error in our schema and needs to be solved. So raise a + # more meaningful error message + raise "#{object} does not implement `to_global_id`. " \ + "Include `GlobalID::Identification` into `#{object.class}" + end + + object.to_global_id + end + end + + query(::Types::QueryType) + subscription(::Types::SubscriptionType) + + use GraphQL::Subscriptions::ActionCableSubscriptions, action_cable: MockActionCable, action_cable_coder: JSON + end + end + end +end diff --git a/spec/support/graphql/subscriptions/notes/helper.rb b/spec/support/graphql/subscriptions/notes/helper.rb new file mode 100644 index 00000000000..9a552f9879e --- /dev/null +++ b/spec/support/graphql/subscriptions/notes/helper.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +module Graphql + module Subscriptions + module Notes + module Helper + def subscription_response + subscription_channel = subscribe + yield + subscription_channel.mock_broadcasted_messages.first + end + + def notes_subscription(name, noteable, current_user) + mock_channel = Graphql::Subscriptions::ActionCable::MockActionCable.get_mock_channel + + query = case name + when 'workItemNoteDeleted' + note_deleted_subscription_query(name, noteable) + when 'workItemNoteUpdated' + note_updated_subscription_query(name, noteable) + when 'workItemNoteCreated' + note_created_subscription_query(name, noteable) + else + raise "Subscription query unknown: #{name}" + end + + GitlabSchema.execute(query, context: { current_user: current_user, channel: mock_channel }) + + mock_channel + end + + def note_subscription(name, noteable, current_user) + mock_channel = Graphql::Subscriptions::ActionCable::MockActionCable.get_mock_channel + + query = <<~SUBSCRIPTION + subscription { + #{name}(noteableId: \"#{noteable.to_gid}\") { + id + body + } + } + SUBSCRIPTION + + GitlabSchema.execute(query, context: { current_user: current_user, channel: mock_channel }) + + mock_channel + end + + private + + def note_deleted_subscription_query(name, noteable) + <<~SUBSCRIPTION + subscription { + #{name}(noteableId: \"#{noteable.to_gid}\") { + id + discussionId + lastDiscussionNote + } + } + SUBSCRIPTION + end + + def note_created_subscription_query(name, noteable) + <<~SUBSCRIPTION + subscription { + #{name}(noteableId: \"#{noteable.to_gid}\") { + id + discussion { + id + notes { + nodes { + id + } + } + } + } + } + SUBSCRIPTION + end + + def note_updated_subscription_query(name, noteable) + <<~SUBSCRIPTION + subscription { + #{name}(noteableId: \"#{noteable.to_gid}\") { + id + body + } + } + SUBSCRIPTION + end + end + end + end +end diff --git a/spec/support/helpers/ci/job_token_scope_helpers.rb b/spec/support/helpers/ci/job_token_scope_helpers.rb new file mode 100644 index 00000000000..1d71356917e --- /dev/null +++ b/spec/support/helpers/ci/job_token_scope_helpers.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module Ci + module JobTokenScopeHelpers + def create_project_in_allowlist(root_project, direction:, target_project: nil) + included_project = target_project || create(:project, + ci_outbound_job_token_scope_enabled: true, + ci_inbound_job_token_scope_enabled: true + ) + create( + :ci_job_token_project_scope_link, + source_project: root_project, + target_project: included_project, + direction: direction + ) + + included_project + end + + def create_project_in_both_allowlists(root_project) + create_project_in_allowlist(root_project, direction: :outbound).tap do |new_project| + create_project_in_allowlist(root_project, target_project: new_project, direction: :inbound) + end + end + + def create_inbound_accessible_project(project) + create(:project).tap do |accessible_project| + add_inbound_accessible_linkage(project, accessible_project) + end + end + + def create_inbound_and_outbound_accessible_project(project) + create(:project).tap do |accessible_project| + make_project_fully_accessible(project, accessible_project) + end + end + + def make_project_fully_accessible(project, accessible_project) + add_outbound_accessible_linkage(project, accessible_project) + add_inbound_accessible_linkage(project, accessible_project) + end + + def add_outbound_accessible_linkage(project, accessible_project) + create( + :ci_job_token_project_scope_link, + source_project: project, + target_project: accessible_project, + direction: :outbound + ) + end + + def add_inbound_accessible_linkage(project, accessible_project) + create( + :ci_job_token_project_scope_link, + source_project: accessible_project, + target_project: project, + direction: :inbound + ) + end + end +end diff --git a/spec/support/helpers/ci/template_helpers.rb b/spec/support/helpers/ci/template_helpers.rb index 2cdd242ac22..cd3ab4bd82d 100644 --- a/spec/support/helpers/ci/template_helpers.rb +++ b/spec/support/helpers/ci/template_helpers.rb @@ -13,14 +13,21 @@ module Ci def public_image_manifest(registry, repository, reference) token = public_image_repository_token(registry, repository) + headers = { + 'Authorization' => "Bearer #{token}", + 'Accept' => 'application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json' + } response = with_net_connect_allowed do - Gitlab::HTTP.get(image_manifest_url(registry, repository, reference), - headers: { 'Authorization' => "Bearer #{token}" }) + Gitlab::HTTP.get(image_manifest_url(registry, repository, reference), headers: headers) end - return unless response.success? - - Gitlab::Json.parse(response.body) + if response.success? + Gitlab::Json.parse(response.body) + elsif response.not_found? + nil + else + raise "Could not retrieve manifest: #{response.body}" + end end def public_image_repository_token(registry, repository) @@ -31,17 +38,17 @@ module Ci Gitlab::HTTP.get(image_manifest_url(registry, repository, 'latest')) end - return unless response.unauthorized? + raise 'Unauthorized' unless response.unauthorized? www_authenticate = response.headers['www-authenticate'] - return unless www_authenticate + raise 'Missing www-authenticate' unless www_authenticate realm, service, scope = www_authenticate.split(',').map { |s| s[/\w+="(.*)"/, 1] } token_response = with_net_connect_allowed do Gitlab::HTTP.get(realm, query: { service: service, scope: scope }) end - return unless token_response.success? + raise "Could not get token: #{token_response.body}" unless token_response.success? token_response['token'] end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index 632f3ea28ee..eba5771e062 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -185,7 +185,7 @@ module CycleAnalyticsHelpers def merge_merge_requests_closing_issue(user, project, issue) merge_requests = Issues::ReferencedMergeRequestsService - .new(project: project, current_user: user) + .new(container: project, current_user: user) .closed_by_merge_requests(issue) merge_requests.each { |merge_request| MergeRequests::MergeService.new(project: project, current_user: user, params: { sha: merge_request.diff_head_sha }).execute(merge_request) } diff --git a/spec/support/helpers/database/multiple_databases_helpers.rb b/spec/support/helpers/database/multiple_databases_helpers.rb index 16f5168ca29..5083ea1ff53 100644 --- a/spec/support/helpers/database/multiple_databases_helpers.rb +++ b/spec/support/helpers/database/multiple_databases_helpers.rb @@ -2,12 +2,28 @@ module Database module MultipleDatabasesHelpers - def skip_if_multiple_databases_not_setup - skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci) + EXTRA_DBS = ::Gitlab::Database::DATABASE_NAMES.map(&:to_sym) - [:main] + + def skip_if_multiple_databases_not_setup(*databases) + unless (databases - EXTRA_DBS).empty? + raise "Unsupported database in #{databases}. It must be one of #{EXTRA_DBS}." + end + + databases = EXTRA_DBS if databases.empty? + return if databases.any? { |db| Gitlab::Database.has_config?(db) } + + skip "Skipping because none of the extra databases #{databases} are setup" end - def skip_if_multiple_databases_are_setup - skip 'Skipping because multiple databases are set up' if Gitlab::Database.has_config?(:ci) + def skip_if_multiple_databases_are_setup(*databases) + unless (databases - EXTRA_DBS).empty? + raise "Unsupported database in #{databases}. It must be one of #{EXTRA_DBS}." + end + + databases = EXTRA_DBS if databases.empty? + return if databases.none? { |db| Gitlab::Database.has_config?(db) } + + skip "Skipping because some of the extra databases #{databases} are setup" end def reconfigure_db_connection(name: nil, config_hash: {}, model: ActiveRecord::Base, config_model: nil) @@ -71,6 +87,14 @@ module Database end # rubocop:enable Database/MultipleDatabases + def with_db_configs(test: test_config) + current_configurations = ActiveRecord::Base.configurations # rubocop:disable Database/MultipleDatabases + ActiveRecord::Base.configurations = { test: test_config } + yield + ensure + ActiveRecord::Base.configurations = current_configurations + end + def with_added_ci_connection if Gitlab::Database.has_config?(:ci) # No need to add a ci: connection if we already have one diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb index d0f6fd466d0..f4bdaa7e425 100644 --- a/spec/support/helpers/email_helpers.rb +++ b/spec/support/helpers/email_helpers.rb @@ -56,4 +56,24 @@ module EmailHelpers have_subject [prefix, suffix].compact.join end + + def enqueue_mail_with(mailer_class, mail_method_name, *args) + args.map! { |arg| arg.is_a?(ActiveRecord::Base) ? arg.id : arg } + have_enqueued_mail(mailer_class, mail_method_name).with(*args) + end + + def not_enqueue_mail_with(mailer_class, mail_method_name, *args) + args.map! { |arg| arg.is_a?(ActiveRecord::Base) ? arg.id : arg } + not_enqueue_mail(mailer_class, mail_method_name).with(*args) + end + + def have_only_enqueued_mail_with_args(mailer_class, mailer_method, *args) + raise ArgumentError, 'You must provide at least one array of mailer arguments' if args.empty? + + count_expectation = have_enqueued_mail(mailer_class, mailer_method).exactly(args.size).times + + args.inject(count_expectation) do |composed_expectation, arguments| + composed_expectation.and(have_enqueued_mail(mailer_class, mailer_method).with(*arguments)) + end + end end diff --git a/spec/support/helpers/features/branches_helpers.rb b/spec/support/helpers/features/branches_helpers.rb index d4f96718cc0..dc4fa448167 100644 --- a/spec/support/helpers/features/branches_helpers.rb +++ b/spec/support/helpers/features/branches_helpers.rb @@ -22,15 +22,10 @@ module Spec end def select_branch(branch_name) - ref_selector = '.ref-selector' - find(ref_selector).click wait_for_requests - page.within(ref_selector) do - fill_in _('Search by Git revision'), with: branch_name - wait_for_requests - find('li', text: branch_name, match: :prefer_exact).click - end + click_button branch_name + send_keys branch_name end end end diff --git a/spec/support/helpers/features/releases_helpers.rb b/spec/support/helpers/features/releases_helpers.rb index a24b99bbe61..545e12341ef 100644 --- a/spec/support/helpers/features/releases_helpers.rb +++ b/spec/support/helpers/features/releases_helpers.rb @@ -15,17 +15,18 @@ module Spec module Helpers module Features module ReleasesHelpers + include ListboxHelpers + def select_new_tag_name(tag_name) page.within '[data-testid="tag-name-field"]' do find('button').click - wait_for_all_requests find('input[aria-label="Search or create tag"]').set(tag_name) - wait_for_all_requests click_button("Create tag #{tag_name}") + click_button tag_name end end @@ -39,7 +40,7 @@ module Spec wait_for_all_requests - click_button(branch_name.to_s) + select_listbox_item(branch_name.to_s, exact_text: true) end end diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb index 50b8083ebb3..504a9b764cf 100644 --- a/spec/support/helpers/features/sorting_helpers.rb +++ b/spec/support/helpers/features/sorting_helpers.rb @@ -26,8 +26,8 @@ module Spec # all of the dropdowns are converted, pajamas_sort_by can be renamed to sort_by # https://gitlab.com/groups/gitlab-org/-/epics/7551 def pajamas_sort_by(value) - find('.filter-dropdown-container .dropdown').click - find('.dropdown-item', text: value).click + find('.filter-dropdown-container .gl-new-dropdown').click + find('.gl-new-dropdown-item', text: value).click end end end diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb index 20c104cd85c..398a2a20f2f 100644 --- a/spec/support/helpers/gitaly_setup.rb +++ b/spec/support/helpers/gitaly_setup.rb @@ -65,6 +65,7 @@ module GitalySetup def env { 'GEM_PATH' => Gem.path.join(':'), + 'BUNDLER_SETUP' => nil, 'BUNDLE_INSTALL_FLAGS' => nil, 'BUNDLE_IGNORE_CONFIG' => '1', 'BUNDLE_PATH' => bundle_path, diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index 40eb46878ad..403456fa48e 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -24,7 +24,9 @@ module JavaScriptFixturesHelpers # pick an arbitrary date from the past, so tests are not time dependent # Also see spec/frontend/__helpers__/fake_date/jest.js - travel_to(Time.utc(2015, 7, 3, 10)) { example.run } + travel_to Time.utc(2015, 7, 3, 10) + example.run + travel_back raise NoMethodError.new('You need to set `response` for the fixture generator! This will automatically happen with `type: :controller` or `type: :request`.', 'response') unless respond_to?(:response) diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index 72524453f34..c3076a2c359 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -534,10 +534,10 @@ module KubernetesHelpers } end - def kube_knative_services_body(**options) + def kube_knative_services_body(...) { "kind" => "List", - "items" => [knative_09_service(**options)] + "items" => [knative_09_service(...)] } end diff --git a/spec/support/helpers/listbox_helpers.rb b/spec/support/helpers/listbox_helpers.rb index 5fcd05f31fb..e943790fc65 100644 --- a/spec/support/helpers/listbox_helpers.rb +++ b/spec/support/helpers/listbox_helpers.rb @@ -7,18 +7,18 @@ module ListboxHelpers end def select_listbox_item(text, exact_text: false) - find('.gl-listbox-item[role="option"]', text: text, exact_text: exact_text).click + find('.gl-new-dropdown-item[role="option"]', text: text, exact_text: exact_text).click end def expect_listbox_item(text) - expect(page).to have_css('.gl-listbox-item[role="option"]', text: text) + expect(page).to have_css('.gl-new-dropdown-item[role="option"]', text: text) end def expect_no_listbox_item(text) - expect(page).not_to have_css('.gl-listbox-item[role="option"]', text: text) + expect(page).not_to have_css('.gl-new-dropdown-item[role="option"]', text: text) end def expect_listbox_items(items) - expect(find_all('.gl-listbox-item[role="option"]').map(&:text)).to eq(items) + expect(find_all('.gl-new-dropdown-item[role="option"]').map(&:text)).to eq(items) end end diff --git a/spec/support/helpers/reload_helpers.rb b/spec/support/helpers/reload_helpers.rb index 368ebaaba8a..71becd535b0 100644 --- a/spec/support/helpers/reload_helpers.rb +++ b/spec/support/helpers/reload_helpers.rb @@ -5,8 +5,8 @@ module ReloadHelpers models.compact.map(&:reload) end - def subject_and_reload(*models) + def subject_and_reload(...) subject - reload_models(*models) + reload_models(...) end end diff --git a/spec/support/helpers/select2_helper.rb b/spec/support/helpers/select2_helper.rb deleted file mode 100644 index 38bf34bdd61..00000000000 --- a/spec/support/helpers/select2_helper.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -require_relative 'wait_for_requests' - -# Select2 ajax programmatic helper -# It allows you to select value from select2 -# -# Params -# value - real value of selected item -# opts - options containing css selector -# -# Usage: -# -# select2(2, from: '#user_ids') -# - -module Select2Helper - include WaitForRequests - - def select2(value, options = {}) - raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash) - - wait_for_requests unless options[:async] - - selector = options.fetch(:from) - - ensure_select2_loaded(selector) - - if options[:multiple] - execute_script("$('#{selector}').select2('val', ['#{value}']).trigger('change');") - else - execute_script("$('#{selector}').select2('val', '#{value}').trigger('change');") - end - end - - def open_select2(selector) - ensure_select2_loaded(selector) - - execute_script("$('#{selector}').select2('open');") - end - - def close_select2(selector) - ensure_select2_loaded(selector) - - execute_script("$('#{selector}').select2('close');") - end - - def scroll_select2_to_bottom(selector) - evaluate_script "$('#{selector}').scrollTop($('#{selector}')[0].scrollHeight); $('#{selector}');" - end - - private - - def ensure_select2_loaded(selector) - first(selector, visible: :all).sibling('.select2-container') - end -end diff --git a/spec/support/helpers/stub_env.rb b/spec/support/helpers/stub_env.rb index 5f344f8fb52..afa501d6279 100644 --- a/spec/support/helpers/stub_env.rb +++ b/spec/support/helpers/stub_env.rb @@ -2,13 +2,23 @@ # Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb module StubENV + # Stub ENV variables + # + # You can provide either a key and value as separate params or both in a Hash format + # + # Keys and values will always be converted to String, to comply with how ENV behaves + # + # @param key_or_hash [String, Hash<String,String>] + # @param value [String] def stub_env(key_or_hash, value = nil) init_stub unless env_stubbed? if key_or_hash.is_a? Hash - key_or_hash.each { |k, v| add_stubbed_value(k, v) } + key_or_hash.each do |key, value| + add_stubbed_value(key, ensure_env_type(value)) + end else - add_stubbed_value key_or_hash, value + add_stubbed_value key_or_hash, ensure_env_type(value) end end @@ -35,4 +45,8 @@ module StubENV allow(ENV).to receive(:fetch).and_call_original add_stubbed_value(STUBBED_KEY, true) end + + def ensure_env_type(value) + value.nil? ? value : value.to_s + end end diff --git a/spec/support/helpers/stubbed_member.rb b/spec/support/helpers/stubbed_member.rb index 27420c9b709..d61cdea5354 100644 --- a/spec/support/helpers/stubbed_member.rb +++ b/spec/support/helpers/stubbed_member.rb @@ -11,9 +11,7 @@ module StubbedMember module Member private - def refresh_member_authorized_projects(blocking:) - return super unless blocking - + def refresh_member_authorized_projects AuthorizedProjectsWorker.new.perform(user_id) end end @@ -21,7 +19,7 @@ module StubbedMember module ProjectMember private - def blocking_project_authorizations_refresh + def execute_project_authorizations_refresh AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker.new.perform(project.id, user.id) end end diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 3530d1b1a39..3403064bf0b 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -423,7 +423,7 @@ module TestEnv return if File.exist?(install_dir) && ci? if component_needs_update?(install_dir, version) - puts "==> Starting #{component} set up...\n" + puts "==> Starting #{component} (#{version}) set up...\n" # Cleanup the component entirely to ensure we start fresh FileUtils.rm_rf(install_dir) if fresh_install diff --git a/spec/support/helpers/trial_status_widget_test_helper.rb b/spec/support/helpers/trial_status_widget_test_helper.rb deleted file mode 100644 index d75620d17ee..00000000000 --- a/spec/support/helpers/trial_status_widget_test_helper.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module TrialStatusWidgetTestHelper - def purchase_href(group) - new_subscriptions_path(namespace_id: group.id, plan_id: 'ultimate-plan-id') - end -end - -TrialStatusWidgetTestHelper.prepend_mod diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index 438f0d129b9..2bec945fbc8 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -4,7 +4,6 @@ module UsageDataHelpers COUNTS_KEYS = %i( assignee_lists ci_builds - ci_internal_pipelines ci_external_pipelines ci_pipeline_config_auto_devops ci_pipeline_config_repository @@ -110,7 +109,6 @@ module UsageDataHelpers gitaly database prometheus_metrics_enabled - web_ide_clientside_preview_enabled object_store topology ).freeze diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb index 3d7a0d29e71..f8f32fa59d1 100644 --- a/spec/support/import_export/common_util.rb +++ b/spec/support/import_export/common_util.rb @@ -51,22 +51,22 @@ module ImportExport json end - def restore_then_save_project(project, import_path:, export_path:) - project_restorer = get_project_restorer(project, import_path) - project_saver = get_project_saver(project, export_path) + def restore_then_save_project(project, user, import_path:, export_path:) + project_restorer = get_project_restorer(project, user, import_path) + project_saver = get_project_saver(project, user, export_path) project_restorer.restore && project_saver.save end - def get_project_restorer(project, import_path) + def get_project_restorer(project, user, import_path) Gitlab::ImportExport::Project::TreeRestorer.new( - user: project.creator, shared: get_shared_env(path: import_path), project: project + user: user, shared: get_shared_env(path: import_path), project: project ) end - def get_project_saver(project, export_path) + def get_project_saver(project, user, export_path) Gitlab::ImportExport::Project::TreeSaver.new( - project: project, current_user: project.creator, shared: get_shared_env(path: export_path) + project: project, current_user: user, shared: get_shared_env(path: export_path) ) end diff --git a/spec/support/import_export/project_tree_expectations.rb b/spec/support/import_export/project_tree_expectations.rb index 2423a58a3e6..0049d0fbd06 100644 --- a/spec/support/import_export/project_tree_expectations.rb +++ b/spec/support/import_export/project_tree_expectations.rb @@ -59,7 +59,7 @@ module ImportExport end def match_arrays(left_node, right_node, stats, location_stack, failures) - has_simple_elements = left_node.none? { |el| Enumerable === el } + has_simple_elements = left_node.none?(Enumerable) # for simple types, we can do a direct order-less set comparison if has_simple_elements && left_node.to_set != right_node.to_set stats[:arrays][:direct] += 1 diff --git a/spec/support/matchers/not_enqueue_mail_matcher.rb b/spec/support/matchers/not_enqueue_mail_matcher.rb new file mode 100644 index 00000000000..0975c038252 --- /dev/null +++ b/spec/support/matchers/not_enqueue_mail_matcher.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +RSpec::Matchers.define_negated_matcher :not_enqueue_mail, :have_enqueued_mail diff --git a/spec/support/matchers/schema_matcher.rb b/spec/support/matchers/schema_matcher.rb index d2f32b60464..d5a07f200dd 100644 --- a/spec/support/matchers/schema_matcher.rb +++ b/spec/support/matchers/schema_matcher.rb @@ -16,20 +16,8 @@ module SchemaPath end def self.validator(schema_path) - unless @schema_cache.key?(schema_path) - @schema_cache[schema_path] = JSONSchemer.schema(schema_path, ref_resolver: SchemaPath.file_ref_resolver) - end - - @schema_cache[schema_path] - end - - def self.file_ref_resolver - proc do |uri| - file = Rails.root.join(uri.path) - raise StandardError, "Ref file #{uri.path} must be json" unless uri.path.ends_with?('.json') - raise StandardError, "File #{file.to_path} doesn't exists" unless file.exist? - - Gitlab::Json.parse(File.read(file)) + @schema_cache.fetch(schema_path) do + @schema_cache[schema_path] = JSONSchemer.schema(schema_path) end end end diff --git a/spec/support/models/ci/partitioning_testing/schema_helpers.rb b/spec/support/models/ci/partitioning_testing/schema_helpers.rb index 3a79ed1b5a9..4107bbcb976 100644 --- a/spec/support/models/ci/partitioning_testing/schema_helpers.rb +++ b/spec/support/models/ci/partitioning_testing/schema_helpers.rb @@ -24,13 +24,13 @@ module Ci each_partitionable_table do |table_name| change_column_default(table_name, from: DEFAULT_PARTITION, to: nil, connection: connection) change_column_default("p_#{table_name}", from: DEFAULT_PARTITION, to: nil, connection: connection) - create_test_partition(table_name, connection: connection) + create_test_partition("p_#{table_name}", connection: connection) end end def teardown(connection: Ci::ApplicationRecord.connection) each_partitionable_table do |table_name| - drop_test_partition(table_name, connection: connection) + drop_test_partition("p_#{table_name}", connection: connection) change_column_default(table_name, from: nil, to: DEFAULT_PARTITION, connection: connection) change_column_default("p_#{table_name}", from: nil, to: DEFAULT_PARTITION, connection: connection) end @@ -54,13 +54,13 @@ module Ci end def create_test_partition(table_name, connection:) - return unless table_available?("p_#{table_name}", connection: connection) + return unless table_available?(table_name, connection: connection) drop_test_partition(table_name, connection: connection) - connection.execute(<<~SQL) + connection.execute(<<~SQL.squish) CREATE TABLE #{full_partition_name(table_name)} - PARTITION OF p_#{table_name} + PARTITION OF #{table_name} FOR VALUES IN (#{PartitioningTesting::PartitionIdentifiers.ci_testing_partition_id}); SQL end @@ -68,7 +68,7 @@ module Ci def drop_test_partition(table_name, connection:) return unless table_available?(table_name, connection: connection) - connection.execute(<<~SQL) + connection.execute(<<~SQL.squish) DROP TABLE IF EXISTS #{full_partition_name(table_name)}; SQL end @@ -79,7 +79,12 @@ module Ci end def full_partition_name(table_name) - "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}._test_gitlab_#{table_name}_partition" + [ + Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA, + '._test_gitlab_', + table_name.delete_prefix('p_'), + '_partition' + ].join('') end end end diff --git a/spec/support/redis.rb b/spec/support/redis.rb index 6d313c8aa16..d5ae0bf1582 100644 --- a/spec/support/redis.rb +++ b/spec/support/redis.rb @@ -25,4 +25,10 @@ RSpec.configure do |config| instance_class.with(&:flushdb) end end + + config.before(:each, :use_null_store_as_repository_cache) do |example| + null_store = ActiveSupport::Cache::NullStore.new + + allow(Gitlab::Redis::RepositoryCache).to receive(:cache_store).and_return(null_store) + end end diff --git a/spec/support/redis/redis_new_instance_shared_examples.rb b/spec/support/redis/redis_new_instance_shared_examples.rb index 0f2de78b2cb..435d342fcca 100644 --- a/spec/support/redis/redis_new_instance_shared_examples.rb +++ b/spec/support/redis/redis_new_instance_shared_examples.rb @@ -27,38 +27,34 @@ RSpec.shared_examples "redis_new_instance_shared_examples" do |name, fallback_cl FileUtils.mkdir_p(File.join(rails_root, 'config')) end - context 'when there is only a resque.yml' do + context 'and there is a global env override' do before do - FileUtils.touch(File.join(rails_root, 'config/resque.yml')) + stub_env('GITLAB_REDIS_CONFIG_FILE', 'global override') end - it { expect(subject).to eq("#{rails_root}/config/resque.yml") } + it { expect(subject).to eq('global override') } - context 'and there is a global env override' do - before do - stub_env('GITLAB_REDIS_CONFIG_FILE', 'global override') - end - - it { expect(subject).to eq('global override') } + context "and #{fallback_class.name.demodulize} has a different config file" do + let(:fallback_config_file) { 'fallback config file' } - context "and #{fallback_class.name.demodulize} has a different config file" do - let(:fallback_config_file) { 'fallback config file' } - - it { expect(subject).to eq('fallback config file') } - end + it { expect(subject).to eq('fallback config file') } end end end describe '#fetch_config' do - context 'when redis.yml exists' do - subject { described_class.new('test').send(:fetch_config) } + subject { described_class.new('test').send(:fetch_config) } + + before do + FileUtils.mkdir_p(File.join(rails_root, 'config')) + + allow(described_class).to receive(:rails_root).and_return(rails_root) + end + context 'when redis.yml exists' do before do allow(described_class).to receive(:config_file_name).and_call_original allow(described_class).to receive(:redis_yml_path).and_call_original - allow(described_class).to receive(:rails_root).and_return(rails_root) - FileUtils.mkdir_p(File.join(rails_root, 'config')) end context 'when the fallback has a redis.yml entry' do @@ -93,5 +89,23 @@ RSpec.shared_examples "redis_new_instance_shared_examples" do |name, fallback_cl end end end + + context 'when no redis config file exsits' do + it 'returns nil' do + expect(subject).to eq(nil) + end + + context 'when resque.yml exists' do + before do + File.write(File.join(rails_root, 'config/resque.yml'), { + 'test' => { 'foobar' => 123 } + }.to_json) + end + + it 'returns the config from resque.yml' do + expect(subject).to eq({ 'foobar' => 123 }) + end + end + end end end diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb index 43c118a362d..8c195a9dbeb 100644 --- a/spec/support/redis/redis_shared_examples.rb +++ b/spec/support/redis/redis_shared_examples.rb @@ -40,42 +40,30 @@ RSpec.shared_examples "redis_shared_examples" do context 'when there is no config file anywhere' do it { expect(subject).to be_nil } - context 'but resque.yml exists' do + context 'and there is a global env override' do before do - FileUtils.touch(File.join(rails_root, 'config', 'resque.yml')) + stub_env('GITLAB_REDIS_CONFIG_FILE', 'global override') end - it { expect(subject).to eq("#{rails_root}/config/resque.yml") } - - it 'returns a path that exists' do - expect(File.file?(subject)).to eq(true) - end + it { expect(subject).to eq('global override') } - context 'and there is a global env override' do + context 'and there is an instance specific config file' do before do - stub_env('GITLAB_REDIS_CONFIG_FILE', 'global override') + FileUtils.touch(File.join(rails_root, instance_specific_config_file)) end - it { expect(subject).to eq('global override') } - - context 'and there is an instance specific config file' do - before do - FileUtils.touch(File.join(rails_root, instance_specific_config_file)) - end + it { expect(subject).to eq("#{rails_root}/#{instance_specific_config_file}") } - it { expect(subject).to eq("#{rails_root}/#{instance_specific_config_file}") } + it 'returns a path that exists' do + expect(File.file?(subject)).to eq(true) + end - it 'returns a path that exists' do - expect(File.file?(subject)).to eq(true) + context 'and there is a specific env override' do + before do + stub_env(environment_config_file_name, 'instance specific override') end - context 'and there is a specific env override' do - before do - stub_env(environment_config_file_name, 'instance specific override') - end - - it { expect(subject).to eq('instance specific override') } - end + it { expect(subject).to eq('instance specific override') } end end end @@ -402,6 +390,13 @@ RSpec.shared_examples "redis_shared_examples" do end describe '#fetch_config' do + before do + FileUtils.mkdir_p(File.join(rails_root, 'config')) + # Undo top-level stub of config_file_name because we are testing that method now. + allow(described_class).to receive(:config_file_name).and_call_original + allow(described_class).to receive(:rails_root).and_return(rails_root) + end + it 'raises an exception when the config file contains invalid yaml' do Tempfile.open('bad.yml') do |file| file.write('{"not":"yaml"') @@ -422,10 +417,7 @@ RSpec.shared_examples "redis_shared_examples" do subject { described_class.new('test').send(:fetch_config) } before do - allow(described_class).to receive(:config_file_name).and_call_original allow(described_class).to receive(:redis_yml_path).and_call_original - allow(described_class).to receive(:rails_root).and_return(rails_root) - FileUtils.mkdir_p(File.join(rails_root, 'config')) end it 'uses config/redis.yml' do @@ -436,6 +428,27 @@ RSpec.shared_examples "redis_shared_examples" do expect(subject).to eq({ 'foobar' => 123 }) end end + + context 'when no config file exsits' do + subject { described_class.new('test').send(:fetch_config) } + + it 'returns nil' do + expect(subject).to eq(nil) + end + + context 'but resque.yml exists' do + before do + FileUtils.mkdir_p(File.join(rails_root, 'config')) + File.write(File.join(rails_root, 'config/resque.yml'), { + 'test' => { 'foobar' => 123 } + }.to_json) + end + + it 'returns the config from resque.yml' do + expect(subject).to eq({ 'foobar' => 123 }) + end + end + end end def clear_pool diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 2f3f0feb87e..7aa7d8e8abd 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -165,7 +165,6 @@ - './ee/spec/controllers/projects/vulnerability_feedback_controller_spec.rb' - './ee/spec/controllers/registrations/company_controller_spec.rb' - './ee/spec/controllers/registrations/groups_projects_controller_spec.rb' -- './ee/spec/controllers/registrations/verification_controller_spec.rb' - './ee/spec/controllers/repositories/git_http_controller_spec.rb' - './ee/spec/controllers/security/dashboard_controller_spec.rb' - './ee/spec/controllers/security/projects_controller_spec.rb' @@ -341,7 +340,6 @@ - './ee/spec/features/issues/form_spec.rb' - './ee/spec/features/issues/gfm_autocomplete_ee_spec.rb' - './ee/spec/features/issues/issue_actions_spec.rb' -- './ee/spec/features/issues/issue_sidebar_spec.rb' - './ee/spec/features/issues/move_issue_resource_weight_events_spec.rb' - './ee/spec/features/issues/related_issues_spec.rb' - './ee/spec/features/issues/resource_weight_events_spec.rb' @@ -428,7 +426,6 @@ - './ee/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb' - './ee/spec/features/projects/milestones/milestone_spec.rb' - './ee/spec/features/projects/mirror_spec.rb' -- './ee/spec/features/projects/navbar_spec.rb' - './ee/spec/features/projects/new_project_from_template_spec.rb' - './ee/spec/features/projects/new_project_spec.rb' - './ee/spec/features/projects/path_locks_spec.rb' @@ -966,11 +963,9 @@ - './ee/spec/helpers/ee/groups/settings_helper_spec.rb' - './ee/spec/helpers/ee/hooks_helper_spec.rb' - './ee/spec/helpers/ee/integrations_helper_spec.rb' -- './ee/spec/helpers/ee/invite_members_helper_spec.rb' - './ee/spec/helpers/ee/issuables_helper_spec.rb' - './ee/spec/helpers/ee/issues_helper_spec.rb' - './ee/spec/helpers/ee/labels_helper_spec.rb' -- './ee/spec/helpers/ee/learn_gitlab_helper_spec.rb' - './ee/spec/helpers/ee/lock_helper_spec.rb' - './ee/spec/helpers/ee/namespaces_helper_spec.rb' - './ee/spec/helpers/ee/namespace_user_cap_reached_alert_helper_spec.rb' @@ -1006,10 +1001,7 @@ - './ee/spec/helpers/manual_quarterly_co_term_banner_helper_spec.rb' - './ee/spec/helpers/markup_helper_spec.rb' - './ee/spec/helpers/merge_requests_helper_spec.rb' -- './ee/spec/helpers/nav/new_dropdown_helper_spec.rb' -- './ee/spec/helpers/nav/top_nav_helper_spec.rb' - './ee/spec/helpers/notes_helper_spec.rb' -- './ee/spec/helpers/paid_feature_callout_helper_spec.rb' - './ee/spec/helpers/path_locks_helper_spec.rb' - './ee/spec/helpers/preferences_helper_spec.rb' - './ee/spec/helpers/prevent_forking_helper_spec.rb' @@ -1640,7 +1632,6 @@ - './ee/spec/models/allowed_email_domain_spec.rb' - './ee/spec/models/analytics/cycle_analytics/aggregation_context_spec.rb' - './ee/spec/models/analytics/cycle_analytics/group_level_spec.rb' -- './ee/spec/models/analytics/cycle_analytics/project_stage_spec.rb' - './ee/spec/models/analytics/cycle_analytics/runtime_limiter_spec.rb' - './ee/spec/models/analytics/devops_adoption/enabled_namespace_spec.rb' - './ee/spec/models/analytics/devops_adoption/snapshot_spec.rb' @@ -1674,7 +1665,6 @@ - './ee/spec/models/broadcast_message_spec.rb' - './ee/spec/models/burndown_spec.rb' - './ee/spec/models/ci/bridge_spec.rb' -- './ee/spec/models/ci/build_spec.rb' - './ee/spec/models/ci/daily_build_group_report_result_spec.rb' - './ee/spec/models/ci/minutes/additional_pack_spec.rb' - './ee/spec/models/ci/minutes/context_spec.rb' @@ -1782,6 +1772,7 @@ - './ee/spec/models/ee/pages_deployment_spec.rb' - './ee/spec/models/ee/personal_access_token_spec.rb' - './ee/spec/models/ee/preloaders/group_policy_preloader_spec.rb' +- './ee/spec/models/ee/project_spec.rb' - './ee/spec/models/ee/project_authorization_spec.rb' - './ee/spec/models/ee/project_group_link_spec.rb' - './ee/spec/models/ee/project_setting_spec.rb' @@ -1905,7 +1896,6 @@ - './ee/spec/models/project_member_spec.rb' - './ee/spec/models/project_repository_state_spec.rb' - './ee/spec/models/project_security_setting_spec.rb' -- './ee/spec/models/project_spec.rb' - './ee/spec/models/project_team_spec.rb' - './ee/spec/models/protected_branch/required_code_owners_section_spec.rb' - './ee/spec/models/protected_branch/unprotect_access_level_spec.rb' @@ -3117,7 +3107,6 @@ - './ee/spec/views/groups/compliance_frameworks/new.html.haml_spec.rb' - './ee/spec/views/groups/edit.html.haml_spec.rb' - './ee/spec/views/groups/feature_discovery_moments/advanced_features_dashboard.html.haml_spec.rb' -- './ee/spec/views/groups/group_members/index.html.haml_spec.rb' - './ee/spec/views/groups/hook_logs/show.html.haml_spec.rb' - './ee/spec/views/groups/hooks/edit.html.haml_spec.rb' - './ee/spec/views/groups/security/discover/show.html.haml_spec.rb' @@ -3128,7 +3117,6 @@ - './ee/spec/views/layouts/header/_current_user_dropdown.html.haml_spec.rb' - './ee/spec/views/layouts/header/_ee_subscribable_banner.html.haml_spec.rb' - './ee/spec/views/layouts/header/help_dropdown/_cross_stage_fdm.html.haml_spec.rb' -- './ee/spec/views/layouts/header/_new_dropdown.haml_spec.rb' - './ee/spec/views/layouts/header/_read_only_banner.html.haml_spec.rb' - './ee/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb' - './ee/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb' @@ -3142,7 +3130,6 @@ - './ee/spec/views/projects/issues/show.html.haml_spec.rb' - './ee/spec/views/projects/_merge_request_status_checks_settings.html.haml_spec.rb' - './ee/spec/views/projects/on_demand_scans/index.html.haml_spec.rb' -- './ee/spec/views/projects/project_members/index.html.haml_spec.rb' - './ee/spec/views/projects/security/corpus_management/show.html.haml_spec.rb' - './ee/spec/views/projects/security/dast_profiles/show.html.haml_spec.rb' - './ee/spec/views/projects/security/dast_scanner_profiles/edit.html.haml_spec.rb' @@ -3154,7 +3141,6 @@ - './ee/spec/views/projects/security/sast_configuration/show.html.haml_spec.rb' - './ee/spec/views/projects/settings/subscriptions/_index.html.haml_spec.rb' - './ee/spec/views/registrations/groups_projects/new.html.haml_spec.rb' -- './ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb' - './ee/spec/views/search/_category.html.haml_spec.rb' - './ee/spec/views/shared/billings/_billing_plan_actions.html.haml_spec.rb' - './ee/spec/views/shared/billings/_billing_plan.html.haml_spec.rb' @@ -3549,7 +3535,6 @@ - './spec/controllers/projects/issues_controller_spec.rb' - './spec/controllers/projects/jobs_controller_spec.rb' - './spec/controllers/projects/labels_controller_spec.rb' -- './spec/controllers/projects/learn_gitlab_controller_spec.rb' - './spec/controllers/projects/mattermosts_controller_spec.rb' - './spec/controllers/projects/merge_requests/conflicts_controller_spec.rb' - './spec/controllers/projects/merge_requests/content_controller_spec.rb' @@ -3635,7 +3620,6 @@ - './spec/experiments/ios_specific_templates_experiment_spec.rb' - './spec/experiments/require_verification_for_namespace_creation_experiment_spec.rb' - './spec/experiments/security_reports_mr_widget_prompt_experiment_spec.rb' -- './spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb' - './spec/features/abuse_report_spec.rb' - './spec/features/action_cable_logging_spec.rb' - './spec/features/admin/admin_abuse_reports_spec.rb' @@ -5162,7 +5146,6 @@ - './spec/helpers/import_helper_spec.rb' - './spec/helpers/instance_configuration_helper_spec.rb' - './spec/helpers/integrations_helper_spec.rb' -- './spec/helpers/invite_members_helper_spec.rb' - './spec/helpers/issuables_description_templates_helper_spec.rb' - './spec/helpers/issuables_helper_spec.rb' - './spec/helpers/issues_helper_spec.rb' @@ -5237,9 +5220,7 @@ - './spec/helpers/wiki_helper_spec.rb' - './spec/helpers/wiki_page_version_helper_spec.rb' - './spec/helpers/x509_helper_spec.rb' -- './spec/initializers/00_deprecations_spec.rb' - './spec/initializers/00_rails_disable_joins_spec.rb' -- './spec/initializers/0_log_deprecations_spec.rb' - './spec/initializers/0_postgresql_types_spec.rb' - './spec/initializers/100_patch_omniauth_oauth2_spec.rb' - './spec/initializers/100_patch_omniauth_saml_spec.rb' @@ -6130,7 +6111,6 @@ - './spec/lib/gitlab/ci/status/success_warning_spec.rb' - './spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb' - './spec/lib/gitlab/ci/tags/bulk_insert_spec.rb' -- './spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb' - './spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb' - './spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb' - './spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb' @@ -7476,7 +7456,6 @@ - './spec/lib/sidebars/menu_spec.rb' - './spec/lib/sidebars/panel_spec.rb' - './spec/lib/sidebars/projects/context_spec.rb' -- './spec/lib/sidebars/projects/menus/analytics_menu_spec.rb' - './spec/lib/sidebars/projects/menus/ci_cd_menu_spec.rb' - './spec/lib/sidebars/projects/menus/confluence_menu_spec.rb' - './spec/lib/sidebars/projects/menus/deployments_menu_spec.rb' @@ -7739,7 +7718,6 @@ - './spec/models/analytics/cycle_analytics/aggregation_spec.rb' - './spec/models/analytics/cycle_analytics/issue_stage_event_spec.rb' - './spec/models/analytics/cycle_analytics/merge_request_stage_event_spec.rb' -- './spec/models/analytics/cycle_analytics/project_stage_spec.rb' - './spec/models/analytics/cycle_analytics/project_value_stream_spec.rb' - './spec/models/analytics/cycle_analytics/stage_event_hash_spec.rb' - './spec/models/analytics/usage_trends/measurement_spec.rb' @@ -10309,7 +10287,6 @@ - './spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb' - './spec/views/projects/pipelines/show.html.haml_spec.rb' - './spec/views/projects/project_members/index.html.haml_spec.rb' -- './spec/views/projects/runners/_specific_runners.html.haml_spec.rb' - './spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb' - './spec/views/projects/settings/integrations/edit.html.haml_spec.rb' - './spec/views/projects/settings/operations/show.html.haml_spec.rb' diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb index b85c3904127..feea21be428 100644 --- a/spec/support/services/issuable_update_service_shared_examples.rb +++ b/spec/support/services/issuable_update_service_shared_examples.rb @@ -26,7 +26,7 @@ RSpec.shared_examples 'issuable update service' do expect(project).to receive(:execute_hooks).with(expected_payload, hook_event) expect(project).to receive(:execute_integrations).with(expected_payload, hook_event) - described_class.new(project: project, current_user: user, params: { state_event: 'reopen' }).execute(closed_issuable) + described_class.new(**described_class.constructor_container_arg(project), current_user: user, params: { state_event: 'reopen' }).execute(closed_issuable) end end @@ -48,7 +48,7 @@ RSpec.shared_examples 'issuable update service' do expect(project).to receive(:execute_hooks).with(expected_payload, hook_event) expect(project).to receive(:execute_integrations).with(expected_payload, hook_event) - described_class.new(project: project, current_user: user, params: { state_event: 'close' }).execute(open_issuable) + described_class.new(**described_class.constructor_container_arg(project), current_user: user, params: { state_event: 'close' }).execute(open_issuable) end end end diff --git a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb new file mode 100644 index 00000000000..1585ef0e7fc --- /dev/null +++ b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +RSpec.shared_context 'with FOSS query type fields' do + # extracted these fields into a shared variable so that we can define FOSS fields once and use them on EE spec as well + let(:expected_foss_fields) do + [ + :board_list, + :ci_application_settings, + :ci_config, + :ci_variables, + :container_repository, + :current_user, + :design_management, + :echo, + :gitpod_enabled, + :group, + :groups, + :issue, + :issues, + :jobs, + :merge_request, + :metadata, + :milestone, + :namespace, + :note, + :package, + :project, + :projects, + :query_complexity, + :runner, + :runner_platforms, + :runner_setup, + :runners, + :snippets, + :synthetic_note, + :timelogs, + :todo, + :topics, + :usage_trends_measurements, + :user, + :users, + :work_item + ] + end +end diff --git a/spec/support/shared_contexts/mailers/emails/service_desk_shared_context.rb b/spec/support/shared_contexts/mailers/emails/service_desk_shared_context.rb new file mode 100644 index 00000000000..4aa4d500f5c --- /dev/null +++ b/spec/support/shared_contexts/mailers/emails/service_desk_shared_context.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +RSpec.shared_context 'with service desk mailer' do + before do + stub_const('ServiceEmailClass', Class.new(ApplicationMailer)) + + ServiceEmailClass.class_eval do + include GitlabRoutingHelper + include EmailsHelper + include Emails::ServiceDesk + + helper GitlabRoutingHelper + helper EmailsHelper + + # this method is implemented in Notify class, we don't need to test it + def reply_key + 'b7721fc7e8419911a8bea145236a0519' + end + + # this method is implemented in Notify class, we don't need to test it + def sender(author_id, params = {}) + author_id + end + + # this method is implemented in Notify class + # + # We do not need to test the Notify method, it is already tested in notify_spec + def mail_new_thread(issue, options) + # we need to rewrite this in order to look up templates in the correct directory + self.class.mailer_name = 'notify' + + # this is needed for default layout + @unsubscribe_url = 'http://unsubscribe.example.com' + + mail(options) + end + alias_method :mail_answer_thread, :mail_new_thread + end + end +end diff --git a/spec/support/shared_contexts/models/ci/job_token_scope.rb b/spec/support/shared_contexts/models/ci/job_token_scope.rb index 51f671b139d..d0fee23b57c 100644 --- a/spec/support/shared_contexts/models/ci/job_token_scope.rb +++ b/spec/support/shared_contexts/models/ci/job_token_scope.rb @@ -1,21 +1,27 @@ # frozen_string_literal: true -RSpec.shared_context 'with scoped projects' do - let_it_be(:inbound_scoped_project) { create_scoped_project(source_project, direction: :inbound) } - let_it_be(:outbound_scoped_project) { create_scoped_project(source_project, direction: :outbound) } +RSpec.shared_context 'with a project in each allowlist' do + let_it_be(:outbound_allowlist_project) { create_project_in_allowlist(source_project, direction: :outbound) } + + include_context 'with inaccessible projects' +end + +RSpec.shared_context 'with accessible and inaccessible projects' do + let_it_be(:outbound_allowlist_project) { create_project_in_allowlist(source_project, direction: :outbound) } + let_it_be(:inbound_accessible_project) { create_inbound_accessible_project(source_project) } + let_it_be(:fully_accessible_project) { create_inbound_and_outbound_accessible_project(source_project) } + + include_context 'with inaccessible projects' +end + +RSpec.shared_context 'with inaccessible projects' do + let_it_be(:inbound_allowlist_project) { create_project_in_allowlist(source_project, direction: :inbound) } + include_context 'with unscoped projects' +end + +RSpec.shared_context 'with unscoped projects' do let_it_be(:unscoped_project1) { create(:project) } let_it_be(:unscoped_project2) { create(:project) } let_it_be(:link_out_of_scope) { create(:ci_job_token_project_scope_link, target_project: unscoped_project1) } - - def create_scoped_project(source_project, direction:) - create(:project).tap do |scoped_project| - create( - :ci_job_token_project_scope_link, - source_project: source_project, - target_project: scoped_project, - direction: direction - ) - end - end end diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb index 9c7cf831241..b74819d2ac7 100644 --- a/spec/support/shared_contexts/navbar_structure_context.rb +++ b/spec/support/shared_contexts/navbar_structure_context.rb @@ -85,7 +85,8 @@ RSpec.shared_context 'project navbar structure' do _('Metrics'), _('Error Tracking'), _('Alerts'), - _('Incidents') + _('Incidents'), + _('Airflow') ] }, { @@ -243,7 +244,10 @@ RSpec.shared_context 'dashboard navbar structure' do }, { nav_item: _("Merge requests"), - nav_sub_items: [] + nav_sub_items: [ + _('Assigned 0'), + _('Review requests 0') + ] }, { nav_item: _("To-Do List"), 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 fddcecbe125..4c081c8464e 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -36,6 +36,7 @@ RSpec.shared_context 'GroupPolicy context' do read_prometheus read_crm_contact read_crm_organization + read_internal_note ] end @@ -81,7 +82,7 @@ RSpec.shared_context 'GroupPolicy context' do ] end - let(:admin_permissions) { %i[read_confidential_issues] } + let(:admin_permissions) { %i[read_confidential_issues read_internal_note] } before_all do group.add_guest(guest) diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb index 6e2caa853f8..afc7fc8766f 100644 --- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb @@ -38,7 +38,8 @@ RSpec.shared_context 'ProjectPolicy context' do read_commit_status read_confidential_issues read_container_image read_harbor_registry read_deployment read_environment read_merge_request read_metrics_dashboard_annotation read_pipeline read_prometheus - read_sentry_issue update_issue create_merge_request_in + read_sentry_issue update_issue create_merge_request_in read_external_emails + read_internal_note ] end @@ -89,6 +90,13 @@ RSpec.shared_context 'ProjectPolicy context' do ] end + let(:admin_permissions) do + %i[ + read_project_for_iids update_max_artifacts_size read_storage_disk_path + owner_access admin_remote_mirror read_internal_note + ] + end + # Used in EE specs let(:additional_guest_permissions) { [] } let(:additional_reporter_permissions) { [] } diff --git a/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb index 7c37e5189f1..f6e10543c84 100644 --- a/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb +++ b/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb @@ -11,7 +11,7 @@ RSpec.shared_context 'conan api setup' do let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let(:project) { package.project } - let(:job) { create(:ci_build, :running, user: user) } + let(:job) { create(:ci_build, :running, user: user, project: project) } let(:job_token) { job.token } let(:auth_token) { personal_access_token.token } let(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } diff --git a/spec/support/shared_contexts/requests/api/debian_repository_shared_context.rb b/spec/support/shared_contexts/requests/api/debian_repository_shared_context.rb index cf090c7a185..57967fb9414 100644 --- a/spec/support/shared_contexts/requests/api/debian_repository_shared_context.rb +++ b/spec/support/shared_contexts/requests/api/debian_repository_shared_context.rb @@ -75,7 +75,8 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_ end end - let(:api_params) { workhorse_params } + let(:extra_params) { {} } + let(:api_params) { workhorse_params.merge(extra_params) } let(:auth_headers) { {} } let(:wh_headers) do diff --git a/spec/support/shared_contexts/requests/api/graphql/releases_and_group_releases_shared_context.rb b/spec/support/shared_contexts/requests/api/graphql/releases_and_group_releases_shared_context.rb new file mode 100644 index 00000000000..81076ea6fdc --- /dev/null +++ b/spec/support/shared_contexts/requests/api/graphql/releases_and_group_releases_shared_context.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +RSpec.shared_context 'when releases and group releases shared context' do + let_it_be(:stranger) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:developer) { create(:user) } + + let(:base_url_params) { { scope: 'all', release_tag: release.tag } } + let(:opened_url_params) { { state: 'opened', **base_url_params } } + let(:merged_url_params) { { state: 'merged', **base_url_params } } + let(:closed_url_params) { { state: 'closed', **base_url_params } } + + let(:query) do + graphql_query_for(resource_type, { fullPath: resource.full_path }, + %( + releases { + count + nodes { + tagName + tagPath + name + commit { + sha + } + assets { + count + sources { + nodes { + url + } + } + } + evidences { + nodes { + sha + } + } + links { + selfUrl + openedMergeRequestsUrl + mergedMergeRequestsUrl + closedMergeRequestsUrl + openedIssuesUrl + closedIssuesUrl + } + } + } + )) + end + + let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } } + let(:post_query) { post_graphql(query, current_user: current_user) } + + let(:data) { graphql_data.dig(resource_type.to_s, 'releases', 'nodes', 0) } + + before do + stub_default_url_options(host: 'www.example.com') + end +end diff --git a/spec/support/shared_examples/bulk_imports/visibility_level_examples.rb b/spec/support/shared_examples/bulk_imports/visibility_level_examples.rb new file mode 100644 index 00000000000..40e9726f89c --- /dev/null +++ b/spec/support/shared_examples/bulk_imports/visibility_level_examples.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'visibility level settings' do + context 'when public' do + let(:data) { { 'visibility' => 'public' } } + + context 'when destination is a public group' do + let(:destination_group) { create(:group, :public) } + + it 'sets visibility level to public' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + end + + context 'when destination is a internal group' do + let(:destination_group) { create(:group, :internal) } + + it 'sets visibility level to internal' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + end + + context 'when destination is a private group' do + let(:destination_group) { create(:group, :private) } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when destination is blank' do + let(:destination_namespace) { '' } + + it 'sets visibility level to public' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + end + end + + context 'when internal' do + let(:data) { { 'visibility' => 'internal' } } + + context 'when destination is a public group' do + let(:destination_group) { create(:group, :public) } + + it 'sets visibility level to internal' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + end + + context 'when destination is a internal group' do + let(:destination_group) { create(:group, :internal) } + + it 'sets visibility level to internal' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + end + + context 'when destination is a private group' do + let(:destination_group) { create(:group, :private) } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when destination is blank' do + let(:destination_namespace) { '' } + + it 'sets visibility level to internal' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + + context 'when visibility level is restricted' do + it 'sets visibility level to private' do + stub_application_setting( + restricted_visibility_levels: [ + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC + ] + ) + + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + end + end + + context 'when private' do + let(:data) { { 'visibility' => 'private' } } + + context 'when destination is a public group' do + let(:destination_group) { create(:group, :public) } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when destination is a internal group' do + let(:destination_group) { create(:group, :internal) } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when destination is a private group' do + let(:destination_group) { create(:group, :private) } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when destination is blank' do + let(:destination_namespace) { '' } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + end +end diff --git a/spec/support/shared_examples/controllers/issuable_anonymous_search_disabled_examples.rb b/spec/support/shared_examples/controllers/issuable_anonymous_search_disabled_examples.rb deleted file mode 100644 index e77acb93798..00000000000 --- a/spec/support/shared_examples/controllers/issuable_anonymous_search_disabled_examples.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'issuable list with anonymous search disabled' do |action| - let(:controller_action) { :index } - let(:params_with_search) { params.merge(search: 'some search term') } - - context 'when disable_anonymous_search is enabled' do - before do - stub_feature_flags(disable_anonymous_search: true) - end - - it 'shows a flash message' do - get controller_action, params: params_with_search - - expect(flash.now[:notice]).to eq('You must sign in to search for specific terms.') - end - - context 'when search param is not given' do - it 'does not show a flash message' do - get controller_action, params: params - - expect(flash.now[:notice]).to be_nil - end - end - - context 'when user is signed-in' do - it 'does not show a flash message' do - sign_in(create(:user)) - get controller_action, params: params_with_search - - expect(flash.now[:notice]).to be_nil - end - end - - context 'when format is not HTML' do - it 'does not show a flash message' do - get controller_action, params: params_with_search.merge(format: :atom) - - expect(flash.now[:notice]).to be_nil - end - end - end - - context 'when disable_anonymous_search is disabled' do - before do - stub_feature_flags(disable_anonymous_search: false) - end - - it 'does not show a flash message' do - get controller_action, params: params_with_search - - expect(flash.now[:notice]).to be_nil - end - end -end diff --git a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb index 3a7588a5cc9..cc28a79b4ca 100644 --- a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb @@ -61,9 +61,14 @@ RSpec.shared_examples Repositories::GitHttpController do end it 'updates the user activity' do - expect_next_instance_of(Users::ActivityService) do |activity_service| - expect(activity_service).to receive(:execute) - end + activity_project = container.is_a?(PersonalSnippet) ? nil : project + + activity_service = instance_double(Users::ActivityService) + + args = { author: user, project: activity_project, namespace: activity_project&.namespace } + expect(Users::ActivityService).to receive(:new).with(args).and_return(activity_service) + + expect(activity_service).to receive(:execute) get :info_refs, params: params end diff --git a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb index 7e99066110d..ba00e3e0610 100644 --- a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb +++ b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb @@ -52,3 +52,12 @@ RSpec.shared_examples 'Snowplow event tracking with RedisHLL context' do |overri end end end + +RSpec.shared_examples 'Snowplow event tracking with Redis context' do |overrides: {}| + it_behaves_like 'Snowplow event tracking', overrides: overrides do + let(:context) do + key_path = try(:label) || action + [Gitlab::Tracking::ServicePingContext.new(data_source: :redis, key_path: key_path).to_context.to_json] + end + end +end diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb index 7f31ea8f9be..96e57980c68 100644 --- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb @@ -2,6 +2,7 @@ RSpec.shared_examples 'a creatable merge request' do include WaitForRequests + include ListboxHelpers it 'creates new merge request', :js do find('.js-assignee-search').click @@ -64,14 +65,16 @@ RSpec.shared_examples 'a creatable merge request' do visit project_new_merge_request_path(source_project) first('.js-target-project').click - find('.dropdown-target-project li', text: target_project.full_path).click + select_listbox_item(target_project.full_path) wait_for_requests first('.js-target-branch').click - within('.js-target-branch-dropdown .dropdown-content') do - expect(page).to have_content('a-brand-new-branch-to-test') - end + find('.gl-listbox-search-input').set('a-brand-new-branch-to-test') + + wait_for_requests + + expect_listbox_item('a-brand-new-branch-to-test') end end diff --git a/spec/support/shared_examples/features/incident_details_routing_shared_examples.rb b/spec/support/shared_examples/features/incident_details_routing_shared_examples.rb new file mode 100644 index 00000000000..dab125caa60 --- /dev/null +++ b/spec/support/shared_examples/features/incident_details_routing_shared_examples.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'for each incident details route' do |example, tab_text:| + before do + sign_in(user) + visit incident_path + end + + context 'for /-/issues/:id route' do + let(:incident_path) { project_issue_path(project, incident) } + + before do + page.within('[data-testid="incident-tabs"]') { click_link tab_text } + end + + it_behaves_like example + end + + context 'for /-/issues/incident/:id route' do + let(:incident_path) { incident_project_issues_path(project, incident) } + + before do + page.within('[data-testid="incident-tabs"]') { click_link tab_text } + end + + it_behaves_like example + end +end diff --git a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb index 149486320ae..b6f7094e422 100644 --- a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb +++ b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true RSpec.shared_examples 'issuable invite members' do + include Spec::Support::Helpers::Features::InviteMembersModalHelper + context 'when a privileged user can invite' do before do project.add_maintainer(user) @@ -21,7 +23,9 @@ RSpec.shared_examples 'issuable invite members' do click_link 'Invite Members' - expect(page).to have_content("You're inviting members to the") + page.within invite_modal_selector do + expect(page).to have_content("You're inviting members to the #{project.name} project") + end end end diff --git a/spec/support/shared_examples/features/reportable_note_shared_examples.rb b/spec/support/shared_examples/features/reportable_note_shared_examples.rb index 9d859403465..bb3fab5b23e 100644 --- a/spec/support/shared_examples/features/reportable_note_shared_examples.rb +++ b/spec/support/shared_examples/features/reportable_note_shared_examples.rb @@ -20,7 +20,7 @@ RSpec.shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - expect(dropdown).to have_link('Report abuse to administrator', href: abuse_report_path) + expect(dropdown).to have_button('Report abuse to administrator') if type == 'issue' || type == 'merge_request' expect(dropdown).to have_button('Delete comment') @@ -33,10 +33,14 @@ RSpec.shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - dropdown.click_link('Report abuse to administrator') + dropdown.click_button('Report abuse to administrator') + + choose "They're posting spam." + click_button "Next" expect(find('#user_name')['value']).to match(note.author.username) expect(find('#abuse_report_reported_from_url')['value']).to match(noteable_note_url(note)) + expect(find('#abuse_report_category', visible: false)['value']).to match('spam') end def open_dropdown(dropdown) diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb index 20078243cfb..63a0832117d 100644 --- a/spec/support/shared_examples/features/runners_shared_examples.rb +++ b/spec/support/shared_examples/features/runners_shared_examples.rb @@ -178,6 +178,22 @@ RSpec.shared_examples 'filters by tag' do end end +RSpec.shared_examples 'shows runner jobs tab' do + context 'when clicking on jobs tab' do + before do + click_on("#{s_('Runners|Jobs')} #{job_count}") + + wait_for_requests + end + + it 'shows job in list' do + within "[data-testid='job-row-#{job.id}']" do + expect(page).to have_link("##{job.id}") + end + end + end +end + RSpec.shared_examples 'submits edit runner form' do it 'breadcrumb contains runner id and token' do page.within '[data-testid="breadcrumb-links"]' do diff --git a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb index 95b306fdaaa..a332fdec963 100644 --- a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb +++ b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb @@ -95,7 +95,7 @@ RSpec.shared_examples 'labels sidebar widget' do end end - it 'creates new label' do + it 'creates new label', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391240' do page.within(labels_widget) do fill_in 'Name new label', with: 'wontfix' page.find('.suggest-colors a', match: :first).click diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb index 77334db6a36..c2c50e8762f 100644 --- a/spec/support/shared_examples/features/sidebar_shared_examples.rb +++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb @@ -42,6 +42,26 @@ RSpec.shared_examples 'issue boards sidebar' do end end + context 'editing issue title' do + it 'edits issue title' do + page.within('[data-testid="sidebar-title"]') do + click_button 'Edit' + + wait_for_requests + + find('input').set('Test title') + + click_button 'Save changes' + + wait_for_requests + + expect(page).to have_content('Test title') + end + + expect(first_card).to have_content('Test title') + end + end + context 'editing issue milestone', :js do it_behaves_like 'milestone sidebar widget' 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 new file mode 100644 index 00000000000..4f3d957ad71 --- /dev/null +++ b/spec/support/shared_examples/features/work_items_shared_examples.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'work items status' do + let(:state_selector) { '[data-testid="work-item-state-select"]' } + + it 'successfully shows and changes the status of the work item' do + expect(find(state_selector)).to have_content 'Open' + + find(state_selector).select("Closed") + + wait_for_requests + + expect(find(state_selector)).to have_content 'Closed' + expect(work_item.reload.state).to eq('closed') + end +end + +RSpec.shared_examples 'work items comments' do + let(:form_selector) { '[data-testid="work-item-add-comment"]' } + + it 'successfully creates and shows comments' do + click_button 'Add a comment' + + find(form_selector).fill_in(with: "Test comment") + click_button "Comment" + + wait_for_requests + + expect(page).to have_content "Test comment" + end +end + +RSpec.shared_examples 'work items assignees' do + it 'successfully assigns the current user by searching' do + # The button is only when the mouse is over the input + find('[data-testid="work-item-assignees-input"]').fill_in(with: user.username) + wait_for_requests + + # submit and simulate blur to save + send_keys(:enter) + find("body").click + + wait_for_requests + + expect(work_item.assignees).to include(user) + end +end + +RSpec.shared_examples 'work items labels' do + 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) + wait_for_requests + + # submit and simulate blur to save + send_keys(:enter) + find("body").click + + wait_for_requests + + expect(work_item.labels).to include(label) + end +end + +RSpec.shared_examples 'work items description' do + it 'shows GFM autocomplete', :aggregate_failures do + click_button "Edit description" + + find('[aria-label="Description"]').send_keys("@#{user.username}") + + wait_for_requests + + page.within('.atwho-container') do + expect(page).to have_text(user.name) + end + end + + it 'autocompletes available quick actions', :aggregate_failures do + click_button "Edit description" + + find('[aria-label="Description"]').send_keys("/") + + wait_for_requests + + page.within('.atwho-container') do + expect(page).to have_text("title") + expect(page).to have_text("shrug") + expect(page).to have_text("tableflip") + expect(page).to have_text("close") + expect(page).to have_text("cc") + end + end + + context 'on conflict' do + let_it_be(:other_user) { create(:user) } + let(:expected_warning) { 'Someone edited the description at the same time you did.' } + + before do + project.add_developer(other_user) + end + + it 'shows conflict message when description changes', :aggregate_failures do + click_button "Edit description" + + wait_for_requests + + ::WorkItems::UpdateService.new( + container: work_item.project, + current_user: other_user, + params: { description: "oh no!" } + ).execute(work_item) + + wait_for_requests + + find('[aria-label="Description"]').send_keys("oh yeah!") + + expect(page.find('[data-testid="work-item-description-conflicts"]')).to have_text(expected_warning) + + click_button "Save and overwrite" + + expect(page.find('[data-testid="work-item-description"]')).to have_text("oh yeah!") + end + end +end + +RSpec.shared_examples 'work items invite members' do + include Spec::Support::Helpers::Features::InviteMembersModalHelper + + it 'successfully assigns the current user by searching' do + # The button is only when the mouse is over the input + find('[data-testid="work-item-assignees-input"]').fill_in(with: 'Invite members') + wait_for_requests + + click_button('Invite members') + + page.within invite_modal_selector do + expect(page).to have_content("You're inviting members to the #{work_item.project.name} project") + end + end +end diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb index 6f4072ba762..93f9e42241b 100644 --- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb +++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb @@ -670,35 +670,6 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context expect(items).to contain_exactly(english) end end - - context 'with anonymous user' do - let_it_be(:public_project) { create(:project, :public, group: subgroup) } - let_it_be(:item6) { create(factory, project: public_project, title: 'tanuki') } - let_it_be(:item7) { create(factory, project: public_project, title: 'ikunat') } - - let(:search_user) { nil } - let(:params) { { search: 'tanuki' } } - - context 'with disable_anonymous_search feature flag enabled' do - before do - stub_feature_flags(disable_anonymous_search: true) - end - - it 'does not perform search' do - expect(items).to contain_exactly(item6, item7) - end - end - - context 'with disable_anonymous_search feature flag disabled' do - before do - stub_feature_flags(disable_anonymous_search: false) - end - - it 'finds one public item' do - expect(items).to contain_exactly(item6) - end - end - end end context 'filtering by item term in title' do @@ -1003,7 +974,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context let(:params) { { issue_types: ['nonsense'] } } it 'returns no items' do - expect(items).to eq(items_model.none) + expect(items.none?).to eq(true) end end end @@ -1285,28 +1256,12 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context subject { described_class.new(nil, params).with_confidentiality_access_check } it_behaves_like 'returns public, does not return hidden or confidential' - - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public and hidden, does not return confidential' - end end context 'for a user without project membership' do subject { described_class.new(user, params).with_confidentiality_access_check } it_behaves_like 'returns public, does not return hidden or confidential' - - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public and hidden, does not return confidential' - end end context 'for a guest user' do @@ -1317,28 +1272,12 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context end it_behaves_like 'returns public, does not return hidden or confidential' - - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public and hidden, does not return confidential' - end end context 'for a project member with access to view confidential items' do subject { described_class.new(authorized_user, params).with_confidentiality_access_check } it_behaves_like 'returns public and confidential, does not return hidden' - - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public, confidential, and hidden' - end end context 'for an admin' do @@ -1348,26 +1287,10 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context context 'when admin mode is enabled', :enable_admin_mode do it_behaves_like 'returns public, confidential, and hidden' - - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public, confidential, and hidden' - end end context 'when admin mode is disabled' do it_behaves_like 'returns public, does not return hidden or confidential' - - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public and hidden, does not return confidential' - end end end end @@ -1380,14 +1303,6 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context it_behaves_like 'returns public, does not return hidden or confidential' - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public and hidden, does not return confidential' - end - it 'does not filter by confidentiality' do expect(items_model).not_to receive(:where).with(a_string_matching('confidential'), anything) subject @@ -1399,14 +1314,6 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context it_behaves_like 'returns public, does not return hidden or confidential' - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public and hidden, does not return confidential' - end - it 'filters by confidentiality' do expect(subject.to_sql).to match("issues.confidential") end @@ -1421,14 +1328,6 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context it_behaves_like 'returns public, does not return hidden or confidential' - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public and hidden, does not return confidential' - end - it 'filters by confidentiality' do expect(subject.to_sql).to match("issues.confidential") end @@ -1439,14 +1338,6 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context it_behaves_like 'returns public and confidential, does not return hidden' - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public, confidential, and hidden' - end - it 'does not filter by confidentiality' do expect(items_model).not_to receive(:where).with(a_string_matching('confidential'), anything) @@ -1462,14 +1353,6 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context context 'when admin mode is enabled', :enable_admin_mode do it_behaves_like 'returns public, confidential, and hidden' - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public, confidential, and hidden' - end - it 'does not filter by confidentiality' do expect(items_model).not_to receive(:where).with(a_string_matching('confidential'), anything) @@ -1480,14 +1363,6 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context context 'when admin mode is disabled' do it_behaves_like 'returns public, does not return hidden or confidential' - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it_behaves_like 'returns public and hidden, does not return confidential' - end - it 'filters by confidentiality' do expect(subject.to_sql).to match("issues.confidential") end diff --git a/spec/support/shared_examples/finders/packages/debian/distributions_finder_shared_examples.rb b/spec/support/shared_examples/finders/packages/debian/distributions_finder_shared_examples.rb index 2700d29bf0e..5e7a2b77797 100644 --- a/spec/support/shared_examples/finders/packages/debian/distributions_finder_shared_examples.rb +++ b/spec/support/shared_examples/finders/packages/debian/distributions_finder_shared_examples.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.shared_examples 'Debian Distributions Finder' do |factory, can_freeze| - let_it_be(:distribution_with_suite, freeze: can_freeze) { create(factory, suite: 'mysuite') } + let_it_be(:distribution_with_suite, freeze: can_freeze) { create(factory, :with_suite) } let_it_be(:container) { distribution_with_suite.container } let_it_be(:distribution_with_same_container, freeze: can_freeze) { create(factory, container: container ) } let_it_be(:distribution_with_same_codename, freeze: can_freeze) { create(factory, codename: distribution_with_suite.codename ) } @@ -35,7 +35,7 @@ RSpec.shared_examples 'Debian Distributions Finder' do |factory, can_freeze| context 'by suite' do context 'with existing suite' do - let(:params) { { suite: 'mysuite' } } + let(:params) { { suite: distribution_with_suite.suite } } it 'finds distribution by suite' do is_expected.to contain_exactly(distribution_with_suite) @@ -61,7 +61,7 @@ RSpec.shared_examples 'Debian Distributions Finder' do |factory, can_freeze| end context 'with existing suite' do - let(:params) { { codename_or_suite: 'mysuite' } } + let(:params) { { codename_or_suite: distribution_with_suite.suite } } it 'finds distribution by suite' do is_expected.to contain_exactly(distribution_with_suite) diff --git a/spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb index f672ec7f5ac..2ec48aa405b 100644 --- a/spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb @@ -31,4 +31,88 @@ RSpec.shared_examples 'update work item description widget' do expect(mutation_response['errors']).to match_array(['Description error message']) end end + + context 'when the edited description includes quick action(s)' do + let(:input) { { 'descriptionWidget' => { 'description' => new_description } } } + + shared_examples 'quick action is applied' do + before do + post_graphql_mutation(mutation, current_user: current_user) + end + + it 'applies the quick action(s)' do + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['workItem']).to include(expected_response) + end + end + + context 'with /title quick action' do + it_behaves_like 'quick action is applied' do + let(:new_description) { "updated description\n/title updated title" } + let(:filtered_description) { "updated description" } + + let(:expected_response) do + { + 'title' => 'updated title', + 'widgets' => include({ + 'description' => filtered_description, + 'type' => 'DESCRIPTION' + }) + } + end + end + end + + context 'with /shrug, /tableflip and /cc quick action' do + it_behaves_like 'quick action is applied' do + let(:new_description) { "/tableflip updated description\n/shrug\n/cc @#{developer.username}" } + # note: \cc performs no action since 15.0 + let(:filtered_description) { "updated description (╯°□°)╯︵ ┻━┻\n ¯\\_(ツ)_/¯\n/cc @#{developer.username}" } + let(:expected_response) do + { + 'widgets' => include({ + 'description' => filtered_description, + 'type' => 'DESCRIPTION' + }) + } + end + end + end + + context 'with /close' do + it_behaves_like 'quick action is applied' do + let(:new_description) { "Resolved work item.\n/close" } + let(:filtered_description) { "Resolved work item." } + let(:expected_response) do + { + 'state' => 'CLOSED', + 'widgets' => include({ + 'description' => filtered_description, + 'type' => 'DESCRIPTION' + }) + } + end + end + end + + context 'with /reopen' do + before do + work_item.close! + end + + it_behaves_like 'quick action is applied' do + let(:new_description) { "Re-opening this work item.\n/reopen" } + let(:filtered_description) { "Re-opening this work item." } + let(:expected_response) do + { + 'state' => 'OPEN', + 'widgets' => include({ + 'description' => filtered_description, + 'type' => 'DESCRIPTION' + }) + } + end + end + end + end end diff --git a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb index 16c2ab07f3a..d9a9400cb4e 100644 --- a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb +++ b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true - +# TODO: Remove in 17.0, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108418 RSpec.shared_examples 'no project services' do it 'returns empty collection' do expect(resolve_services).to be_empty diff --git a/spec/support/shared_examples/graphql/resolvers/issuable_resolvers_shared_examples.rb b/spec/support/shared_examples/graphql/resolvers/issuable_resolvers_shared_examples.rb index 25008bca619..6b2b45d28e8 100644 --- a/spec/support/shared_examples/graphql/resolvers/issuable_resolvers_shared_examples.rb +++ b/spec/support/shared_examples/graphql/resolvers/issuable_resolvers_shared_examples.rb @@ -58,41 +58,6 @@ RSpec.shared_examples 'graphql query for searching issuables' do end end - context 'with anonymous user' do - let_it_be(:current_user) { nil } - - context 'with disable_anonymous_search as `true`' do - before do - stub_feature_flags(disable_anonymous_search: true) - end - - it 'returns an error' do - error_message = "User must be authenticated to include the `search` argument." - - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, error_message) do - resolve_issuables(search: 'created') - end - end - - it 'does not return error if search term is not present' do - expect(resolve_issuables).not_to be_instance_of(Gitlab::Graphql::Errors::ArgumentError) - end - end - - context 'with disable_anonymous_search as `false`' do - before do - stub_feature_flags(disable_anonymous_search: false) - parent.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) - end - - it 'filters issuables by search term' do - issuables = resolve_issuables(search: 'created') - - expect(issuables).to contain_exactly(issuable1, issuable2) - end - end - end - def resolve_issuables(args = {}, obj = parent, context = { current_user: current_user }) resolve(described_class, obj: obj, args: args, ctx: context, arg_style: :internal) end diff --git a/spec/support/shared_examples/graphql/resolvers/releases_resolvers_shared_examples.rb b/spec/support/shared_examples/graphql/resolvers/releases_resolvers_shared_examples.rb new file mode 100644 index 00000000000..0e09a9d9e66 --- /dev/null +++ b/spec/support/shared_examples/graphql/resolvers/releases_resolvers_shared_examples.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'releases and group releases resolver' do + context 'when the user does not have access to the project' do + let(:current_user) { public_user } + + it 'returns an empty array' do + expect(resolve_releases).to be_empty + end + end + + context "when the user has full access to the project's releases" do + let(:current_user) { developer } + + it 'returns all releases associated to the project' do + expect(resolve_releases).to match_array(all_releases) + end + + describe 'when order_by is released_at' do + context 'with sort: desc' do + let(:args) { { sort: :released_at_desc } } + + it 'returns the releases ordered by released_at in descending order' do + expect(resolve_releases.to_a) + .to match_array(all_releases) + .and be_sorted(:released_at, :desc) + end + end + + context 'with sort: asc' do + let(:args) { { sort: :released_at_asc } } + + it 'returns the releases ordered by released_at in ascending order' do + expect(resolve_releases.to_a) + .to match_array(all_releases) + .and be_sorted(:released_at, :asc) + end + end + end + end +end diff --git a/spec/support/shared_examples/graphql/subscriptions/notes/notes_subscription_shared_examples.rb b/spec/support/shared_examples/graphql/subscriptions/notes/notes_subscription_shared_examples.rb new file mode 100644 index 00000000000..949eb4fb643 --- /dev/null +++ b/spec/support/shared_examples/graphql/subscriptions/notes/notes_subscription_shared_examples.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'graphql notes subscriptions' do + describe '#resolve' do + let_it_be(:unauthorized_user) { create(:user) } + let_it_be(:work_item) { create(:work_item, :task) } + let_it_be(:note) { create(:note, noteable: work_item, project: work_item.project) } + let_it_be(:current_user) { work_item.author } + let_it_be(:noteable_id) { work_item.to_gid } + + subject { resolver.resolve_with_support(noteable_id: noteable_id) } + + context 'on initial subscription' do + let(:resolver) do + resolver_instance(described_class, ctx: { current_user: current_user }, subscription_update: false) + end + + it 'returns nil' do + expect(subject).to eq(nil) + end + + context 'when user is unauthorized' do + let(:current_user) { unauthorized_user } + + it 'raises an exception' do + expect { subject }.to raise_error(GraphQL::ExecutionError) + end + end + + context 'when work_item does not exist' do + let(:noteable_id) { GlobalID.parse("gid://gitlab/WorkItem/#{non_existing_record_id}") } + + it 'raises an exception' do + expect { subject }.to raise_error(GraphQL::ExecutionError) + end + end + end + + context 'on subscription updates' do + let(:resolver) do + resolver_instance(described_class, obj: note, ctx: { current_user: current_user }, subscription_update: true) + end + + it 'returns the resolved object' do + expect(subject).to eq(note) + end + + context 'when user is unauthorized' do + let(:current_user) { unauthorized_user } + + it 'unsubscribes the user' do + # GraphQL::Execution::Execute::Skip is returned when unsubscribed + expect(subject).to be_an(GraphQL::Execution::Execute::Skip) + end + end + end + end +end diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb index 67576a18c80..4dc2ce61c4d 100644 --- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb +++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb @@ -5,8 +5,7 @@ RSpec.shared_examples 'Gitlab-style deprecations' do it 'raises an informative error if `deprecation_reason` is used' do expect { subject(deprecation_reason: 'foo') }.to raise_error( ArgumentError, - 'Use `deprecated` property instead of `deprecation_reason`. ' \ - 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-schema-items' + start_with('Use `deprecated` property instead of `deprecation_reason`.') ) end diff --git a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb index 19ceb465383..bb33a7559dc 100644 --- a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb +++ b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb @@ -41,6 +41,7 @@ RSpec.shared_examples "a user type with merge request interaction type" do preferencesGitpodPath profileEnableGitpodPath savedReplies + savedReply ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/support/shared_examples/integrations/integration_settings_form.rb b/spec/support/shared_examples/integrations/integration_settings_form.rb index 5041ac4a660..aeb4e0feb12 100644 --- a/spec/support/shared_examples/integrations/integration_settings_form.rb +++ b/spec/support/shared_examples/integrations/integration_settings_form.rb @@ -4,9 +4,10 @@ RSpec.shared_examples 'integration settings form' do include IntegrationsHelper # Note: these specs don't validate channel fields # which are present on a few integrations - it 'displays all the integrations' do + it 'displays all the integrations', feature_category: :integrations do aggregate_failures do integrations.each do |integration| + stub_feature_flags(integration_slack_app_notifications: false) navigate_to_integration(integration) page.within('form.integration-settings-form') do @@ -53,12 +54,14 @@ RSpec.shared_examples 'integration settings form' do # Should match `integrationTriggerEventTitles` in app/assets/javascripts/integrations/constants.js event_titles = { push_events: s_('IntegrationEvents|A push is made to the repository'), - issues_events: s_('IntegrationEvents|IntegrationEvents|An issue is created, updated, or closed'), - confidential_issues_events: s_('IntegrationEvents|A confidential issue is created, updated, or closed'), - merge_requests_events: s_('IntegrationEvents|A merge request is created, updated, or merged'), - note_events: s_('IntegrationEvents|A comment is added on an issue'), - confidential_note_events: s_('IntegrationEvents|A comment is added on a confidential issue'), - tag_push_events: s_('IntegrationEvents|A tag is pushed to the repository'), + issues_events: s_('IntegrationEvents|An issue is created, closed, or reopened'), + confidential_issues_events: s_('A confidential issue is created, closed, or reopened'), + merge_requests_events: s_('IntegrationEvents|A merge request is created, merged, closed, or reopened'), + note_events: s_('IntegrationEvents|A comment is added'), + confidential_note_events: s_( + 'IntegrationEvents|An internal note or comment on a confidential issue is added' + ), + tag_push_events: s_('IntegrationEvents|A tag is pushed to the repository or removed'), pipeline_events: s_('IntegrationEvents|A pipeline status changes'), wiki_page_events: s_('IntegrationEvents|A wiki page is created or updated') }.with_indifferent_access diff --git a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb index 6cdd7954b5f..42e8fa3d51f 100644 --- a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb +++ b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb @@ -17,7 +17,7 @@ RSpec.shared_examples_for 'object cache helper' do end it "fetches from the cache" do - expect(instance.cache).to receive(:fetch).with("#{presenter.class.name}:#{presentable.cache_key}:#{user.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once + expect(instance.cache).to receive(:fetch).with("#{expected_cache_key_prefix}:#{presentable.cache_key}:#{user.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once subject end @@ -28,7 +28,7 @@ RSpec.shared_examples_for 'object cache helper' do end it "uses the context to augment the cache key" do - expect(instance.cache).to receive(:fetch).with("#{presenter.class.name}:#{presentable.cache_key}:#{project.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once + expect(instance.cache).to receive(:fetch).with("#{expected_cache_key_prefix}:#{presentable.cache_key}:#{project.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once subject end @@ -38,7 +38,7 @@ RSpec.shared_examples_for 'object cache helper' do it "sets the expiry when accessing the cache" do kwargs[:expires_in] = 7.days - expect(instance.cache).to receive(:fetch).with("#{presenter.class.name}:#{presentable.cache_key}:#{user.cache_key}", expires_in: 7.days).once + expect(instance.cache).to receive(:fetch).with("#{expected_cache_key_prefix}:#{presentable.cache_key}:#{user.cache_key}", expires_in: 7.days).once subject end @@ -90,7 +90,7 @@ RSpec.shared_examples_for 'collection cache helper' do end it "fetches from the cache" do - keys = presentable.map { |item| "#{presenter.class.name}:#{item.cache_key}:#{user.cache_key}" } + keys = presentable.map { |item| "#{expected_cache_key_prefix}:#{item.cache_key}:#{user.cache_key}" } expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: described_class::DEFAULT_EXPIRY).once.and_call_original @@ -103,7 +103,7 @@ RSpec.shared_examples_for 'collection cache helper' do end it "uses the context to augment the cache key" do - keys = presentable.map { |item| "#{presenter.class.name}:#{item.cache_key}:#{project.cache_key}" } + keys = presentable.map { |item| "#{expected_cache_key_prefix}:#{item.cache_key}:#{project.cache_key}" } expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: described_class::DEFAULT_EXPIRY).once.and_call_original @@ -113,7 +113,7 @@ RSpec.shared_examples_for 'collection cache helper' do context "expires_in is supplied" do it "sets the expiry when accessing the cache" do - keys = presentable.map { |item| "#{presenter.class.name}:#{item.cache_key}:#{user.cache_key}" } + keys = presentable.map { |item| "#{expected_cache_key_prefix}:#{item.cache_key}:#{user.cache_key}" } kwargs[:expires_in] = 7.days expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: 7.days).once.and_call_original diff --git a/spec/support/shared_examples/mailers/notify_shared_examples.rb b/spec/support/shared_examples/mailers/notify_shared_examples.rb index b0cbf0b0d65..2e182fb399d 100644 --- a/spec/support/shared_examples/mailers/notify_shared_examples.rb +++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb @@ -280,6 +280,12 @@ RSpec.shared_examples 'no email is sent' do end end +RSpec.shared_examples 'a mail with default delivery method' do + it 'uses mailer default delivery method' do + expect(subject.delivery_method).to be_a ActionMailer::Base.delivery_methods[described_class.delivery_method] + end +end + RSpec.shared_examples 'does not render a manage notifications link' do it do aggregate_failures do diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb index 6d0462a9ee8..085fec6ff1e 100644 --- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb @@ -33,7 +33,7 @@ RSpec.shared_examples "chat integration" do |integration_name| describe "#execute" do let_it_be(:user) { create(:user) } - let_it_be_with_reload(:project) { create(:project, :repository) } + let_it_be_with_refind(:project) { create(:project, :repository) } let(:webhook_url) { "https://example.gitlab.com/" } let(:webhook_url_regex) { /\A#{webhook_url}.*/ } @@ -165,7 +165,7 @@ RSpec.shared_examples "chat integration" do |integration_name| context "with issue events" do let(:opts) { { title: "Awesome issue", description: "please fix" } } let(:sample_data) do - service = Issues::CreateService.new(project: project, current_user: user, params: opts, spam_params: nil) + service = Issues::CreateService.new(container: project, current_user: user, params: opts, spam_params: nil) issue = service.execute[:issue] service.hook_data(issue, "open") end diff --git a/spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb b/spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb new file mode 100644 index 00000000000..122774a9028 --- /dev/null +++ b/spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb @@ -0,0 +1,272 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a hook that gets automatically disabled on failure' do + shared_examples 'is tolerant of invalid records' do + specify do + hook.url = nil + + expect(hook).to be_invalid + run_expectation + end + end + + describe '.executable/.disabled', :freeze_time do + let!(:not_executable) do + [ + [4, nil], # Exceeded the grace period, set by #fail! + [4, 1.second.from_now], # Exceeded the grace period, set by #backoff! + [4, Time.current] # Exceeded the grace period, set by #backoff!, edge-case + ].map do |(recent_failures, disabled_until)| + create(hook_factory, **default_factory_arguments, recent_failures: recent_failures, +disabled_until: disabled_until) + end + end + + let!(:executables) do + expired = 1.second.ago + borderline = Time.current + suspended = 1.second.from_now + + [ + # Most of these are impossible states, but are included for completeness + [0, nil], + [1, nil], + [3, nil], + [4, expired], + + # Impossible cases: + [3, suspended], + [3, expired], + [3, borderline], + [1, suspended], + [1, expired], + [1, borderline], + [0, borderline], + [0, suspended], + [0, expired] + ].map do |(recent_failures, disabled_until)| + create(hook_factory, **default_factory_arguments, recent_failures: recent_failures, +disabled_until: disabled_until) + end + end + + it 'finds the correct set of project hooks' do + expect(find_hooks.executable).to match_array executables + expect(find_hooks.executable).to all(be_executable) + + # As expected, and consistent + expect(find_hooks.disabled).to match_array not_executable + expect(find_hooks.disabled.map(&:executable?)).not_to include(true) + + # Nothing is missing + expect(find_hooks.executable.to_a + find_hooks.disabled.to_a).to match_array(find_hooks.to_a) + end + end + + describe '#executable?', :freeze_time do + let(:web_hook) { create(hook_factory, **default_factory_arguments) } + + where(:recent_failures, :not_until, :executable) do + [ + [0, :not_set, true], + [0, :past, true], + [0, :future, true], + [0, :now, true], + [1, :not_set, true], + [1, :past, true], + [1, :future, true], + [3, :not_set, true], + [3, :past, true], + [3, :future, true], + [4, :not_set, false], + [4, :past, true], # expired suspension + [4, :now, false], # active suspension + [4, :future, false] # active suspension + ] + end + + with_them do + # Phasing means we cannot put these values in the where block, + # which is not subject to the frozen time context. + let(:disabled_until) do + case not_until + when :not_set + nil + when :past + 1.minute.ago + when :future + 1.minute.from_now + when :now + Time.current + end + end + + before do + web_hook.update!(recent_failures: recent_failures, disabled_until: disabled_until) + end + + it 'has the correct state' do + expect(web_hook.executable?).to eq(executable) + end + end + end + + describe '#enable!' do + it 'makes a hook executable if it was marked as failed' do + hook.recent_failures = 1000 + + expect { hook.enable! }.to change { hook.executable? }.from(false).to(true) + end + + it 'makes a hook executable if it is currently backed off' do + hook.recent_failures = 1000 + hook.disabled_until = 1.hour.from_now + + expect { hook.enable! }.to change { hook.executable? }.from(false).to(true) + end + + it 'does not update hooks unless necessary' do + hook + + sql_count = ActiveRecord::QueryRecorder.new { hook.enable! }.count + + expect(sql_count).to eq(0) + end + + include_examples 'is tolerant of invalid records' do + def run_expectation + hook.recent_failures = 1000 + + expect { hook.enable! }.to change { hook.executable? }.from(false).to(true) + end + end + end + + describe '#backoff!' do + context 'when we have not backed off before' do + it 'does not disable the hook' do + expect { hook.backoff! }.not_to change { hook.executable? }.from(true) + end + end + + context 'when we have exhausted the grace period' do + before do + hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD) + end + + context 'when the hook is permanently disabled' do + before do + allow(hook).to receive(:permanently_disabled?).and_return(true) + end + + it 'does not set disabled_until' do + expect { hook.backoff! }.not_to change { hook.disabled_until } + end + + it 'does not increment the backoff count' do + expect { hook.backoff! }.not_to change { hook.backoff_count } + end + end + + include_examples 'is tolerant of invalid records' do + def run_expectation + expect { hook.backoff! }.to change { hook.backoff_count }.by(1) + end + end + end + end + + describe '#failed!' do + include_examples 'is tolerant of invalid records' do + def run_expectation + expect { hook.failed! }.to change { hook.recent_failures }.by(1) + end + end + end + + describe '#disable!' do + it 'disables a hook' do + expect { hook.disable! }.to change { hook.executable? }.from(true).to(false) + end + + it 'does nothing if the hook is already disabled' do + allow(hook).to receive(:permanently_disabled?).and_return(true) + + sql_count = ActiveRecord::QueryRecorder.new { hook.disable! }.count + + expect(sql_count).to eq(0) + end + + include_examples 'is tolerant of invalid records' do + def run_expectation + expect { hook.disable! }.to change { hook.executable? }.from(true).to(false) + end + end + end + + describe '#temporarily_disabled?' do + it 'is false when not temporarily disabled' do + expect(hook).not_to be_temporarily_disabled + end + + it 'allows FAILURE_THRESHOLD initial failures before we back-off' do + WebHook::FAILURE_THRESHOLD.times do + hook.backoff! + expect(hook).not_to be_temporarily_disabled + end + + hook.backoff! + expect(hook).to be_temporarily_disabled + end + + context 'when hook has been told to back off' do + before do + hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD) + hook.backoff! + end + + it 'is true' do + expect(hook).to be_temporarily_disabled + end + end + end + + describe '#permanently_disabled?' do + it 'is false when not disabled' do + expect(hook).not_to be_permanently_disabled + end + + context 'when hook has been disabled' do + before do + hook.disable! + end + + it 'is true' do + expect(hook).to be_permanently_disabled + end + end + end + + describe '#alert_status' do + subject(:status) { hook.alert_status } + + it { is_expected.to eq :executable } + + context 'when hook has been disabled' do + before do + hook.disable! + end + + it { is_expected.to eq :disabled } + end + + context 'when hook has been backed off' do + before do + hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1) + hook.disabled_until = 1.hour.from_now + end + + it { is_expected.to eq :temporarily_disabled } + end + end +end diff --git a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb index f98be12523d..5755b9a56b1 100644 --- a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| describe attribute do describe '#increment_counter', :redis do let(:amount) { 10 } - let(:increment) { Gitlab::Counters::Increment.new(amount: amount) } + let(:increment) { Gitlab::Counters::Increment.new(amount: amount, ref: 3) } let(:counter_key) { model.counter(attribute).key } subject { model.increment_counter(attribute, increment) } @@ -31,6 +31,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| attribute: attribute, project_id: model.project_id, increment: amount, + ref: increment.ref, new_counter_value: 0 + amount, current_db_value: model.read_attribute(attribute), 'correlation_id' => an_instance_of(String), @@ -74,27 +75,36 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| end describe '#bulk_increment_counter', :redis do - let(:increments) { [Gitlab::Counters::Increment.new(amount: 10), Gitlab::Counters::Increment.new(amount: 5)] } + let(:increments) do + [ + Gitlab::Counters::Increment.new(amount: 10, ref: 1), + Gitlab::Counters::Increment.new(amount: 5, ref: 2) + ] + end + let(:total_amount) { increments.sum(&:amount) } let(:counter_key) { model.counter(attribute).key } subject { model.bulk_increment_counter(attribute, increments) } context 'when attribute is a counter attribute' do - it 'increments the counter in Redis and logs it' do - expect(Gitlab::AppLogger).to receive(:info).with( - hash_including( - message: 'Increment counter attribute', - attribute: attribute, - project_id: model.project_id, - increment: total_amount, - new_counter_value: 0 + total_amount, - current_db_value: model.read_attribute(attribute), - 'correlation_id' => an_instance_of(String), - 'meta.feature_category' => 'test', - 'meta.caller_id' => 'caller' + it 'increments the counter in Redis and logs each increment' do + increments.each do |increment| + expect(Gitlab::AppLogger).to receive(:info).with( + hash_including( + message: 'Increment counter attribute', + attribute: attribute, + project_id: model.project_id, + increment: increment.amount, + ref: increment.ref, + new_counter_value: 0 + total_amount, + current_db_value: model.read_attribute(attribute), + 'correlation_id' => an_instance_of(String), + 'meta.feature_category' => 'test', + 'meta.caller_id' => 'caller' + ) ) - ) + end subject @@ -104,6 +114,30 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| end end + context 'when feature flag split_log_bulk_increment_counter is disabled' do + before do + stub_feature_flags(split_log_bulk_increment_counter: false) + end + + it 'logs a single total increment' do + expect(Gitlab::AppLogger).to receive(:info).with( + hash_including( + message: 'Increment counter attribute', + attribute: attribute, + project_id: model.project_id, + increment: increments.sum(&:amount), + new_counter_value: 0 + total_amount, + current_db_value: model.read_attribute(attribute), + 'correlation_id' => an_instance_of(String), + 'meta.feature_category' => 'test', + 'meta.caller_id' => 'caller' + ) + ) + + subject + end + end + it 'does not increment the counter for the record' do expect { subject }.not_to change { model.reset.read_attribute(attribute) } end diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb index 974fc8f402a..0ef9ab25505 100644 --- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb @@ -276,7 +276,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name describe 'Push events' do let_it_be(:user) { create(:user) } - let_it_be_with_reload(:project) { create(:project, :repository, creator: user) } + let_it_be_with_refind(:project) { create(:project, :repository, creator: user) } before do allow(chat_integration).to receive_messages( @@ -520,7 +520,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name describe 'Pipeline events' do let_it_be(:user) { create(:user) } - let_it_be_with_reload(:project) { create(:project, :repository, creator: user) } + let_it_be_with_refind(:project) { create(:project, :repository, creator: user) } let(:pipeline) do create(:ci_pipeline, project: project, status: status, @@ -671,7 +671,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name describe 'Deployment events' do let_it_be(:user) { create(:user) } - let_it_be_with_reload(:project) { create(:project, :repository, creator: user) } + let_it_be_with_refind(:project) { create(:project, :repository, creator: user) } let_it_be(:deployment) do create(:deployment, :success, project: project, sha: project.commit.sha, ref: project.default_branch) diff --git a/spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb b/spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb new file mode 100644 index 00000000000..848840ee297 --- /dev/null +++ b/spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a hook that does not get automatically disabled on failure' do + describe '.executable/.disabled', :freeze_time do + let!(:executables) do + [ + [0, Time.current], + [0, 1.minute.from_now], + [1, 1.minute.from_now], + [3, 1.minute.from_now], + [4, nil], + [4, 1.day.ago], + [4, 1.minute.from_now], + [0, nil], + [0, 1.day.ago], + [1, nil], + [1, 1.day.ago], + [3, nil], + [3, 1.day.ago] + ].map do |(recent_failures, disabled_until)| + create(hook_factory, **default_factory_arguments, recent_failures: recent_failures, +disabled_until: disabled_until) + end + end + + it 'finds the correct set of project hooks' do + expect(find_hooks).to all(be_executable) + expect(find_hooks.executable).to match_array executables + expect(find_hooks.disabled).to be_empty + end + end + + describe '#executable?', :freeze_time do + let(:web_hook) { create(hook_factory, **default_factory_arguments) } + + where(:recent_failures, :not_until) do + [ + [0, :not_set], + [0, :past], + [0, :future], + [0, :now], + [1, :not_set], + [1, :past], + [1, :future], + [3, :not_set], + [3, :past], + [3, :future], + [4, :not_set], + [4, :past], # expired suspension + [4, :now], # active suspension + [4, :future] # active suspension + ] + end + + with_them do + # Phasing means we cannot put these values in the where block, + # which is not subject to the frozen time context. + let(:disabled_until) do + case not_until + when :not_set + nil + when :past + 1.minute.ago + when :future + 1.minute.from_now + when :now + Time.current + end + end + + before do + web_hook.update!(recent_failures: recent_failures, disabled_until: disabled_until) + end + + it 'has the correct state' do + expect(web_hook).to be_executable + end + end + end + + describe '#enable!' do + it 'makes a hook executable if it was marked as failed' do + hook.recent_failures = 1000 + + expect { hook.enable! }.not_to change { hook.executable? }.from(true) + end + + it 'makes a hook executable if it is currently backed off' do + hook.recent_failures = 1000 + hook.disabled_until = 1.hour.from_now + + expect { hook.enable! }.not_to change { hook.executable? }.from(true) + end + + it 'does not update hooks unless necessary' do + hook + + sql_count = ActiveRecord::QueryRecorder.new { hook.enable! }.count + + expect(sql_count).to eq(0) + end + end + + describe '#backoff!' do + context 'when we have not backed off before' do + it 'does not disable the hook' do + expect { hook.backoff! }.not_to change { hook.executable? }.from(true) + end + end + + context 'when we have exhausted the grace period' do + before do + hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD) + end + + it 'does not disable the hook' do + expect { hook.backoff! }.not_to change { hook.executable? }.from(true) + end + end + end + + describe '#disable!' do + it 'does not disable a group hook' do + expect { hook.disable! }.not_to change { hook.executable? }.from(true) + end + end + + describe '#temporarily_disabled?' do + it 'is false' do + # Initially + expect(hook).not_to be_temporarily_disabled + + # Backing off + WebHook::FAILURE_THRESHOLD.times do + hook.backoff! + expect(hook).not_to be_temporarily_disabled + end + + hook.backoff! + expect(hook).not_to be_temporarily_disabled + end + end + + describe '#permanently_disabled?' do + it 'is false' do + # Initially + expect(hook).not_to be_permanently_disabled + + hook.disable! + + expect(hook).not_to be_permanently_disabled + end + end + + describe '#alert_status' do + subject(:status) { hook.alert_status } + + it { is_expected.to eq :executable } + + context 'when hook has been disabled' do + before do + hook.disable! + end + + it { is_expected.to eq :executable } + end + + context 'when hook has been backed off' do + before do + hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1) + hook.disabled_until = 1.hour.from_now + end + + it { is_expected.to eq :executable } + end + end +end diff --git a/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb b/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb new file mode 100644 index 00000000000..cd6eb8c77fa --- /dev/null +++ b/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'something that has web-hooks' do + describe '#any_hook_failed?', :clean_gitlab_redis_shared_state do + subject { object.any_hook_failed? } + + context 'when there are no hooks' do + it { is_expected.to eq(false) } + end + + context 'when there are hooks' do + before do + create_hook + create_hook + end + + it { is_expected.to eq(false) } + + context 'when there is a failed hook' do + before do + hook = create_hook + hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1) + end + + it { is_expected.to eq(true) } + end + end + end + + describe '#cache_web_hook_failure', :clean_gitlab_redis_shared_state do + context 'when no value is passed' do + it 'stores the return value of #any_hook_failed? and passes it back' do + allow(object).to receive(:any_hook_failed?).and_return(true) + + Gitlab::Redis::SharedState.with do |r| + expect(r).to receive(:set) + .with(object.web_hook_failure_redis_key, 'true', ex: 1.hour) + .and_call_original + end + + expect(object.cache_web_hook_failure).to eq(true) + end + end + + context 'when a value is passed' do + it 'stores the value and passes it back' do + expect(object).not_to receive(:any_hook_failed?) + + Gitlab::Redis::SharedState.with do |r| + expect(r).to receive(:set) + .with(object.web_hook_failure_redis_key, 'foo', ex: 1.hour) + .and_call_original + end + + expect(object.cache_web_hook_failure(:foo)).to eq(:foo) + end + end + end + + describe '#get_web_hook_failure', :clean_gitlab_redis_shared_state do + subject { object.get_web_hook_failure } + + context 'when no value is stored' do + it { is_expected.to be_nil } + end + + context 'when stored as true' do + before do + object.cache_web_hook_failure(true) + end + + it { is_expected.to eq(true) } + end + + context 'when stored as false' do + before do + object.cache_web_hook_failure(false) + end + + it { is_expected.to eq(false) } + end + end + + describe '#fetch_web_hook_failure', :clean_gitlab_redis_shared_state do + context 'when a value has not been stored' do + it 'does not call #any_hook_failed?' do + expect(object.get_web_hook_failure).to be_nil + expect(object).to receive(:any_hook_failed?).and_return(true) + + expect(object.fetch_web_hook_failure).to eq(true) + expect(object.get_web_hook_failure).to eq(true) + end + end + + context 'when a value has been stored' do + before do + object.cache_web_hook_failure(true) + end + + it 'does not call #any_hook_failed?' do + expect(object).not_to receive(:any_hook_failed?) + + expect(object.fetch_web_hook_failure).to eq(true) + end + end + end +end diff --git a/spec/support/shared_examples/models/exportable_shared_examples.rb b/spec/support/shared_examples/models/exportable_shared_examples.rb new file mode 100644 index 00000000000..37c3e68fd5f --- /dev/null +++ b/spec/support/shared_examples/models/exportable_shared_examples.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'resource with exportable associations' do + before do + stub_licensed_features(stubbed_features) if stubbed_features.any? + end + + describe '#exportable_association?' do + let(:association) { single_association } + + subject { resource.exportable_association?(association, current_user: user) } + + it { is_expected.to be_falsey } + + context 'when user can read resource' do + before do + group.add_developer(user) + end + + it { is_expected.to be_falsey } + + context "when user can read resource's association" do + before do + other_group.add_developer(user) + end + + it { is_expected.to be_truthy } + + context 'for an unknown association' do + let(:association) { :foo } + + it { is_expected.to be_falsey } + end + + context 'for an unauthenticated user' do + let(:user) { nil } + + it { is_expected.to be_falsey } + end + end + end + end + + describe '#readable_records' do + subject { resource.readable_records(association, current_user: user) } + + before do + group.add_developer(user) + end + + context 'when association not supported' do + let(:association) { :foo } + + it { is_expected.to be_nil } + end + + context 'when association is `:notes`' do + let(:association) { :notes } + + it { is_expected.to match_array([readable_note]) } + + context 'when user have access' do + before do + other_group.add_developer(user) + end + + it 'returns all records' do + is_expected.to match_array([readable_note, restricted_note]) + end + end + end + end +end diff --git a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb index 3d7d97bbeae..ac4ad4525aa 100644 --- a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb +++ b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| - let_it_be(:distribution_with_suite, freeze: can_freeze) { create(factory, suite: 'mysuite') } + let_it_be(:distribution_with_suite, freeze: can_freeze) { create(factory, :with_suite) } let_it_be(:distribution_with_same_container, freeze: can_freeze) { create(factory, container: distribution_with_suite.container ) } let_it_be(:distribution_with_same_codename, freeze: can_freeze) { create(factory, codename: distribution_with_suite.codename ) } let_it_be(:distribution_with_same_suite, freeze: can_freeze) { create(factory, suite: distribution_with_suite.suite ) } diff --git a/spec/support/shared_examples/models/resource_event_shared_examples.rb b/spec/support/shared_examples/models/resource_event_shared_examples.rb index 8cab2de076d..038ff33c68a 100644 --- a/spec/support/shared_examples/models/resource_event_shared_examples.rb +++ b/spec/support/shared_examples/models/resource_event_shared_examples.rb @@ -160,6 +160,16 @@ RSpec.shared_examples 'a resource event for merge requests' do end end end + + context 'on callbacks' do + it 'does not trigger note created subscription' do + event = build(described_class.name.underscore.to_sym, merge_request: merge_request1) + + expect(GraphqlTriggers).not_to receive(:work_item_note_created) + expect(event).not_to receive(:trigger_note_subscription_create) + event.save! + end + end end RSpec.shared_examples 'a note for work item resource event' do @@ -172,4 +182,14 @@ RSpec.shared_examples 'a note for work item resource event' do expect(event.work_item_synthetic_system_note.class.name).to eq(event.synthetic_note_class.name) end + + context 'on callbacks' do + it 'triggers note created subscription' do + event = build(described_class.name.underscore.to_sym, issue: work_item) + + expect(GraphqlTriggers).to receive(:work_item_note_created) + expect(event).to receive(:trigger_note_subscription_create).and_call_original + event.save! + end + end end diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb index 73e22b97abc..600539f7d0a 100644 --- a/spec/support/shared_examples/namespaces/traversal_examples.rb +++ b/spec/support/shared_examples/namespaces/traversal_examples.rb @@ -57,11 +57,6 @@ RSpec.shared_examples 'namespace traversal' do end describe '#ancestors' do - before do - # #reload is called to make sure traversal_ids are reloaded - reload_models(group, nested_group, deep_nested_group, very_deep_nested_group) - end - it 'returns the correct ancestors' do expect(very_deep_nested_group.ancestors).to contain_exactly(group, nested_group, deep_nested_group) expect(deep_nested_group.ancestors).to contain_exactly(group, nested_group) diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb index 15d56c402d1..9ec1b8b3f5a 100644 --- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -333,6 +333,7 @@ RSpec.shared_examples 'project policies as admin with admin mode' do expect_disallowed(*team_member_reporter_permissions) expect_allowed(*developer_permissions) expect_allowed(*maintainer_permissions) + expect_allowed(*admin_permissions) expect_allowed(*owner_permissions) end @@ -354,6 +355,7 @@ RSpec.shared_examples 'project policies as admin with admin mode' do expect_disallowed(*team_member_reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) + expect_disallowed(*admin_permissions) expect_disallowed(*owner_permissions) end end diff --git a/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb b/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb index f5431b29ee2..b5704ad8f17 100644 --- a/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb @@ -54,43 +54,3 @@ RSpec.shared_examples 'does not exceed the issuable size limit' do end end end - -RSpec.shared_examples 'does not exceed the issuable size limit with ff off' do - let(:user1) { create(:user) } - let(:user2) { create(:user) } - let(:user3) { create(:user) } - - before do - project.add_maintainer(user) - project.add_maintainer(user1) - project.add_maintainer(user2) - project.add_maintainer(user3) - end - - context 'when feature flag is off' do - before do - stub_feature_flags(feature_flag_hash) - end - - context "when the number of users of issuable does exceed the limit" do - before do - stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 2) - end - - it 'will not add more than the allowed number of users' do - allow_next_instance_of(MergeRequests::UpdateService) do |service| - expect(service).to receive(:execute).and_call_original - end - - note = described_class.new(project, user, opts.merge( - note: note_text, - noteable_type: 'MergeRequest', - noteable_id: issuable.id, - confidential: false - )).execute - - expect(note.errors[:validation]).to be_empty - end - end - end -end diff --git a/spec/support/shared_examples/requests/admin_mode_shared_examples.rb b/spec/support/shared_examples/requests/admin_mode_shared_examples.rb new file mode 100644 index 00000000000..07fde7d3f35 --- /dev/null +++ b/spec/support/shared_examples/requests/admin_mode_shared_examples.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true +RSpec.shared_examples 'GET request permissions for admin mode' do + it_behaves_like 'GET request permissions for admin mode when user' + it_behaves_like 'GET request permissions for admin mode when admin' +end + +RSpec.shared_examples 'PUT request permissions for admin mode' do |params| + it_behaves_like 'PUT request permissions for admin mode when user', params + it_behaves_like 'PUT request permissions for admin mode when admin', params +end + +RSpec.shared_examples 'POST request permissions for admin mode' do |params| + it_behaves_like 'POST request permissions for admin mode when user', params + it_behaves_like 'POST request permissions for admin mode when admin', params +end + +RSpec.shared_examples 'DELETE request permissions for admin mode' do + it_behaves_like 'DELETE request permissions for admin mode when user' + it_behaves_like 'DELETE request permissions for admin mode when admin' +end + +RSpec.shared_examples 'GET request permissions for admin mode when user' do + subject { get api(path, current_user, admin_mode: admin_mode) } + + let_it_be(:current_user) { create(:user) } + + it_behaves_like 'admin mode on', true, :forbidden + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'GET request permissions for admin mode when admin' do + subject { get api(path, current_user, admin_mode: admin_mode) } + + let_it_be(:current_user) { create(:admin) } + + it_behaves_like 'admin mode on', true, :ok + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'PUT request permissions for admin mode when user' do |params| + subject { put api(path, current_user, admin_mode: admin_mode), params: params } + + let_it_be(:current_user) { create(:user) } + + it_behaves_like 'admin mode on', true, :forbidden + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'PUT request permissions for admin mode when admin' do |params| + subject { put api(path, current_user, admin_mode: admin_mode), params: params } + + let_it_be(:current_user) { create(:admin) } + + it_behaves_like 'admin mode on', true, :ok + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'POST request permissions for admin mode when user' do |params| + subject { post api(path, current_user, admin_mode: admin_mode), params: params } + + let_it_be(:current_user) { create(:user) } + + it_behaves_like 'admin mode on', true, :forbidden + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'POST request permissions for admin mode when admin' do |params| + subject { post api(path, current_user, admin_mode: admin_mode), params: params } + + let_it_be(:current_user) { create(:admin) } + + it_behaves_like 'admin mode on', true, :created + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'DELETE request permissions for admin mode when user' do + subject { delete api(path, current_user, admin_mode: admin_mode) } + + let_it_be(:current_user) { create(:user) } + + it_behaves_like 'admin mode on', true, :forbidden + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples 'DELETE request permissions for admin mode when admin' do + subject { delete api(path, current_user, admin_mode: admin_mode) } + + let_it_be(:current_user) { create(:admin) } + + it_behaves_like 'admin mode on', true, :no_content + it_behaves_like 'admin mode on', false, :forbidden +end + +RSpec.shared_examples "admin mode on" do |admin_mode, status| + let_it_be(:admin_mode) { admin_mode } + + it_behaves_like 'returning response status', status +end diff --git a/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb index 2ba42b8e8fa..e0225070986 100644 --- a/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb @@ -15,9 +15,3 @@ RSpec.shared_examples 'rejects Debian access with unknown container id' do |anon end end end - -RSpec.shared_examples 'Debian API FIPS mode' do - context 'when FIPS mode is enabled', :fips_mode do - it_behaves_like 'returning response status', :not_found - end -end diff --git a/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb index f13ac05591c..5cd63c33936 100644 --- a/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb @@ -3,8 +3,6 @@ RSpec.shared_examples 'Debian distributions GET request' do |status, body = nil| and_body = body.nil? ? '' : ' and expected body' - it_behaves_like 'Debian API FIPS mode' - it "returns #{status}#{and_body}" do subject @@ -19,8 +17,6 @@ end RSpec.shared_examples 'Debian distributions PUT request' do |status, body| and_body = body.nil? ? '' : ' and expected body' - it_behaves_like 'Debian API FIPS mode' - if status == :success it 'updates distribution', :aggregate_failures do expect(::Packages::Debian::UpdateDistributionService).to receive(:new).with(distribution, api_params.except(:codename)).and_call_original @@ -53,8 +49,6 @@ end RSpec.shared_examples 'Debian distributions DELETE request' do |status, body| and_body = body.nil? ? '' : ' and expected body' - it_behaves_like 'Debian API FIPS mode' - if status == :success it 'updates distribution', :aggregate_failures do expect { subject } diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb index 14a83d2889b..6d29076da0f 100644 --- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb @@ -3,8 +3,6 @@ RSpec.shared_examples 'Debian packages GET request' do |status, body = nil| and_body = body.nil? ? '' : ' and expected body' - it_behaves_like 'Debian API FIPS mode' - it "returns #{status}#{and_body}" do subject @@ -19,11 +17,8 @@ end RSpec.shared_examples 'Debian packages upload request' do |status, body = nil| and_body = body.nil? ? '' : ' and expected body' - it_behaves_like 'Debian API FIPS mode' - if status == :created it 'creates package files', :aggregate_failures do - expect(::Packages::Debian::FindOrCreateIncomingService).to receive(:new).with(container, user).and_call_original expect(::Packages::Debian::CreatePackageFileService).to receive(:new).with(package: be_a(Packages::Package), current_user: be_an(User), params: be_an(Hash)).and_call_original if file_name.end_with? '.changes' @@ -32,10 +27,23 @@ RSpec.shared_examples 'Debian packages upload request' do |status, body = nil| expect(::Packages::Debian::ProcessChangesWorker).not_to receive(:perform_async) end - expect { subject } + if extra_params[:distribution] + expect(::Packages::Debian::FindOrCreateIncomingService).not_to receive(:new) + expect(::Packages::Debian::ProcessPackageFileWorker).to receive(:perform_async) + + expect { subject } + .to change { container.packages.debian.count }.by(1) + .and not_change { container.packages.debian.where(name: 'incoming').count } + .and change { container.package_files.count }.by(1) + else + expect(::Packages::Debian::FindOrCreateIncomingService).to receive(:new).with(container, user).and_call_original + expect(::Packages::Debian::ProcessPackageFileWorker).not_to receive(:perform_async) + + expect { subject } .to change { container.packages.debian.count }.by(1) .and change { container.packages.debian.where(name: 'incoming').count }.by(1) .and change { container.package_files.count }.by(1) + end expect(response).to have_gitlab_http_status(status) expect(response.media_type).to eq('text/plain') diff --git a/spec/support/shared_examples/requests/api/graphql/ci/sorted_paginated_variables_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/ci/sorted_paginated_variables_shared_examples.rb new file mode 100644 index 00000000000..306310c9e9c --- /dev/null +++ b/spec/support/shared_examples/requests/api/graphql/ci/sorted_paginated_variables_shared_examples.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Requires `current_user`, `data_path`, `variables`, and `pagination_query(params)` bindings +RSpec.shared_examples 'sorted paginated variables' do + subject(:expected_ordered_variables) { ordered_variables.map { |var| var.to_global_id.to_s } } + + context 'when sorted by key ascending' do + let(:ordered_variables) { variables.sort_by(&:key) } + + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :KEY_ASC } + let(:first_param) { 2 } + let(:all_records) { expected_ordered_variables } + end + end + + context 'when sorted by key descending' do + let(:ordered_variables) { variables.sort_by(&:key).reverse } + + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :KEY_DESC } + let(:first_param) { 2 } + let(:all_records) { expected_ordered_variables } + 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 d4af9e570d1..6c8b792bf92 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 @@ -5,7 +5,7 @@ RSpec.shared_examples 'graphql issue list request spec' do let(:fields) do <<~QUERY nodes { - #{all_graphql_fields_for('issues'.classify)} + #{all_graphql_fields_for('issues'.classify, excluded: ['relatedMergeRequests'])} } QUERY end @@ -683,6 +683,28 @@ RSpec.shared_examples 'graphql issue list request spec' do end end + context 'when selecting `related_merge_requests`' do + let(:fields) do + <<~QUERY + nodes { + relatedMergeRequests { + nodes { + id + } + } + } + QUERY + end + + it 'limits the field to 1 execution' do + post_query + + expect_graphql_errors_to_include( + '"relatedMergeRequests" field can be requested only for 1 Issue(s) at a time.' + ) + end + end + it 'includes a web_url' do post_query diff --git a/spec/support/shared_examples/requests/api/graphql/releases_and_group_releases_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/releases_and_group_releases_shared_examples.rb new file mode 100644 index 00000000000..b40cf6daea9 --- /dev/null +++ b/spec/support/shared_examples/requests/api/graphql/releases_and_group_releases_shared_examples.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'correct total count' do + let(:data) { graphql_data.dig(resource_type.to_s, 'releases') } + + before do + create_list(:release, 2, project: project) + + post_query + end + + it 'returns the total count' do + expect(data['count']).to eq(project.releases.count) + end +end + +RSpec.shared_examples 'full access to all repository-related fields' do + describe 'repository-related fields' do + before do + post_query + end + + it 'returns data for fields that are protected in private projects' do + expected_sources = release.sources.map do |s| + { 'url' => s.url } + end + + expected_evidences = release.evidences.map do |e| + { 'sha' => e.sha } + end + expect(data).to eq( + 'tagName' => release.tag, + 'tagPath' => project_tag_path(project, release.tag), + 'name' => release.name, + 'commit' => { + 'sha' => release.commit.sha + }, + 'assets' => { + 'count' => release.assets_count, + 'sources' => { + 'nodes' => expected_sources + } + }, + 'evidences' => { + 'nodes' => expected_evidences + }, + 'links' => { + 'selfUrl' => project_release_url(project, release), + 'openedMergeRequestsUrl' => project_merge_requests_url(project, opened_url_params), + 'mergedMergeRequestsUrl' => project_merge_requests_url(project, merged_url_params), + 'closedMergeRequestsUrl' => project_merge_requests_url(project, closed_url_params), + 'openedIssuesUrl' => project_issues_url(project, opened_url_params), + 'closedIssuesUrl' => project_issues_url(project, closed_url_params) + } + ) + end + end + + it_behaves_like 'correct total count' +end + +RSpec.shared_examples 'no access to any repository-related fields' do + describe 'repository-related fields' do + before do + post_query + end + + it 'does not return data for fields that expose repository information' do + tag_name = release.tag + release_name = release.name + expect(data).to eq( + 'tagName' => tag_name, + 'tagPath' => nil, + 'name' => release_name, + 'commit' => nil, + 'assets' => { + 'count' => release.assets_count(except: [:sources]), + 'sources' => { + 'nodes' => [] + } + }, + 'evidences' => { + 'nodes' => [] + }, + 'links' => { + 'closedIssuesUrl' => nil, + 'closedMergeRequestsUrl' => nil, + 'mergedMergeRequestsUrl' => nil, + 'openedIssuesUrl' => nil, + 'openedMergeRequestsUrl' => nil, + 'selfUrl' => project_release_url(project, release) + } + ) + end + end + + it_behaves_like 'correct total count' +end + +RSpec.shared_examples 'access to editUrl' do + # editUrl is tested separately because its permissions + # are slightly different than other release fields + let(:query) do + graphql_query_for(resource_type, { fullPath: resource.full_path }, + %( + releases { + nodes { + links { + editUrl + } + } + } + )) + end + + before do + post_query + end + + it 'returns editUrl' do + expect(data).to eq( + 'links' => { + 'editUrl' => edit_project_release_url(project, release) + } + ) + end +end + +RSpec.shared_examples 'no access to editUrl' do + let(:query) do + graphql_query_for(resource_type, { fullPath: resource.full_path }, + %( + releases { + nodes { + links { + editUrl + } + } + } + )) + end + + before do + post_query + end + + it 'does not return editUrl' do + expect(data).to eq( + 'links' => { + 'editUrl' => nil + } + ) + end +end + +RSpec.shared_examples 'no access to any release data' do + before do + post_query + end + + it 'returns nil' do + expect(data).to eq(nil) + end +end diff --git a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb index d666a754d9f..f2002de4b55 100644 --- a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb @@ -128,7 +128,8 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| get api(hook_uri, user) expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to include('alert_status' => 'disabled') + + expect(json_response).to include('alert_status' => 'disabled') unless hook.executable? end end @@ -142,10 +143,13 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| get api(hook_uri, user) expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to include( - 'alert_status' => 'temporarily_disabled', - 'disabled_until' => hook.disabled_until.iso8601(3) - ) + + unless hook.executable? + expect(json_response).to include( + 'alert_status' => 'temporarily_disabled', + 'disabled_until' => hook.disabled_until.iso8601(3) + ) + end end end end diff --git a/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb b/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb index fcde3b65b4f..f06a80375e8 100644 --- a/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb @@ -1,40 +1,5 @@ # frozen_string_literal: true -RSpec.shared_examples 'issuable anonymous search' do - context 'with anonymous user' do - context 'with disable_anonymous_search disabled' do - before do - stub_feature_flags(disable_anonymous_search: false) - end - - it 'returns issuables matching given search string for title' do - get api(url), params: { scope: 'all', search: issuable.title } - - expect_paginated_array_response(result) - end - - it 'returns issuables matching given search string for description' do - get api(url), params: { scope: 'all', search: issuable.description } - - expect_paginated_array_response(result) - end - end - - context 'with disable_anonymous_search enabled' do - before do - stub_feature_flags(disable_anonymous_search: true) - end - - it "returns 422 error" do - get api(url), params: { scope: 'all', search: issuable.title } - - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(json_response['message']).to eq('User must be authenticated to use search') - end - end - end -end - RSpec.shared_examples 'issuable API rate-limited search' do it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit do let(:current_user) { user } @@ -49,20 +14,4 @@ RSpec.shared_examples 'issuable API rate-limited search' do get api(url), params: { scope: 'all', search: issuable.title } end end - - context 'when rate_limit_issuable_searches is disabled', :freeze_time, :clean_gitlab_redis_rate_limiting do - before do - stub_feature_flags(rate_limit_issuable_searches: false) - - allow(Gitlab::ApplicationRateLimiter).to receive(:threshold) - .with(:search_rate_limit_unauthenticated).and_return(1) - end - - it 'does not enforce the rate limit' do - get api(url), params: { scope: 'all', search: issuable.title } - get api(url), params: { scope: 'all', search: issuable.title } - - expect(response).to have_gitlab_http_status(:ok) - end - end end diff --git a/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb b/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb index 381583ff2a9..86a1fd76d09 100644 --- a/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb @@ -142,7 +142,7 @@ RSpec.shared_examples 'time tracking endpoints' do |issuable_name| if issuable_name == 'issue' it 'calls update service without :use_specialized_service param' do expect(::Issues::UpdateService).to receive(:new).with( - project: project, + container: project, current_user: user, params: { spend_time: { duration: 3600, summary: 'summary', user_id: user.id } }) diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb index 82ed6eb4c95..3f457890f35 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -382,7 +382,7 @@ RSpec.shared_examples 'tracking when dry-run mode is set' do end def reset_rack_attack - Rack::Attack.reset! + Gitlab::Redis::RateLimiting.with(&:flushdb) Rack::Attack.clear_configuration Gitlab::RackAttack.configure(Rack::Attack) end diff --git a/spec/support/shared_examples/serializers/note_entity_shared_examples.rb b/spec/support/shared_examples/serializers/note_entity_shared_examples.rb index 2e557ca090c..b5e3a407b53 100644 --- a/spec/support/shared_examples/serializers/note_entity_shared_examples.rb +++ b/spec/support/shared_examples/serializers/note_entity_shared_examples.rb @@ -38,6 +38,10 @@ RSpec.shared_examples 'note entity' do expect(subject[:current_user]).to include(:can_edit, :can_award_emoji, :can_resolve, :can_resolve_discussion) end + it 'exposes the report_abuse_path' do + expect(subject[:report_abuse_path]).to eq(add_category_abuse_reports_path) + end + describe ':can_resolve_discussion' do context 'discussion is resolvable' do before do diff --git a/spec/support/shared_examples/services/export_csv/export_csv_invalid_fields_shared_examples.rb b/spec/support/shared_examples/services/export_csv/export_csv_invalid_fields_shared_examples.rb new file mode 100644 index 00000000000..25899f93914 --- /dev/null +++ b/spec/support/shared_examples/services/export_csv/export_csv_invalid_fields_shared_examples.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a service that returns invalid fields from selection' do + describe '#invalid_fields' do + it 'returns invalid fields from selection' do + fields = %w[title invalid_1 invalid_2] + + service = described_class.new(WorkItem.all, project, fields) + + expect(service.invalid_fields).to eq(%w[invalid_1 invalid_2]) + end + end +end diff --git a/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb b/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb index c38ca6a3bf0..4188c4aa56c 100644 --- a/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb @@ -26,7 +26,9 @@ RSpec.shared_examples 'listing issuable discussions' do |user_role, internal_dis discussions = next_page_discussions_service.execute expect(discussions.count).to eq(2) - expect(discussions.first.notes.map(&:note)).to match_array(["added #{label.to_reference} label"]) + expect(discussions.first.notes.map(&:note)).to match_array( + ["added #{label.to_reference} #{label_2.to_reference} labels"] + ) expect(discussions.second.notes.map(&:note)).to match_array(["removed #{label.to_reference} label"]) end end @@ -93,7 +95,15 @@ def create_notes(issuable, note_body) discussion_id: first_discussion.discussion_id, noteable: issuable, project: issuable.project, note: "reply on #{note_body}") - create(:resource_label_event, user: current_user, "#{assoc_name}": issuable, label: label, action: 'add') + now = Time.current + create( + :resource_label_event, + user: current_user, "#{assoc_name}": issuable, label: label, action: 'add', created_at: now + ) + create( + :resource_label_event, + user: current_user, "#{assoc_name}": issuable, label: label_2, action: 'add', created_at: now + ) create(:resource_label_event, user: current_user, "#{assoc_name}": issuable, label: label, action: 'remove') unless issuable.is_a?(Epic) diff --git a/spec/support/shared_examples/services/issuable_shared_examples.rb b/spec/support/shared_examples/services/issuable_shared_examples.rb index 142d4ae8531..fe868d494d2 100644 --- a/spec/support/shared_examples/services/issuable_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable_shared_examples.rb @@ -11,7 +11,8 @@ end RSpec.shared_examples 'updating a single task' do def update_issuable(opts) issuable = try(:issue) || try(:merge_request) - described_class.new(project: project, current_user: user, params: opts).execute(issuable) + described_class.new(**described_class.constructor_container_arg(project), current_user: user, params: opts) + .execute(issuable) end before do diff --git a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb index 4df12f7849b..bdb01b12607 100644 --- a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb +++ b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb @@ -12,27 +12,20 @@ RSpec.shared_examples 'misconfigured dashboard service response' do |status_code end RSpec.shared_examples 'valid dashboard service response for schema' do - file_ref_resolver = proc do |uri| - file = Rails.root.join(uri.path) - raise StandardError, "Ref file #{uri.path} must be json" unless uri.path.ends_with?('.json') - raise StandardError, "File #{file.to_path} doesn't exists" unless file.exist? - - Gitlab::Json.parse(File.read(file)) - end - it 'returns a json representation of the dashboard' do result = service_call expect(result.keys).to contain_exactly(:dashboard, :status) expect(result[:status]).to eq(:success) - validator = JSONSchemer.schema(dashboard_schema, ref_resolver: file_ref_resolver) + schema_path = Rails.root.join('spec/fixtures', dashboard_schema) + validator = JSONSchemer.schema(schema_path) expect(validator.valid?(result[:dashboard].with_indifferent_access)).to be true end end RSpec.shared_examples 'valid dashboard service response' do - let(:dashboard_schema) { Gitlab::Json.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/dashboard.json')) } + let(:dashboard_schema) { 'lib/gitlab/metrics/dashboard/schemas/dashboard.json' } it_behaves_like 'valid dashboard service response for schema' end @@ -76,7 +69,7 @@ RSpec.shared_examples 'dashboard_version contains SHA256 hash of dashboard file end RSpec.shared_examples 'valid embedded dashboard service response' do - let(:dashboard_schema) { Gitlab::Json.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/embedded_dashboard.json')) } + let(:dashboard_schema) { 'lib/gitlab/metrics/dashboard/schemas/embedded_dashboard.json' } it_behaves_like 'valid dashboard service response for schema' end diff --git a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb index ea79dc674a1..a3042ac2e26 100644 --- a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb +++ b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb @@ -4,6 +4,8 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do def check_release_files(expected_release_content) distribution.reload + expect(expected_release_content).not_to include('MD5') + distribution.file.use_file do |file_path| expect(File.read(file_path)).to eq(expected_release_content) end @@ -12,8 +14,10 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do expect(distribution.file_signature).to end_with("\n-----END PGP SIGNATURE-----\n") distribution.signed_file.use_file do |file_path| - expect(File.read(file_path)).to start_with("-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA512\n\n#{expected_release_content}-----BEGIN PGP SIGNATURE-----\n") - expect(File.read(file_path)).to end_with("\n-----END PGP SIGNATURE-----\n") + signed_file_content = File.read(file_path) + expect(signed_file_content).to start_with("-----BEGIN PGP SIGNED MESSAGE-----\nHash:") + expect(signed_file_content).to include("\n\n#{expected_release_content}-----BEGIN PGP SIGNATURE-----\n") + expect(signed_file_content).to end_with("\n-----END PGP SIGNATURE-----\n") end end @@ -45,6 +49,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do expect(component_file.updated_at).to eq(release_date) unless expected_content.nil? + expect(expected_content).not_to include('MD5') component_file.file.use_file do |file_path| expect(File.read(file_path)).to eq(expected_content) end @@ -73,9 +78,9 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do .and change { component_file1.reload.updated_at }.to(current_time.round) package_files = package.package_files.order(id: :asc).preload_debian_file_metadata.to_a - pool_prefix = 'pool/unstable' + pool_prefix = "pool/#{distribution.codename}" pool_prefix += "/#{project.id}" if container_type == :group - pool_prefix += "/p/#{package.name}/#{package.version}" + pool_prefix += "/#{package.name[0]}/#{package.name}/#{package.version}" expected_main_amd64_content = <<~EOF Package: libsample0 Source: #{package.name} @@ -93,7 +98,6 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do Priority: optional Filename: #{pool_prefix}/libsample0_1.2.3~alpha2_amd64.deb Size: 409600 - MD5sum: #{package_files[2].file_md5} SHA256: #{package_files[2].file_sha256} Package: sample-dev @@ -113,7 +117,6 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do Priority: optional Filename: #{pool_prefix}/sample-dev_1.2.3~binary_amd64.deb Size: 409600 - MD5sum: #{package_files[3].file_md5} SHA256: #{package_files[3].file_sha256} EOF @@ -122,7 +125,6 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do Priority: extra Filename: #{pool_prefix}/sample-udeb_1.2.3~alpha2_amd64.udeb Size: 409600 - MD5sum: #{package_files[4].file_md5} SHA256: #{package_files[4].file_sha256} EOF @@ -171,45 +173,26 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do check_component_file(current_time.round, 'contrib', :sources, nil, nil) main_amd64_size = expected_main_amd64_content.length - main_amd64_md5sum = Digest::MD5.hexdigest(expected_main_amd64_content) main_amd64_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_content) contrib_all_size = component_file1.size - contrib_all_md5sum = component_file1.file_md5 contrib_all_sha256 = component_file1.file_sha256 main_amd64_di_size = expected_main_amd64_di_content.length - main_amd64_di_md5sum = Digest::MD5.hexdigest(expected_main_amd64_di_content) main_amd64_di_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_di_content) main_sources_size = expected_main_sources_content.length - main_sources_md5sum = Digest::MD5.hexdigest(expected_main_sources_content) main_sources_sha256 = Digest::SHA256.hexdigest(expected_main_sources_content) expected_release_content = <<~EOF - Codename: unstable + Codename: #{distribution.codename} Date: Sat, 25 Jan 2020 15:17:18 +0000 Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 Acquire-By-Hash: yes Architectures: all amd64 arm64 Components: contrib main - MD5Sum: - #{contrib_all_md5sum} #{contrib_all_size} contrib/binary-all/Packages - d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-all/Packages - d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-amd64/Packages - d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-amd64/Packages - d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-arm64/Packages - d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-arm64/Packages - d41d8cd98f00b204e9800998ecf8427e 0 contrib/source/Sources - d41d8cd98f00b204e9800998ecf8427e 0 main/binary-all/Packages - d41d8cd98f00b204e9800998ecf8427e 0 main/debian-installer/binary-all/Packages - #{main_amd64_md5sum} #{main_amd64_size} main/binary-amd64/Packages - #{main_amd64_di_md5sum} #{main_amd64_di_size} main/debian-installer/binary-amd64/Packages - d41d8cd98f00b204e9800998ecf8427e 0 main/binary-arm64/Packages - d41d8cd98f00b204e9800998ecf8427e 0 main/debian-installer/binary-arm64/Packages - #{main_sources_md5sum} #{main_sources_size} main/source/Sources SHA256: - #{contrib_all_sha256} #{contrib_all_size} contrib/binary-all/Packages + #{contrib_all_sha256} #{contrib_all_size.to_s.rjust(8)} contrib/binary-all/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages @@ -218,12 +201,13 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Sources e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-all/Packages - #{main_amd64_sha256} #{main_amd64_size} main/binary-amd64/Packages - #{main_amd64_di_sha256} #{main_amd64_di_size} main/debian-installer/binary-amd64/Packages + #{main_amd64_sha256} #{main_amd64_size.to_s.rjust(8)} main/binary-amd64/Packages + #{main_amd64_di_sha256} #{main_amd64_di_size.to_s.rjust(8)} main/debian-installer/binary-amd64/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-arm64/Packages - #{main_sources_sha256} #{main_sources_size} main/source/Sources + #{main_sources_sha256} #{main_sources_size.to_s.rjust(8)} main/source/Sources EOF + expected_release_content = "Suite: #{distribution.suite}\n#{expected_release_content}" if distribution.suite check_release_files(expected_release_content) end @@ -247,13 +231,13 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do .and not_change { distribution.component_files.reset.count } expected_release_content = <<~EOF - Codename: unstable + Codename: #{distribution.codename} Date: Sat, 25 Jan 2020 15:17:18 +0000 Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 Acquire-By-Hash: yes - MD5Sum: SHA256: EOF + expected_release_content = "Suite: #{distribution.suite}\n#{expected_release_content}" if distribution.suite check_release_files(expected_release_content) end diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb index e0dd08ec50e..f63693dbf26 100644 --- a/spec/support/shared_examples/services/packages_shared_examples.rb +++ b/spec/support/shared_examples/services/packages_shared_examples.rb @@ -2,7 +2,7 @@ RSpec.shared_examples 'assigns build to package' do context 'with build info' do - let(:job) { create(:ci_build, user: user) } + let(:job) { create(:ci_build, user: user, project: project) } let(:params) { super().merge(build: job) } it 'assigns the pipeline to the package' do diff --git a/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb b/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb index a7e51408032..1383346644a 100644 --- a/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb +++ b/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb @@ -8,7 +8,7 @@ RSpec.shared_examples 'filters by paginated notes' do |event_type| end it 'only returns given notes' do - paginated_notes = { event_type.to_s.pluralize => [double(id: event.id)] } + paginated_notes = { event_type.to_s.pluralize => [double(ids: [event.id])] } notes = described_class.new(event.issue, user, paginated_notes: paginated_notes).execute expect(notes.size).to eq(1) diff --git a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb index 716be8c6210..209be09c807 100644 --- a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb +++ b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb @@ -160,6 +160,21 @@ RSpec.shared_examples_for 'services security ci configuration create service' do end end end + + context 'when the project is empty' do + let(:params) { nil } + let_it_be(:project) { create(:project_empty_repo) } + + it 'returns an error' do + expect { result }.to raise_error { |error| + expect(error).to be_a(Gitlab::Graphql::Errors::MutationError) + expect(error.message).to eq('You must <a target="_blank" rel="noopener noreferrer" ' \ + 'href="http://localhost/help/user/project/repository/index.md' \ + '#add-files-to-a-repository">add at least one file to the repository' \ + '</a> before using Security features.') + } + end + end end end end diff --git a/spec/support/shared_examples/services/updating_mentions_shared_examples.rb b/spec/support/shared_examples/services/updating_mentions_shared_examples.rb index 13a2aa9ddac..0f649173683 100644 --- a/spec/support/shared_examples/services/updating_mentions_shared_examples.rb +++ b/spec/support/shared_examples/services/updating_mentions_shared_examples.rb @@ -15,7 +15,8 @@ RSpec.shared_examples 'updating mentions' do |service_class| def update_mentionable(opts) perform_enqueued_jobs do - service_class.new(project: project, current_user: user, params: opts).execute(mentionable) + service_class.new(**service_class.constructor_container_arg(project), + current_user: user, params: opts).execute(mentionable) end mentionable.reload diff --git a/spec/support/shared_examples/views/themed_layout_examples.rb b/spec/support/shared_examples/views/themed_layout_examples.rb index b6c53dce4cb..599fd141dd7 100644 --- a/spec/support/shared_examples/views/themed_layout_examples.rb +++ b/spec/support/shared_examples/views/themed_layout_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples "a layout which reflects the application theme setting", :themed_layout do +RSpec.shared_examples "a layout which reflects the application theme setting" do context 'as a themed layout' do let(:default_theme_class) { ::Gitlab::Themes.default.css_class } diff --git a/spec/support/shared_examples/work_item_base_types_importer.rb b/spec/support/shared_examples/work_item_base_types_importer.rb index b1011037584..1703d400aea 100644 --- a/spec/support/shared_examples/work_item_base_types_importer.rb +++ b/spec/support/shared_examples/work_item_base_types_importer.rb @@ -15,6 +15,22 @@ RSpec.shared_examples 'work item base types importer' do expect(WorkItems::Type.all).to all(be_valid) end + it 'creates all default widget definitions' do + WorkItems::WidgetDefinition.delete_all + widget_mapping = ::Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter::WIDGETS_FOR_TYPE + + expect { subject }.to change { WorkItems::WidgetDefinition.count }.from(0).to(widget_mapping.values.flatten.count) + + created_widgets = WorkItems::WidgetDefinition.global.map do |widget| + { name: widget.work_item_type.name, type: widget.widget_type } + end + expected_widgets = widget_mapping.flat_map do |type_sym, widget_types| + widget_types.map { |type| { name: ::WorkItems::Type::TYPE_NAMES[type_sym], type: type.to_s } } + end + + expect(created_widgets).to match_array(expected_widgets) + end + it 'upserts base work item types if they already exist' do first_type = WorkItems::Type.first original_name = first_type.name @@ -29,8 +45,34 @@ RSpec.shared_examples 'work item base types importer' do ) end - it 'executes a single INSERT query' do - expect { subject }.to make_queries_matching(/INSERT/, 1) + it 'upserts default widget definitions if they already exist and type changes' do + widget = WorkItems::WidgetDefinition.global.find_by_widget_type(:labels) + + widget.update!(widget_type: :weight) + + expect do + subject + widget.reload + end.to not_change(WorkItems::WidgetDefinition, :count).and( + change { widget.widget_type }.from('weight').to('labels') + ) + end + + it 'does not change default widget definitions if they already exist with changed disabled status' do + widget = WorkItems::WidgetDefinition.global.find_by_widget_type(:labels) + + widget.update!(disabled: true) + + expect do + subject + widget.reload + end.to not_change(WorkItems::WidgetDefinition, :count).and( + not_change { widget.disabled } + ) + end + + it 'executes single INSERT query per types and widget definitions' do + expect { subject }.to make_queries_matching(/INSERT/, 2) end context 'when some base types exist' do @@ -39,10 +81,22 @@ RSpec.shared_examples 'work item base types importer' do end it 'inserts all types and does nothing if some already existed' do - expect { subject }.to make_queries_matching(/INSERT/, 1).and( + expect { subject }.to make_queries_matching(/INSERT/, 2).and( change { WorkItems::Type.count }.by(1) ) expect(WorkItems::Type.count).to eq(WorkItems::Type::BASE_TYPES.count) end end + + context 'when some widget definitions exist' do + before do + WorkItems::WidgetDefinition.limit(1).delete_all + end + + it 'inserts all widget definitions and does nothing if some already existed' do + expect { subject }.to make_queries_matching(/INSERT/, 2).and( + change { WorkItems::WidgetDefinition.count }.by(1) + ) + end + end end diff --git a/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb index ae29b76ee87..e224b71da91 100644 --- a/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb +++ b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb @@ -101,7 +101,7 @@ RSpec.shared_examples 'batched background migrations execution worker' do context 'when the provided database is sharing config' do before do - skip_if_multiple_databases_not_setup + skip_if_multiple_databases_not_setup(:ci) end it 'does nothing' do diff --git a/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb b/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb index ba1bdfa7aa8..abb00efdee8 100644 --- a/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb +++ b/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb @@ -147,5 +147,19 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true| subject.perform(resource.id, 'prune', lease_key, lease_uuid) end end + + context 'eager' do + before do + expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid) + end + + specify do + expect_next_instance_of(Gitlab::GitalyClient::RepositoryService, repository.raw_repository) do |instance| + expect(instance).to receive(:optimize_repository).with(eager: true).and_call_original + end + + subject.perform(resource.id, 'eager', lease_key, lease_uuid) + end + end end end diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb index b9bd3f82f65..171c7ace2d2 100644 --- a/spec/support/webmock.rb +++ b/spec/support/webmock.rb @@ -9,12 +9,26 @@ def webmock_allowed_hosts hosts << URI.parse(ENV['ELASTIC_URL']).host end + if ENV.key?('ZOEKT_INDEX_BASE_URL') + hosts.concat(allowed_host_and_ip(ENV['ZOEKT_INDEX_BASE_URL'])) + end + + if ENV.key?('ZOEKT_SEARCH_BASE_URL') + hosts.concat(allowed_host_and_ip(ENV['ZOEKT_SEARCH_BASE_URL'])) + end + if Gitlab.config.webpack&.dev_server&.enabled hosts << Gitlab.config.webpack.dev_server.host end end.compact.uniq end +def allowed_host_and_ip(url) + host = URI.parse(url).host + ip_address = Addrinfo.ip(host).ip_address + [host, ip_address] +end + def with_net_connect_allowed WebMock.allow_net_connect! yield |