diff options
Diffstat (limited to 'spec/support')
156 files changed, 2433 insertions, 1131 deletions
diff --git a/spec/support/atlassian/jira_connect/schemata.rb b/spec/support/atlassian/jira_connect/schemata.rb index 61e8aa8e15c..73a6833b7cc 100644 --- a/spec/support/atlassian/jira_connect/schemata.rb +++ b/spec/support/atlassian/jira_connect/schemata.rb @@ -11,7 +11,7 @@ module Atlassian schemaVersion pipelineId buildNumber updateSequenceNumber displayName url state issueKeys testInfo references lastUpdated - ), + ), 'properties' => { 'schemaVersion' => schema_version_type, 'pipelineId' => { 'type' => 'string' }, diff --git a/spec/support/banzai/filter_timeout_shared_examples.rb b/spec/support/banzai/filter_timeout_shared_examples.rb new file mode 100644 index 00000000000..1f2ebe6fef6 --- /dev/null +++ b/spec/support/banzai/filter_timeout_shared_examples.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# This shared_example requires the following variables: +# - text: The text to be run through the filter +# +# Usage: +# +# it_behaves_like 'filter timeout' do +# let(:text) { 'some text' } +# end +RSpec.shared_examples 'filter timeout' do + context 'when rendering takes too long' do + let_it_be(:project) { create(:project) } + let_it_be(:context) { { project: project } } + + it 'times out' do + stub_const("Banzai::Filter::TimeoutHtmlPipelineFilter::RENDER_TIMEOUT", 0.1) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:call_with_timeout) do + sleep(0.2) + text + end + end + + expect(Gitlab::RenderTimeout).to receive(:timeout).and_call_original + expect(Gitlab::ErrorTracking).to receive(:track_exception).with( + instance_of(Timeout::Error), + project_id: context[:project].id, + class_name: described_class.name.demodulize + ) + + result = filter(text) + + expect(result.to_html).to eq text + end + end +end diff --git a/spec/support/before_all_adapter.rb b/spec/support/before_all_adapter.rb index 890bdd6a2c4..f4946ff271f 100644 --- a/spec/support/before_all_adapter.rb +++ b/spec/support/before_all_adapter.rb @@ -1,27 +1,44 @@ # frozen_string_literal: true -class BeforeAllAdapter # rubocop:disable Gitlab/NamespacedClass - def self.all_connection_classes - @all_connection_classes ||= [ActiveRecord::Base] + ActiveRecord::Base.descendants.select(&:connection_class?) # rubocop: disable Database/MultipleDatabases - end - - def self.begin_transaction - self.all_connection_classes.each do |connection_class| - connection_class.connection.begin_transaction(joinable: false) +module TestProfBeforeAllAdapter + module MultipleDatabaseAdapter + def self.all_connection_classes + @all_connection_classes ||= [ActiveRecord::Base] + ActiveRecord::Base.descendants.select(&:connection_class?) # rubocop: disable Database/MultipleDatabases end - end - def self.rollback_transaction - self.all_connection_classes.each do |connection_class| - if connection_class.connection.open_transactions.zero? - warn "!!! before_all transaction has been already rollbacked and " \ - "could work incorrectly" - next + def self.begin_transaction + self.all_connection_classes.each do |connection_class| + connection_class.connection.begin_transaction(joinable: false) end + end - connection_class.connection.rollback_transaction + def self.rollback_transaction + self.all_connection_classes.each do |connection_class| + if connection_class.connection.open_transactions.zero? + warn "!!! before_all transaction has been already rollbacked and " \ + "could work incorrectly" + next + end + + connection_class.connection.rollback_transaction + end end end + + # This class is required so we can disable transactions on migration specs + module NoTransactionAdapter + def self.begin_transaction; end + + def self.rollback_transaction; end + end + + def self.default_adapter + MultipleDatabaseAdapter + end + + def self.no_transaction_adapter + NoTransactionAdapter + end end -TestProf::BeforeAll.adapter = ::BeforeAllAdapter +TestProf::BeforeAll.adapter = ::TestProfBeforeAllAdapter.default_adapter diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 57065400220..aea853d1c23 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -16,15 +16,17 @@ Capybara.server_port = ENV['CAPYBARA_PORT'] if ENV['CAPYBARA_PORT'] JSConsoleError = Class.new(StandardError) # Filter out innocuous JS console messages -JS_CONSOLE_FILTER = Regexp.union([ - '"[HMR] Waiting for update signal from WDS..."', - '"[WDS] Hot Module Replacement enabled."', - '"[WDS] Live Reloading enabled."', - 'Download the Vue Devtools extension', - 'Download the Apollo DevTools', - "Unrecognized feature: 'interest-cohort'", - 'Does this page need fixes or improvements?' -]) +JS_CONSOLE_FILTER = Regexp.union( + [ + '"[HMR] Waiting for update signal from WDS..."', + '"[WDS] Hot Module Replacement enabled."', + '"[WDS] Live Reloading enabled."', + 'Download the Vue Devtools extension', + 'Download the Apollo DevTools', + "Unrecognized feature: 'interest-cohort'", + 'Does this page need fixes or improvements?' + ] +) CAPYBARA_WINDOW_SIZE = [1366, 768].freeze diff --git a/spec/support/counter_attribute.rb b/spec/support/counter_attribute.rb index 8bd40b72dcf..44df2df0ea5 100644 --- a/spec/support/counter_attribute.rb +++ b/spec/support/counter_attribute.rb @@ -7,12 +7,15 @@ RSpec.configure do |config| CounterAttributeModel.class_eval do include CounterAttribute + after_initialize { self.allow_package_size_counter = true } + counter_attribute :build_artifacts_size counter_attribute :commit_count + counter_attribute :packages_size, if: ->(instance) { instance.allow_package_size_counter } - attr_accessor :flushed + attr_accessor :flushed, :allow_package_size_counter - counter_attribute_after_flush do |subject| + counter_attribute_after_commit do |subject| subject.flushed = true end end diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb index f866220b919..816caf5f775 100644 --- a/spec/support/cycle_analytics_helpers/test_generation.rb +++ b/spec/support/cycle_analytics_helpers/test_generation.rb @@ -42,17 +42,17 @@ module CycleAnalyticsHelpers end_time = start_time + rand(1..5).days start_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(start_time) { condition_fn[self, data] } + travel_to(start_time) { condition_fn[self, data] } end # Run `before_end_fn` at the midpoint between `start_time` and `end_time` - Timecop.freeze(start_time + (end_time - start_time) / 2) { before_end_fn[self, data] } if before_end_fn + travel_to(start_time + (end_time - start_time) / 2) { before_end_fn[self, data] } if before_end_fn end_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(end_time) { condition_fn[self, data] } + travel_to(end_time) { condition_fn[self, data] } end - Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + travel_to(end_time + 1.day) { post_fn[self, data] } if post_fn end_time - start_time end @@ -74,14 +74,14 @@ module CycleAnalyticsHelpers end_time = rand(1..10).days.from_now start_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(start_time) { condition_fn[self, data] } + travel_to(start_time) { condition_fn[self, data] } end end_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(end_time) { condition_fn[self, data] } + travel_to(end_time) { condition_fn[self, data] } end - Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + travel_to(end_time + 1.day) { post_fn[self, data] } if post_fn # Turn off the stub before checking assertions allow(self).to receive(:project).and_call_original @@ -97,17 +97,17 @@ module CycleAnalyticsHelpers end_time = start_time + rand(1..5).days # Run `before_end_fn` at the midpoint between `start_time` and `end_time` - Timecop.freeze(start_time + (end_time - start_time) / 2) { before_end_fn[self, data] } if before_end_fn + travel_to(start_time + (end_time - start_time) / 2) { before_end_fn[self, data] } if before_end_fn end_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(start_time) { condition_fn[self, data] } + travel_to(start_time) { condition_fn[self, data] } end start_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(end_time) { condition_fn[self, data] } + travel_to(end_time) { condition_fn[self, data] } end - Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + travel_to(end_time + 1.day) { post_fn[self, data] } if post_fn expect(subject[phase].project_median).to be_nil end @@ -122,10 +122,10 @@ module CycleAnalyticsHelpers end_time = rand(1..10).days.from_now end_time_conditions.each_with_index do |(_condition_name, condition_fn), index| - Timecop.freeze(end_time + index.days) { condition_fn[self, data] } + travel_to(end_time + index.days) { condition_fn[self, data] } end - Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + travel_to(end_time + 1.day) { post_fn[self, data] } if post_fn expect(subject[phase].project_median).to be_nil end @@ -139,7 +139,7 @@ module CycleAnalyticsHelpers start_time = Time.now start_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(start_time) { condition_fn[self, data] } + travel_to(start_time) { condition_fn[self, data] } end post_fn[self, data] if post_fn diff --git a/spec/support/database/query_recorder.rb b/spec/support/database/query_recorder.rb index 1050120e528..c0736221af3 100644 --- a/spec/support/database/query_recorder.rb +++ b/spec/support/database/query_recorder.rb @@ -3,7 +3,15 @@ RSpec.configure do |config| # Truncate the query_recorder log file before starting the suite config.before(:suite) do - log_path = Rails.root.join(Gitlab::Database::QueryAnalyzers::QueryRecorder::LOG_FILE) - File.write(log_path, '') if File.exist?(log_path) + log_file = Rails.root.join(Gitlab::Database::QueryAnalyzers::QueryRecorder.log_file) + File.write(log_file, '') if File.exist?(log_file) + File.delete("#{log_file}.gz") if File.exist?("#{log_file}.gz") + end + + config.after(:suite) do + if ENV['CI'] + log_file = Rails.root.join(Gitlab::Database::QueryAnalyzers::QueryRecorder.log_file) + system("gzip #{log_file}") if File.exist?(log_file) + end end end diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb index 24cdbe04fc2..588fe466a42 100644 --- a/spec/support/db_cleaner.rb +++ b/spec/support/db_cleaner.rb @@ -2,7 +2,7 @@ module DbCleaner def all_connection_classes - ::BeforeAllAdapter.all_connection_classes + ::TestProfBeforeAllAdapter::MultipleDatabaseAdapter.all_connection_classes end def delete_from_all_tables!(except: []) @@ -12,7 +12,7 @@ module DbCleaner end def deletion_except_tables - ['work_item_types'] + %w[work_item_types work_item_hierarchy_restrictions] end def setup_database_cleaner diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml index c8af07905c2..750295e16c4 100644 --- a/spec/support/finder_collection_allowlist.yml +++ b/spec/support/finder_collection_allowlist.yml @@ -1,8 +1,10 @@ # Allow list for spec/support/finder_collection.rb -# Permenant excludes +# Permanent excludes # For example: # FooFinder # Reason: It uses a memory backend +- Namespaces::BilledUsersFinder # Reason: There is no need to have anything else besides the ids is current structure +- Namespaces::FreeUserCap::UsersFinder # Reason: There is no need to have anything else besides the count # Temporary excludes (aka TODOs) # For example: diff --git a/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb b/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb index e9a13f7bf63..cef9860fe25 100644 --- a/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb +++ b/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb @@ -30,7 +30,7 @@ RSpec.shared_examples 'a correct instrumented metric query' do |params| end before do - allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) + allow(metric.send(:relation).connection).to receive(:transaction_open?).and_return(false) end it 'has correct generate query' do diff --git a/spec/support/gitlab_stubs/gitlab_ci.yml b/spec/support/gitlab_stubs/gitlab_ci.yml index 94523591765..dbbfe044e35 100644 --- a/spec/support/gitlab_stubs/gitlab_ci.yml +++ b/spec/support/gitlab_stubs/gitlab_ci.yml @@ -12,7 +12,8 @@ variables: description: 'value of KEY_VALUE_VAR' DB_NAME: postgres ENVIRONMENT_VAR: - value: ['env var value', 'env var value2'] + value: 'env var value' + options: ['env var value', 'env var value2'] description: 'env var description' stages: diff --git a/spec/support/helpers/batch_destroy_dependent_associations_helper.rb b/spec/support/helpers/batch_destroy_dependent_associations_helper.rb new file mode 100644 index 00000000000..22170de053b --- /dev/null +++ b/spec/support/helpers/batch_destroy_dependent_associations_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module BatchDestroyDependentAssociationsHelper + private + + def delete_in_batches_regexps(table, column, resource, items, batch_size: 1000) + # rubocop:disable Layout/LineLength + select_query = %r{^SELECT "#{table}".* FROM "#{table}" WHERE.* "#{table}"."#{column}" = #{resource.id}.*ORDER BY "#{table}"."id" ASC LIMIT #{batch_size}} + # rubocop:enable Layout/LineLength + + [select_query] + items.map { |item| %r{^DELETE FROM "#{table}" WHERE "#{table}"."id" = #{item.id}} } + end +end diff --git a/spec/support/helpers/ci/partitioning_helpers.rb b/spec/support/helpers/ci/partitioning_helpers.rb new file mode 100644 index 00000000000..110199a3147 --- /dev/null +++ b/spec/support/helpers/ci/partitioning_helpers.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Ci + module PartitioningHelpers + def stub_current_partition_id(id = Ci::PartitioningTesting::PartitionIdentifiers.ci_testing_partition_id) + allow(::Ci::Pipeline) + .to receive(:current_partition_value) + .and_return(id) + end + end +end diff --git a/spec/support/helpers/content_security_policy_helpers.rb b/spec/support/helpers/content_security_policy_helpers.rb index 230075ead70..7e3de9fd219 100644 --- a/spec/support/helpers/content_security_policy_helpers.rb +++ b/spec/support/helpers/content_security_policy_helpers.rb @@ -4,11 +4,17 @@ module ContentSecurityPolicyHelpers # Expecting 2 calls to current_content_security_policy by default: # 1. call that's being tested # 2. call in ApplicationController - def setup_csp_for_controller(controller_class, csp = ActionDispatch::ContentSecurityPolicy.new, times: 2) + def setup_csp_for_controller( + controller_class, csp = ActionDispatch::ContentSecurityPolicy.new, times: 2, +any_time: false) expect_next_instance_of(controller_class) do |controller| - expect(controller) + if any_time + expect(controller).to receive(:current_content_security_policy).at_least(:once).and_return(csp) + else + expect(controller) .to receive(:current_content_security_policy).exactly(times).times .and_return(csp) + end end end end diff --git a/spec/support/helpers/cookie_helper.rb b/spec/support/helpers/cookie_helper.rb index ea4be12355b..8971c03a5cc 100644 --- a/spec/support/helpers/cookie_helper.rb +++ b/spec/support/helpers/cookie_helper.rb @@ -27,6 +27,12 @@ module CookieHelper page.driver.browser.manage.cookie_named(name) end + def wait_for_cookie_set(name) + wait_for("Complete setting cookie") do + get_cookie(name) + end + end + private def on_a_page? diff --git a/spec/support/helpers/countries_controller_test_helper.rb b/spec/support/helpers/countries_controller_test_helper.rb deleted file mode 100644 index 5d36a29bba7..00000000000 --- a/spec/support/helpers/countries_controller_test_helper.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module CountriesControllerTestHelper - def world_deny_list - ::World::DENYLIST + ::World::JH_MARKET - end -end - -CountriesControllerTestHelper.prepend_mod diff --git a/spec/support/helpers/doc_url_helper.rb b/spec/support/helpers/doc_url_helper.rb index bbff4827c56..f98c16a3cc4 100644 --- a/spec/support/helpers/doc_url_helper.rb +++ b/spec/support/helpers/doc_url_helper.rb @@ -13,7 +13,7 @@ module DocUrlHelper "#{documentation_base_url}/ee/#{path}.html" end - def stub_doc_file_read(file_name: 'index.md', content: ) + def stub_doc_file_read(content:, file_name: 'index.md') expect_file_read(File.join(Rails.root, 'doc', file_name), content: content) end end diff --git a/spec/support/helpers/features/branches_helpers.rb b/spec/support/helpers/features/branches_helpers.rb index 2a50b41cb4e..d4f96718cc0 100644 --- a/spec/support/helpers/features/branches_helpers.rb +++ b/spec/support/helpers/features/branches_helpers.rb @@ -22,10 +22,14 @@ module Spec end def select_branch(branch_name) - find(".js-branch-select").click + ref_selector = '.ref-selector' + find(ref_selector).click + wait_for_requests - page.within("#new-branch-form .dropdown-menu") do - click_link(branch_name) + 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 end end diff --git a/spec/support/helpers/features/invite_members_modal_helper.rb b/spec/support/helpers/features/invite_members_modal_helper.rb index d02ec06d886..47cbd6b5208 100644 --- a/spec/support/helpers/features/invite_members_modal_helper.rb +++ b/spec/support/helpers/features/invite_members_modal_helper.rb @@ -5,7 +5,7 @@ module Spec module Helpers module Features module InviteMembersModalHelper - def invite_member(names, role: 'Guest', expires_at: nil, refresh: true) + def invite_member(names, role: 'Guest', expires_at: nil) click_on 'Invite members' page.within invite_modal_selector do @@ -14,7 +14,23 @@ module Spec submit_invites end - page.refresh if refresh + wait_for_requests + end + + def invite_member_by_email(role) + click_on _('Invite members') + + page.within invite_modal_selector do + choose_options(role, nil) + find(member_dropdown_selector).set('new_email@gitlab.com') + wait_for_requests + + find('.dropdown-item', text: 'Invite "new_email@gitlab.com" by email').click + + submit_invites + + wait_for_requests + end end def input_invites(names) @@ -43,8 +59,6 @@ module Spec choose_options(role, expires_at) submit_invites - - page.refresh end def submit_invites @@ -52,12 +66,7 @@ module Spec end def choose_options(role, expires_at) - unless role == 'Guest' - click_button 'Guest' - wait_for_requests - click_button role - end - + select role, from: 'Select a role' fill_in 'YYYY-MM-DD', with: expires_at.strftime('%Y-%m-%d') if expires_at end diff --git a/spec/support/helpers/features/runners_helpers.rb b/spec/support/helpers/features/runners_helpers.rb index 63fc628358c..c5d26108953 100644 --- a/spec/support/helpers/features/runners_helpers.rb +++ b/spec/support/helpers/features/runners_helpers.rb @@ -50,7 +50,7 @@ module Spec page.within(search_bar_selector) do click_on filter - # For OPERATOR_IS_ONLY, clicking the filter + # For OPERATORS_IS, clicking the filter # immediately preselects "=" operator page.find('input').send_keys(value) diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb index 278dc79e1d0..20c104cd85c 100644 --- a/spec/support/helpers/gitaly_setup.rb +++ b/spec/support/helpers/gitaly_setup.rb @@ -205,7 +205,7 @@ module GitalySetup # This code needs to work in an environment where we cannot use bundler, # so we cannot easily use the toml-rb gem. This ad-hoc parser should be # good enough. - config_text = IO.read(toml) + config_text = File.read(toml) config_text.lines.each do |line| match_data = line.match(/^\s*(socket_path|listen_addr)\s*=\s*"([^"]*)"$/) diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index bd0efc96bd8..2176a477371 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -859,7 +859,7 @@ module GraphqlHelpers # A lookahead that selects everything def positive_lookahead - double(selects?: true).tap do |selection| + double(selected?: true, selects?: true).tap do |selection| allow(selection).to receive(:selection).and_return(selection) allow(selection).to receive(:selections).and_return(selection) allow(selection).to receive(:map).and_return(double(include?: true)) @@ -868,7 +868,7 @@ module GraphqlHelpers # A lookahead that selects nothing def negative_lookahead - double(selects?: false).tap do |selection| + double(selected?: false, selects?: false, selections: []).tap do |selection| allow(selection).to receive(:selection).and_return(selection) end end diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index 32e6e8d50bd..40eb46878ad 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -3,12 +3,14 @@ require 'action_dispatch/testing/test_request' require 'fileutils' require 'graphlyte' +require 'active_support/testing/time_helpers' require_relative '../../../lib/gitlab/popen' module JavaScriptFixturesHelpers extend ActiveSupport::Concern include Gitlab::Popen + include ActiveSupport::Testing::TimeHelpers extend self @@ -22,7 +24,7 @@ module JavaScriptFixturesHelpers # pick an arbitrary date from the past, so tests are not time dependent # Also see spec/frontend/__helpers__/fake_date/jest.js - Timecop.freeze(Time.utc(2015, 7, 3, 10)) { example.run } + travel_to(Time.utc(2015, 7, 3, 10)) { example.run } 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/listbox_input_helper.rb b/spec/support/helpers/listbox_input_helper.rb new file mode 100644 index 00000000000..ca7fbac5daa --- /dev/null +++ b/spec/support/helpers/listbox_input_helper.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ListboxInputHelper + include WaitForRequests + + def listbox_input(value, from:) + open_listbox_input(from) do + find('[role="option"]', text: value).click + end + end + + def open_listbox_input(selector) + page.within(selector) do + page.find('button[aria-haspopup="listbox"]').click + yield + end + end +end diff --git a/spec/support/helpers/migrations_helpers/work_item_types_helper.rb b/spec/support/helpers/migrations_helpers/work_item_types_helper.rb index b05caf265ee..40f84486537 100644 --- a/spec/support/helpers/migrations_helpers/work_item_types_helper.rb +++ b/spec/support/helpers/migrations_helpers/work_item_types_helper.rb @@ -2,26 +2,9 @@ module MigrationHelpers module WorkItemTypesHelper - DEFAULT_WORK_ITEM_TYPES = { - issue: { name: 'Issue', icon_name: 'issue-type-issue', enum_value: 0 }, - incident: { name: 'Incident', icon_name: 'issue-type-incident', enum_value: 1 }, - test_case: { name: 'Test Case', icon_name: 'issue-type-test-case', enum_value: 2 }, - requirement: { name: 'Requirement', icon_name: 'issue-type-requirements', enum_value: 3 }, - task: { name: 'Task', icon_name: 'issue-type-task', enum_value: 4 } - }.freeze - def reset_work_item_types - work_item_types_table.delete_all - - DEFAULT_WORK_ITEM_TYPES.each do |type, attributes| - work_item_types_table.create!(base_type: attributes[:enum_value], **attributes.slice(:name, :icon_name)) - end - end - - private - - def work_item_types_table - table(:work_item_types) + Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types + Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions end end end diff --git a/spec/support/helpers/project_template_test_helper.rb b/spec/support/helpers/project_template_test_helper.rb index eab41f6a1cf..bedbb8601e8 100644 --- a/spec/support/helpers/project_template_test_helper.rb +++ b/spec/support/helpers/project_template_test_helper.rb @@ -3,14 +3,14 @@ module ProjectTemplateTestHelper def all_templates %w[ - rails spring express iosswift dotnetcore android - gomicro gatsby hugo jekyll plainhtml gitbook - hexo middleman gitpod_spring_petclinic nfhugo - nfjekyll nfplainhtml nfgitbook nfhexo salesforcedx - serverless_framework tencent_serverless_framework - jsonnet cluster_management kotlin_native_linux - pelican - ] + rails spring express iosswift dotnetcore android + gomicro gatsby hugo jekyll plainhtml gitbook + hexo middleman gitpod_spring_petclinic nfhugo + nfjekyll nfplainhtml nfgitbook nfhexo salesforcedx + serverless_framework tencent_serverless_framework + jsonnet cluster_management kotlin_native_linux + pelican bridgetown typo3_distribution + ] end end diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb index e76a1dd5a74..9f37cf61cc9 100644 --- a/spec/support/helpers/repo_helpers.rb +++ b/spec/support/helpers/repo_helpers.rb @@ -137,4 +137,28 @@ eos file_content: content ).execute end + + def create_and_delete_files(project, files, &block) + files.each do |filename, content| + project.repository.create_file( + project.creator, + filename, + content, + message: "Automatically created file #{filename}", + branch_name: project.default_branch_or_main + ) + end + + yield + + ensure + files.each do |filename, _content| + project.repository.delete_file( + project.creator, + filename, + message: "Automatically deleted file #{filename}", + branch_name: project.default_branch_or_main + ) + end + end end diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb index 7d0f8c09933..eab30be9243 100644 --- a/spec/support/helpers/search_helpers.rb +++ b/spec/support/helpers/search_helpers.rb @@ -35,6 +35,8 @@ module SearchHelpers def select_search_scope(scope) page.within '[data-testid="search-filter"]' do click_link scope + + wait_for_all_requests end end diff --git a/spec/support/helpers/service_desk_helper.rb b/spec/support/helpers/service_desk_helper.rb new file mode 100644 index 00000000000..d67ee5b8a11 --- /dev/null +++ b/spec/support/helpers/service_desk_helper.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module ServiceDeskHelper + def set_template_file(file_name, content) + file_path = ".gitlab/issue_templates/#{file_name}.md" + project.repository.create_file(user, file_path, content, message: 'message', branch_name: 'master') + settings.update!(issue_template_key: file_name) + end +end diff --git a/spec/support/helpers/smime_helper.rb b/spec/support/helpers/smime_helper.rb index fa16c433c6b..1a414c72fbb 100644 --- a/spec/support/helpers/smime_helper.rb +++ b/spec/support/helpers/smime_helper.rb @@ -17,7 +17,7 @@ module SmimeHelper end # returns a hash { key:, cert: } containing a generated key, cert pair - def issue(email_address: 'test@example.com', cn: nil, signed_by:, expires_in:, certificate_authority:) + def issue(signed_by:, expires_in:, certificate_authority:, email_address: 'test@example.com', cn: nil) key = OpenSSL::PKey::RSA.new(4096) public_key = key.public_key diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index 24c768258a1..4ca8f26be9e 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -20,6 +20,17 @@ module StubConfiguration allow_any_instance_of(ApplicationSetting).to receive(:cached_html_up_to_date?).and_return(false) end + # For enums with `_prefix: true`, this allows us to stub the application setting properly + def stub_application_setting_enum(setting, value) + stub_application_setting(setting.to_sym => value) + + ApplicationSetting.send(setting.pluralize.to_sym).each_key do |key| + stub_application_setting("#{setting}_#{key}".to_sym => key == value) + end + + Gitlab::CurrentSettings.send(setting) + end + def stub_not_protect_default_branch stub_application_setting( default_branch_protection: Gitlab::Access::PROTECTION_NONE) diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb index 87e2a71b1cd..c163ce1d880 100644 --- a/spec/support/helpers/stub_object_storage.rb +++ b/spec/support/helpers/stub_object_storage.rb @@ -12,7 +12,6 @@ module StubObjectStorage uploader:, enabled: true, proxy_download: false, - background_upload: false, direct_upload: false, cdn: {} ) @@ -20,7 +19,6 @@ module StubObjectStorage new_config = config.to_h.deep_symbolize_keys.merge({ enabled: enabled, proxy_download: proxy_download, - background_upload: background_upload, direct_upload: direct_upload, cdn: cdn }) @@ -30,7 +28,6 @@ module StubObjectStorage allow(config).to receive(:to_h).and_return(new_config) allow(config).to receive(:enabled) { enabled } allow(config).to receive(:proxy_download) { proxy_download } - allow(config).to receive(:background_upload) { background_upload } allow(config).to receive(:direct_upload) { direct_upload } uploader_config = Settingslogic.new(new_config.deep_stringify_keys) diff --git a/spec/support/helpers/stub_snowplow.rb b/spec/support/helpers/stub_snowplow.rb index 85c605efea3..80342863f7b 100644 --- a/spec/support/helpers/stub_snowplow.rb +++ b/spec/support/helpers/stub_snowplow.rb @@ -10,7 +10,7 @@ module StubSnowplow # rubocop:disable RSpec/AnyInstanceOf allow_any_instance_of(Gitlab::Tracking::Destinations::Snowplow) .to receive(:emitter) - .and_return(SnowplowTracker::Emitter.new(host, buffer_size: buffer_size)) + .and_return(SnowplowTracker::Emitter.new(endpoint: host, options: { buffer_size: buffer_size })) # rubocop:enable RSpec/AnyInstanceOf stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: host) diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index e1b461cf37e..3530d1b1a39 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -91,7 +91,8 @@ module TestEnv 'utf-16' => 'f05a987', 'gitaly-rename-test' => '94bb47c', 'smime-signed-commits' => 'ed775cc', - 'Ääh-test-utf-8' => '7975be0' + 'Ääh-test-utf-8' => '7975be0', + 'ssh-signed-commit' => '7b5160f' }.freeze # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily @@ -312,12 +313,6 @@ module TestEnv end end - def storage_dir_exists?(storage, dir) - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - File.exist?(File.join(GitalySetup.repos_path(storage), dir)) - end - end - def repos_path @repos_path ||= GitalySetup.repos_path end @@ -375,6 +370,7 @@ module TestEnv def seed_db Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types + Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions end private @@ -427,6 +423,8 @@ module TestEnv return if File.exist?(install_dir) && ci? if component_needs_update?(install_dir, version) + puts "==> Starting #{component} set up...\n" + # Cleanup the component entirely to ensure we start fresh FileUtils.rm_rf(install_dir) if fresh_install @@ -486,12 +484,14 @@ module TestEnv # The HEAD of the component_folder will be used as heuristic for the version # of the binaries, allowing to use Git to determine if HEAD is later than # the expected version. Note: Git considers HEAD to be an anchestor of HEAD. - _out, exit_status = Gitlab::Popen.popen(%W[ - #{Gitlab.config.git.bin_path} - -C #{component_folder} - merge-base --is-ancestor - #{expected_version} HEAD -]) + _out, exit_status = Gitlab::Popen.popen( + %W[ + #{Gitlab.config.git.bin_path} + -C #{component_folder} + merge-base --is-ancestor + #{expected_version} HEAD + ] + ) exit_status == 0 end diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index 92a946db337..78ceaf297a8 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -2,124 +2,118 @@ module UsageDataHelpers COUNTS_KEYS = %i( - assignee_lists - ci_builds - ci_internal_pipelines - ci_external_pipelines - ci_pipeline_config_auto_devops - ci_pipeline_config_repository - ci_runners - ci_triggers - ci_pipeline_schedules - auto_devops_enabled - auto_devops_disabled - deploy_keys - deployments - successful_deployments - failed_deployments - environments - clusters - clusters_enabled - project_clusters_enabled - group_clusters_enabled - instance_clusters_enabled - clusters_disabled - project_clusters_disabled - group_clusters_disabled - instance_clusters_disabled - clusters_platforms_eks - clusters_platforms_gke - clusters_platforms_user - clusters_integrations_prometheus - clusters_management_project - in_review_folder - grafana_integrated_projects - groups - issues - issues_created_from_gitlab_error_tracking_ui - issues_with_associated_zoom_link - issues_using_zoom_quick_actions - issues_with_embedded_grafana_charts_approx - incident_issues - keys - label_lists - labels - lfs_objects - merge_requests - milestone_lists - milestones - notes - pool_repositories - projects - projects_imported_from_github - projects_asana_active - projects_jenkins_active - projects_jira_active - projects_jira_server_active - projects_jira_cloud_active - projects_jira_dvcs_cloud_active - projects_jira_dvcs_server_active - projects_slack_active - projects_slack_slash_commands_active - projects_custom_issue_tracker_active - projects_mattermost_active - projects_prometheus_active - projects_with_repositories_enabled - projects_with_error_tracking_enabled - projects_with_enabled_alert_integrations - projects_with_expiration_policy_enabled_with_older_than_unset - projects_with_expiration_policy_enabled_with_older_than_set_to_7d - projects_with_expiration_policy_enabled_with_older_than_set_to_14d - projects_with_expiration_policy_enabled_with_older_than_set_to_30d - projects_with_expiration_policy_enabled_with_older_than_set_to_60d - projects_with_expiration_policy_enabled_with_older_than_set_to_90d - projects_with_terraform_reports - projects_with_terraform_states - pages_domains - protected_branches - protected_branches_except_default - releases - remote_mirrors - snippets - personal_snippets - project_snippets - suggestions - terraform_reports - terraform_states - todos - uploads - web_hooks - user_preferences_user_gitpod_enabled - ).freeze + assignee_lists + ci_builds + ci_internal_pipelines + ci_external_pipelines + ci_pipeline_config_auto_devops + ci_pipeline_config_repository + ci_runners + ci_triggers + ci_pipeline_schedules + auto_devops_enabled + auto_devops_disabled + deploy_keys + deployments + successful_deployments + failed_deployments + environments + clusters + clusters_enabled + project_clusters_enabled + group_clusters_enabled + instance_clusters_enabled + clusters_disabled + project_clusters_disabled + group_clusters_disabled + instance_clusters_disabled + clusters_platforms_eks + clusters_platforms_gke + clusters_platforms_user + clusters_integrations_prometheus + clusters_management_project + in_review_folder + grafana_integrated_projects + groups + issues + issues_created_from_gitlab_error_tracking_ui + issues_with_associated_zoom_link + issues_using_zoom_quick_actions + issues_with_embedded_grafana_charts_approx + incident_issues + keys + label_lists + labels + lfs_objects + merge_requests + milestone_lists + milestones + notes + pool_repositories + projects + projects_imported_from_github + projects_asana_active + projects_jenkins_active + projects_jira_active + projects_jira_server_active + projects_jira_cloud_active + projects_jira_dvcs_cloud_active + projects_jira_dvcs_server_active + projects_slack_active + projects_slack_slash_commands_active + projects_custom_issue_tracker_active + projects_mattermost_active + projects_prometheus_active + projects_with_repositories_enabled + projects_with_error_tracking_enabled + projects_with_enabled_alert_integrations + projects_with_terraform_reports + projects_with_terraform_states + pages_domains + protected_branches + protected_branches_except_default + releases + remote_mirrors + snippets + personal_snippets + project_snippets + suggestions + terraform_reports + terraform_states + todos + uploads + web_hooks + user_preferences_user_gitpod_enabled + ).freeze USAGE_DATA_KEYS = %i( - active_user_count - counts - counts_monthly - recorded_at - edition - version - installation_type - uuid - hostname - mattermost_enabled - signup_enabled - ldap_enabled - gravatar_enabled - omniauth_enabled - reply_by_email_enabled - container_registry_enabled - dependency_proxy_enabled - gitlab_shared_runners_enabled - gitlab_pages - git - gitaly - database - prometheus_metrics_enabled - web_ide_clientside_preview_enabled - object_store - topology - ).freeze + active_user_count + counts + counts_monthly + recorded_at + edition + version + installation_type + uuid + hostname + mattermost_enabled + signup_enabled + ldap_enabled + gravatar_enabled + omniauth_enabled + reply_by_email_enabled + container_registry_enabled + dependency_proxy_enabled + gitlab_shared_runners_enabled + gitlab_pages + git + gitaly + database + prometheus_metrics_enabled + web_ide_clientside_preview_enabled + object_store + topology + ).freeze def stub_usage_data_connections allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) @@ -162,7 +156,6 @@ module UsageDataHelpers 'direct_upload' => true, 'connection' => { 'provider' => 'AWS', 'aws_access_key_id' => 'minio', 'aws_secret_access_key' => 'gdk-minio', 'region' => 'gdk', 'endpoint' => 'http://127.0.0.1:9000', 'path_style' => true }, - 'background_upload' => false, 'proxy_download' => false } } ) @@ -177,7 +170,6 @@ module UsageDataHelpers 'direct_upload' => true, 'connection' => { 'provider' => 'AWS', 'aws_access_key_id' => 'minio', 'aws_secret_access_key' => 'gdk-minio', 'region' => 'gdk', 'endpoint' => 'http://127.0.0.1:9000', 'path_style' => true }, - 'background_upload' => false, 'proxy_download' => false } } ) allow(Settings).to receive(:[]).with('uploads') @@ -188,7 +180,6 @@ module UsageDataHelpers 'direct_upload' => true, 'connection' => { 'provider' => 'AWS', 'aws_access_key_id' => 'minio', 'aws_secret_access_key' => 'gdk-minio', 'region' => 'gdk', 'endpoint' => 'http://127.0.0.1:9000', 'path_style' => true }, - 'background_upload' => false, 'proxy_download' => false } } ) allow(Settings).to receive(:[]).with('packages') @@ -200,7 +191,6 @@ module UsageDataHelpers 'direct_upload' => false, 'connection' => { 'provider' => 'AWS', 'aws_access_key_id' => 'minio', 'aws_secret_access_key' => 'gdk-minio', 'region' => 'gdk', 'endpoint' => 'http://127.0.0.1:9000', 'path_style' => true }, - 'background_upload' => true, 'proxy_download' => false } } ) end diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb index 6f22df9ae0f..f894aff373c 100644 --- a/spec/support/helpers/workhorse_helpers.rb +++ b/spec/support/helpers/workhorse_helpers.rb @@ -39,11 +39,11 @@ module WorkhorseHelpers # workhorse_finalize will transform file_key inside params as if it was the finalize call of an inline object storage upload. # note that based on the content of the params it can simulate a disc acceleration or an object storage upload - def workhorse_finalize(url, method: :post, file_key:, params:, headers: {}, send_rewritten_field: false) + def workhorse_finalize(url, file_key:, params:, method: :post, headers: {}, send_rewritten_field: false) workhorse_finalize_with_multiple_files(url, method: method, file_keys: file_key, params: params, headers: headers, send_rewritten_field: send_rewritten_field) end - def workhorse_finalize_with_multiple_files(url, method: :post, file_keys:, params:, headers: {}, send_rewritten_field: false) + def workhorse_finalize_with_multiple_files(url, file_keys:, params:, method: :post, headers: {}, send_rewritten_field: false) workhorse_request_with_multiple_files(method, url, file_keys: file_keys, params: params, @@ -52,11 +52,11 @@ module WorkhorseHelpers ) end - def workhorse_request_with_file(method, url, file_key:, params:, env: {}, extra_headers: {}, send_rewritten_field:) + def workhorse_request_with_file(method, url, file_key:, params:, send_rewritten_field:, env: {}, extra_headers: {}) workhorse_request_with_multiple_files(method, url, file_keys: file_key, params: params, env: env, extra_headers: extra_headers, send_rewritten_field: send_rewritten_field) end - def workhorse_request_with_multiple_files(method, url, file_keys:, params:, env: {}, extra_headers: {}, send_rewritten_field:) + def workhorse_request_with_multiple_files(method, url, file_keys:, params:, send_rewritten_field:, env: {}, extra_headers: {}) workhorse_params = params.dup file_keys = Array(file_keys) diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb index 9da151895a7..3d7a0d29e71 100644 --- a/spec/support/import_export/common_util.rb +++ b/spec/support/import_export/common_util.rb @@ -83,7 +83,7 @@ module ImportExport path = File.join(dir_path, "#{exportable_path}.json") return unless File.exist?(path) - Gitlab::Json.parse(IO.read(path)) + Gitlab::Json.parse(File.read(path)) end def consume_relations(dir_path, exportable_path, key) @@ -101,7 +101,7 @@ module ImportExport end def project_json(filename) - Gitlab::Json.parse(IO.read(filename)) + Gitlab::Json.parse(File.read(filename)) end end end diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb index 3134e5c32a3..9a26f50903f 100644 --- a/spec/support/import_export/export_file_helper.rb +++ b/spec/support/import_export/export_file_helper.rb @@ -117,8 +117,8 @@ module ExportFileHelper # Check whether this is a hash attribute inside a model if model.is_a?(Symbol) return true if (safe_hashes[model] - parent.keys).empty? - else - return true if safe_model?(model, excluded_attributes, parent) + elsif safe_model?(model, excluded_attributes, parent) + return true end end diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb index 6d7658b7c33..a5a017828b3 100644 --- a/spec/support/matchers/exceed_query_limit.rb +++ b/spec/support/matchers/exceed_query_limit.rb @@ -63,14 +63,16 @@ module ExceedQueryLimitHelpers end end - MARGINALIA_ANNOTATION_REGEX = %r{\s*\/\*.*\*\/}.freeze - - DB_QUERY_RE = Regexp.union([ - /^(?<prefix>SELECT .* FROM "?[a-z_]+"?) (?<suffix>.*)$/m, - /^(?<prefix>UPDATE "?[a-z_]+"?) (?<suffix>.*)$/m, - /^(?<prefix>INSERT INTO "[a-z_]+" \((?:"[a-z_]+",?\s?)+\)) (?<suffix>.*)$/m, - /^(?<prefix>DELETE FROM "[a-z_]+") (?<suffix>.*)$/m - ]).freeze + MARGINALIA_ANNOTATION_REGEX = %r{\s*/\*.*\*/}.freeze + + DB_QUERY_RE = Regexp.union( + [ + /^(?<prefix>SELECT .* FROM "?[a-z_]+"?) (?<suffix>.*)$/m, + /^(?<prefix>UPDATE "?[a-z_]+"?) (?<suffix>.*)$/m, + /^(?<prefix>INSERT INTO "[a-z_]+" \((?:"[a-z_]+",?\s?)+\)) (?<suffix>.*)$/m, + /^(?<prefix>DELETE FROM "[a-z_]+") (?<suffix>.*)$/m + ] + ).freeze def with_threshold(threshold) @threshold = threshold diff --git a/spec/support/memory_instrumentation_helper.rb b/spec/support/memory_instrumentation_helper.rb index 84ec02fa5aa..51506376a75 100644 --- a/spec/support/memory_instrumentation_helper.rb +++ b/spec/support/memory_instrumentation_helper.rb @@ -5,13 +5,10 @@ # This concept is currently tried to be upstreamed here: # - https://github.com/ruby/ruby/pull/3978 module MemoryInstrumentationHelper - def skip_memory_instrumentation! + def verify_memory_instrumentation_available! return if ::Gitlab::Memory::Instrumentation.available? - # if we are running in CI, a test cannot be skipped - return if ENV['CI'] - - skip 'Missing a memory instrumentation patch. ' \ + raise 'Ruby is missing a required patch that enables memory instrumentation. ' \ 'More information can be found here: https://gitlab.com/gitlab-org/gitlab/-/issues/296530.' end end diff --git a/spec/support/migration.rb b/spec/support/migration.rb index 4d4a293e9ff..b1e75d9c9e2 100644 --- a/spec/support/migration.rb +++ b/spec/support/migration.rb @@ -20,6 +20,14 @@ RSpec.configure do |config| Gitlab::CurrentSettings.clear_in_memory_application_settings! end + config.prepend_before(:all, :migration) do + TestProf::BeforeAll.adapter = ::TestProfBeforeAllAdapter.no_transaction_adapter + end + + config.append_after(:all, :migration) do + TestProf::BeforeAll.adapter = ::TestProfBeforeAllAdapter.default_adapter + end + config.append_after(:context, :migration) do recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables end diff --git a/spec/support/migrations_helpers/vulnerabilities_findings_helper.rb b/spec/support/migrations_helpers/vulnerabilities_findings_helper.rb index a3cccc3a75d..9a5313c3fa4 100644 --- a/spec/support/migrations_helpers/vulnerabilities_findings_helper.rb +++ b/spec/support/migrations_helpers/vulnerabilities_findings_helper.rb @@ -92,10 +92,10 @@ module MigrationHelpers "url" => "http://goat:8080/WebGoat/logout", "body" => "", "headers" => [ - { - "name" => "Accept", - "value" => "*/*" - } + { + "name" => "Accept", + "value" => "*/*" + } ] }, "response" => { diff --git a/spec/support/models/ci/partitioning_testing/cascade_check.rb b/spec/support/models/ci/partitioning_testing/cascade_check.rb index f553a47ef4f..bcfc9675476 100644 --- a/spec/support/models/ci/partitioning_testing/cascade_check.rb +++ b/spec/support/models/ci/partitioning_testing/cascade_check.rb @@ -15,6 +15,13 @@ module PartitioningTesting raise "partition_id was expected to equal #{partition_scope_value} but it was #{partition_id}." end + + class_methods do + # Allowing partition callback to be used with BulkInsertSafe + def _bulk_insert_callback_allowed?(name, args) + super || args.first == :after && args.second == :check_partition_cascade_value + end + 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 712178710da..3a79ed1b5a9 100644 --- a/spec/support/models/ci/partitioning_testing/schema_helpers.rb +++ b/spec/support/models/ci/partitioning_testing/schema_helpers.rb @@ -8,10 +8,10 @@ module Ci module_function def with_routing_tables - Ci::BuildMetadata.table_name = :p_ci_builds_metadata + # model.table_name = :routing_table yield - ensure - Ci::BuildMetadata.table_name = :ci_builds_metadata + # ensure + # model.table_name = :regular_table end # We're dropping the default values here to ensure that the application code diff --git a/spec/support/patches/rspec_mocks_prepended_methods.rb b/spec/support/patches/rspec_mocks_prepended_methods.rb index fa3a74c670c..d51fb37a499 100644 --- a/spec/support/patches/rspec_mocks_prepended_methods.rb +++ b/spec/support/patches/rspec_mocks_prepended_methods.rb @@ -45,7 +45,7 @@ module RSpec private def method_owner - @method_owner ||= Object.instance_method(:method).bind(object).call(@method_name).owner + @method_owner ||= Object.instance_method(:method).bind_call(object, @method_name).owner end end end diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb index 3a5909cd908..e589baf0909 100644 --- a/spec/support/prometheus/additional_metrics_shared_examples.rb +++ b/spec/support/prometheus/additional_metrics_shared_examples.rb @@ -92,15 +92,15 @@ RSpec.shared_examples 'additional metrics query' do metrics: [ { title: 'title', weight: 1, y_label: 'Values', queries: [ - { query_range: 'query_range_a', result: query_range_result }, - { query_range: 'query_range_b', label: 'label', unit: 'unit', result: query_range_result } - ] + { query_range: 'query_range_a', result: query_range_result }, + { query_range: 'query_range_b', label: 'label', unit: 'unit', result: query_range_result } + ] } ] } ] - expect(query_result).to match_schema('prometheus/additional_metrics_query_result') + expect(query_result.to_json).to match_schema('prometheus/additional_metrics_query_result') expect(query_result).to eq(expected) end end @@ -128,7 +128,7 @@ RSpec.shared_examples 'additional metrics query' do queries_with_result_a = { queries: [{ query_range: 'query_range_a', result: query_range_result }] } queries_with_result_b = { queries: [{ query_range: 'query_range_b', result: query_range_result }] } - expect(query_result).to match_schema('prometheus/additional_metrics_query_result') + expect(query_result.to_json).to match_schema('prometheus/additional_metrics_query_result') expect(query_result.count).to eq(2) expect(query_result).to all(satisfy { |r| r[:metrics].count == 1 }) @@ -147,7 +147,7 @@ RSpec.shared_examples 'additional metrics query' do it 'return group data only for query with results' do queries_with_result = { queries: [{ query_range: 'query_range_a', result: query_range_result }] } - expect(query_result).to match_schema('prometheus/additional_metrics_query_result') + expect(query_result.to_json).to match_schema('prometheus/additional_metrics_query_result') expect(query_result.count).to eq(1) expect(query_result).to all(satisfy { |r| r[:metrics].count == 1 }) diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb index 33945509675..0368fd63357 100644 --- a/spec/support/redis/redis_shared_examples.rb +++ b/spec/support/redis/redis_shared_examples.rb @@ -4,6 +4,7 @@ RSpec.shared_examples "redis_shared_examples" do include StubENV let(:test_redis_url) { "redis://redishost:#{redis_port}" } + let(:test_cluster_config) { { cluster: [{ host: "redis://redishost", port: redis_port }] } } let(:config_file_name) { instance_specific_config_file } let(:config_old_format_socket) { "spec/fixtures/config/redis_old_format_socket.yml" } let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" } @@ -11,6 +12,7 @@ RSpec.shared_examples "redis_shared_examples" do let(:new_socket_path) { "/path/to/redis.sock" } let(:config_old_format_host) { "spec/fixtures/config/redis_old_format_host.yml" } let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" } + let(:config_cluster_format_host) { "spec/fixtures/config/redis_cluster_format_host.yml" } let(:redis_port) { 6379 } let(:redis_database) { 99 } let(:sentinel_port) { 26379 } @@ -191,6 +193,30 @@ RSpec.shared_examples "redis_shared_examples" do end end end + + context 'with redis cluster format' do + let(:config_file_name) { config_cluster_format_host } + + where(:rails_env, :host) do + [ + %w[development development-master], + %w[test test-master], + %w[production production-master] + ] + end + + with_them do + it 'returns hash with cluster and password' do + is_expected.to include(password: 'myclusterpassword', + cluster: [ + { host: "#{host}1", port: redis_port }, + { host: "#{host}2", port: redis_port } + ] + ) + is_expected.not_to have_key(:url) + end + end + end end end @@ -317,6 +343,14 @@ RSpec.shared_examples "redis_shared_examples" do expect(subject).to eq(redis_database) end end + + context 'with cluster-mode' do + let(:config_file_name) { config_cluster_format_host } + + it 'returns the correct db' do + expect(subject).to eq(0) + end + end end describe '#sentinels' do @@ -350,6 +384,14 @@ RSpec.shared_examples "redis_shared_examples" do is_expected.to be_nil end end + + context 'when cluster is defined' do + let(:config_file_name) { config_cluster_format_host } + + it 'returns nil' do + is_expected.to be_nil + end + end end describe '#sentinels?' do @@ -370,6 +412,14 @@ RSpec.shared_examples "redis_shared_examples" do is_expected.to be_falsey end end + + context 'when cluster is defined' do + let(:config_file_name) { config_cluster_format_host } + + it 'returns false' do + is_expected.to be_falsey + end + end end describe '#raw_config_hash' do @@ -377,6 +427,11 @@ RSpec.shared_examples "redis_shared_examples" do expect(subject).to receive(:fetch_config) { test_redis_url } expect(subject.send(:raw_config_hash)).to eq(url: test_redis_url) end + + it 'returns cluster config without url key in a hash' do + expect(subject).to receive(:fetch_config) { test_cluster_config } + expect(subject.send(:raw_config_hash)).to eq(test_cluster_config) + end end describe '#fetch_config' do diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb index 71dfc3fd5a3..ff0b5bebe33 100644 --- a/spec/support/rspec.rb +++ b/spec/support/rspec.rb @@ -8,6 +8,8 @@ require_relative "helpers/stub_object_storage" require_relative "helpers/stub_env" require_relative "helpers/fast_rails_root" +require_relative "../../lib/gitlab/utils" + RSpec::Expectations.configuration.on_potential_false_positives = :raise RSpec.configure do |config| @@ -35,4 +37,13 @@ RSpec.configure do |config| config.include StubObjectStorage config.include StubENV config.include FastRailsRoot + + warn_missing_feature_category = Gitlab::Utils.to_boolean(ENV['RSPEC_WARN_MISSING_FEATURE_CATEGORY'], default: true) + + # Add warning for example missing feature_category + config.before do |example| + if warn_missing_feature_category && example.metadata[:feature_category].blank? && !ENV['CI'] + warn "Missing metadata feature_category: #{example.location} See https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#feature-category-metadata" + end + end end diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 67b7023f1ff..489ed89c048 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -5,9 +5,6 @@ # --- - './ee/spec/components/billing/plan_component_spec.rb' -- './ee/spec/components/namespaces/free_user_cap/alert_component_spec.rb' -- './ee/spec/components/namespaces/free_user_cap/preview_alert_component_spec.rb' -- './ee/spec/components/namespaces/free_user_cap/preview_usage_quota_alert_component_spec.rb' - './ee/spec/components/namespaces/free_user_cap/usage_quota_alert_component_spec.rb' - './ee/spec/components/namespaces/free_user_cap/usage_quota_trial_alert_component_spec.rb' - './ee/spec/components/namespaces/storage/limit_alert_component_spec.rb' @@ -137,8 +134,6 @@ - './ee/spec/controllers/projects/environments_controller_spec.rb' - './ee/spec/controllers/projects/feature_flag_issues_controller_spec.rb' - './ee/spec/controllers/projects/imports_controller_spec.rb' -- './ee/spec/controllers/projects/incident_management/escalation_policies_controller_spec.rb' -- './ee/spec/controllers/projects/incident_management/oncall_schedules_controller_spec.rb' - './ee/spec/controllers/projects/insights_controller_spec.rb' - './ee/spec/controllers/projects/integrations/jira/issues_controller_spec.rb' - './ee/spec/controllers/projects/integrations/zentao/issues_controller_spec.rb' @@ -262,7 +257,6 @@ - './ee/spec/features/boards/user_visits_board_spec.rb' - './ee/spec/features/burndown_charts_spec.rb' - './ee/spec/features/burnup_charts_spec.rb' -- './ee/spec/features/ci/ci_minutes_spec.rb' - './ee/spec/features/ci_shared_runner_settings_spec.rb' - './ee/spec/features/ci_shared_runner_warnings_spec.rb' - './ee/spec/features/clusters/cluster_detail_page_spec.rb' @@ -353,8 +347,6 @@ - './ee/spec/features/groups/wiki/user_views_wiki_empty_spec.rb' - './ee/spec/features/ide/user_commits_changes_spec.rb' - './ee/spec/features/ide/user_opens_ide_spec.rb' -- './ee/spec/features/incidents/incident_details_spec.rb' -- './ee/spec/features/incidents/incidents_list_spec.rb' - './ee/spec/features/integrations/jira/jira_issues_list_spec.rb' - './ee/spec/features/invites_spec.rb' - './ee/spec/features/issues/blocking_issues_spec.rb' @@ -421,7 +413,6 @@ - './ee/spec/features/profiles/account_spec.rb' - './ee/spec/features/profiles/billing_spec.rb' - './ee/spec/features/profiles/password_spec.rb' -- './ee/spec/features/profiles/usage_quotas_spec.rb' - './ee/spec/features/profiles/user_visits_public_profile_spec.rb' - './ee/spec/features/projects/active_tabs_spec.rb' - './ee/spec/features/projects/audit_events_spec.rb' @@ -592,13 +583,6 @@ - './ee/spec/finders/group_projects_finder_spec.rb' - './ee/spec/finders/group_saml_identity_finder_spec.rb' - './ee/spec/finders/groups_with_templates_finder_spec.rb' -- './ee/spec/finders/incident_management/escalation_policies_finder_spec.rb' -- './ee/spec/finders/incident_management/escalation_rules_finder_spec.rb' -- './ee/spec/finders/incident_management/issuable_resource_links_finder_spec.rb' -- './ee/spec/finders/incident_management/member_oncall_rotations_finder_spec.rb' -- './ee/spec/finders/incident_management/oncall_rotations_finder_spec.rb' -- './ee/spec/finders/incident_management/oncall_schedules_finder_spec.rb' -- './ee/spec/finders/incident_management/oncall_users_finder_spec.rb' - './ee/spec/finders/issues_finder_spec.rb' - './ee/spec/finders/iterations/cadences_finder_spec.rb' - './ee/spec/finders/iterations_finder_spec.rb' @@ -625,8 +609,6 @@ - './ee/spec/finders/security/vulnerability_reads_finder_spec.rb' - './ee/spec/finders/snippets_finder_spec.rb' - './ee/spec/finders/software_license_policies_finder_spec.rb' -- './ee/spec/finders/status_page/incident_comments_finder_spec.rb' -- './ee/spec/finders/status_page/incidents_finder_spec.rb' - './ee/spec/finders/template_finder_spec.rb' - './ee/spec/finders/users_finder_spec.rb' - './ee/spec/frontend/fixtures/analytics/charts.rb' @@ -714,17 +696,6 @@ - './ee/spec/graphql/mutations/epics/create_spec.rb' - './ee/spec/graphql/mutations/epics/update_spec.rb' - './ee/spec/graphql/mutations/gitlab_subscriptions/activate_spec.rb' -- './ee/spec/graphql/mutations/incident_management/escalation_policy/create_spec.rb' -- './ee/spec/graphql/mutations/incident_management/escalation_policy/destroy_spec.rb' -- './ee/spec/graphql/mutations/incident_management/escalation_policy/update_spec.rb' -- './ee/spec/graphql/mutations/incident_management/issuable_resource_link/create_spec.rb' -- './ee/spec/graphql/mutations/incident_management/issuable_resource_link/destroy_spec.rb' -- './ee/spec/graphql/mutations/incident_management/oncall_rotation/create_spec.rb' -- './ee/spec/graphql/mutations/incident_management/oncall_rotation/destroy_spec.rb' -- './ee/spec/graphql/mutations/incident_management/oncall_rotation/update_spec.rb' -- './ee/spec/graphql/mutations/incident_management/oncall_schedule/create_spec.rb' -- './ee/spec/graphql/mutations/incident_management/oncall_schedule/destroy_spec.rb' -- './ee/spec/graphql/mutations/incident_management/oncall_schedule/update_spec.rb' - './ee/spec/graphql/mutations/instance_security_dashboard/add_project_spec.rb' - './ee/spec/graphql/mutations/instance_security_dashboard/remove_project_spec.rb' - './ee/spec/graphql/mutations/issues/create_spec.rb' @@ -796,12 +767,6 @@ - './ee/spec/graphql/resolvers/geo/snippet_repository_registries_resolver_spec.rb' - './ee/spec/graphql/resolvers/geo/terraform_state_version_registries_resolver_spec.rb' - './ee/spec/graphql/resolvers/geo/upload_registries_resolver_spec.rb' -- './ee/spec/graphql/resolvers/incident_management/escalation_policies_resolver_spec.rb' -- './ee/spec/graphql/resolvers/incident_management/issuable_resource_links_resolver_spec.rb' -- './ee/spec/graphql/resolvers/incident_management/oncall_rotations_resolver_spec.rb' -- './ee/spec/graphql/resolvers/incident_management/oncall_schedule_resolver_spec.rb' -- './ee/spec/graphql/resolvers/incident_management/oncall_shifts_resolver_spec.rb' -- './ee/spec/graphql/resolvers/incident_management/oncall_users_resolver_spec.rb' - './ee/spec/graphql/resolvers/instance_security_dashboard/projects_resolver_spec.rb' - './ee/spec/graphql/resolvers/instance_security_dashboard_resolver_spec.rb' - './ee/spec/graphql/resolvers/iterations/cadences_resolver_spec.rb' @@ -899,16 +864,6 @@ - './ee/spec/graphql/types/group_release_stats_type_spec.rb' - './ee/spec/graphql/types/group_stats_type_spec.rb' - './ee/spec/graphql/types/health_status_enum_spec.rb' -- './ee/spec/graphql/types/incident_management/escalation_policy_type_spec.rb' -- './ee/spec/graphql/types/incident_management/escalation_rule_input_type_spec.rb' -- './ee/spec/graphql/types/incident_management/escalation_rule_type_spec.rb' -- './ee/spec/graphql/types/incident_management/issuable_resource_link_type_enum_spec.rb' -- './ee/spec/graphql/types/incident_management/issuable_resource_link_type_spec.rb' -- './ee/spec/graphql/types/incident_management/oncall_participant_type_spec.rb' -- './ee/spec/graphql/types/incident_management/oncall_rotation_date_input_type_spec.rb' -- './ee/spec/graphql/types/incident_management/oncall_rotation_type_spec.rb' -- './ee/spec/graphql/types/incident_management/oncall_schedule_type_spec.rb' -- './ee/spec/graphql/types/incident_management/oncall_shift_type_spec.rb' - './ee/spec/graphql/types/instance_security_dashboard_type_spec.rb' - './ee/spec/graphql/types/issue_connection_type_spec.rb' - './ee/spec/graphql/types/issue_type_spec.rb' @@ -1049,7 +1004,6 @@ - './ee/spec/helpers/ee/operations_helper_spec.rb' - './ee/spec/helpers/ee/personal_access_tokens_helper_spec.rb' - './ee/spec/helpers/ee/profiles_helper_spec.rb' -- './ee/spec/helpers/ee/projects/incidents_helper_spec.rb' - './ee/spec/helpers/ee/projects/pipeline_helper_spec.rb' - './ee/spec/helpers/ee/projects/security/api_fuzzing_configuration_helper_spec.rb' - './ee/spec/helpers/ee/projects/security/configuration_helper_spec.rb' @@ -1074,8 +1028,6 @@ - './ee/spec/helpers/groups/ldap_sync_helper_spec.rb' - './ee/spec/helpers/groups/security_features_helper_spec.rb' - './ee/spec/helpers/groups/sso_helper_spec.rb' -- './ee/spec/helpers/incident_management/escalation_policy_helper_spec.rb' -- './ee/spec/helpers/incident_management/oncall_schedule_helper_spec.rb' - './ee/spec/helpers/kerberos_helper_spec.rb' - './ee/spec/helpers/license_helper_spec.rb' - './ee/spec/helpers/license_monitoring_helper_spec.rb' @@ -1525,7 +1477,6 @@ - './ee/spec/lib/gitlab/geo/log_cursor/daemon_spec.rb' - './ee/spec/lib/gitlab/geo/log_cursor/event_logs_spec.rb' - './ee/spec/lib/gitlab/geo/log_cursor/events/cache_invalidation_event_spec.rb' -- './ee/spec/lib/gitlab/geo/log_cursor/events/container_repository_updated_event_spec.rb' - './ee/spec/lib/gitlab/geo/log_cursor/events/design_repository_updated_event_spec.rb' - './ee/spec/lib/gitlab/geo/log_cursor/events/event_spec.rb' - './ee/spec/lib/gitlab/geo/log_cursor/events/hashed_storage_attachments_event_spec.rb' @@ -1572,7 +1523,6 @@ - './ee/spec/lib/gitlab/import_export/group/relation_factory_spec.rb' - './ee/spec/lib/gitlab/import_export/project/object_builder_spec.rb' - './ee/spec/lib/gitlab/import_sources_spec.rb' -- './ee/spec/lib/gitlab/incident_management_spec.rb' - './ee/spec/lib/gitlab/ingestion/bulk_insertable_task_spec.rb' - './ee/spec/lib/gitlab/insights/executors/dora_executor_spec.rb' - './ee/spec/lib/gitlab/insights/executors/issuable_executor_spec.rb' @@ -1629,14 +1579,6 @@ - './ee/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb' - './ee/spec/lib/gitlab/spdx/catalogue_gateway_spec.rb' - './ee/spec/lib/gitlab/spdx/catalogue_spec.rb' -- './ee/spec/lib/gitlab/status_page/filter/image_filter_spec.rb' -- './ee/spec/lib/gitlab/status_page/filter/mention_anonymization_filter_spec.rb' -- './ee/spec/lib/gitlab/status_page/pipeline/post_process_pipeline_spec.rb' -- './ee/spec/lib/gitlab/status_page_spec.rb' -- './ee/spec/lib/gitlab/status_page/storage/s3_client_spec.rb' -- './ee/spec/lib/gitlab/status_page/storage/s3_multipart_upload_spec.rb' -- './ee/spec/lib/gitlab/status_page/storage_spec.rb' -- './ee/spec/lib/gitlab/status_page/usage_data_counters/incident_counter_spec.rb' - './ee/spec/lib/gitlab/subscription_portal/clients/graphql_spec.rb' - './ee/spec/lib/gitlab/subscription_portal/client_spec.rb' - './ee/spec/lib/gitlab/subscription_portal/clients/rest_spec.rb' @@ -1686,7 +1628,6 @@ - './ee/spec/lib/gitlab/web_ide/config/entry/schema_spec.rb' - './ee/spec/lib/gitlab/web_ide/config/entry/schemas_spec.rb' - './ee/spec/lib/gitlab/web_ide/config/entry/schema/uri_spec.rb' -- './ee/spec/lib/incident_management/oncall_shift_generator_spec.rb' - './ee/spec/lib/omni_auth/strategies/group_saml_spec.rb' - './ee/spec/lib/omni_auth/strategies/kerberos_spec.rb' - './ee/spec/lib/peek/views/elasticsearch_spec.rb' @@ -1830,7 +1771,6 @@ - './ee/spec/models/concerns/geo/verifiable_model_spec.rb' - './ee/spec/models/concerns/geo/verification_state_spec.rb' - './ee/spec/models/concerns/health_status_spec.rb' -- './ee/spec/models/concerns/incident_management/base_pending_escalation_spec.rb' - './ee/spec/models/concerns/password_complexity_spec.rb' - './ee/spec/models/concerns/scim_paginatable_spec.rb' - './ee/spec/models/container_registry/event_spec.rb' @@ -1874,7 +1814,6 @@ - './ee/spec/models/ee/group_group_link_spec.rb' - './ee/spec/models/ee/groups/feature_setting_spec.rb' - './ee/spec/models/ee/group_spec.rb' -- './ee/spec/models/ee/incident_management/project_incident_management_setting_spec.rb' - './ee/spec/models/ee/integrations/jira_spec.rb' - './ee/spec/models/ee/integration_spec.rb' - './ee/spec/models/ee/iterations/cadence_spec.rb' @@ -1930,7 +1869,6 @@ - './ee/spec/models/geo/cache_invalidation_event_spec.rb' - './ee/spec/models/geo/ci_secure_file_registry_spec.rb' - './ee/spec/models/geo/container_repository_registry_spec.rb' -- './ee/spec/models/geo/container_repository_updated_event_spec.rb' - './ee/spec/models/geo/deleted_project_spec.rb' - './ee/spec/models/geo/design_registry_spec.rb' - './ee/spec/models/geo/event_log_spec.rb' @@ -1974,16 +1912,6 @@ - './ee/spec/models/historical_data_spec.rb' - './ee/spec/models/hooks/group_hook_spec.rb' - './ee/spec/models/identity_spec.rb' -- './ee/spec/models/incident_management/escalation_policy_spec.rb' -- './ee/spec/models/incident_management/escalation_rule_spec.rb' -- './ee/spec/models/incident_management/issuable_escalation_status_spec.rb' -- './ee/spec/models/incident_management/issuable_resource_link_spec.rb' -- './ee/spec/models/incident_management/oncall_participant_spec.rb' -- './ee/spec/models/incident_management/oncall_rotation_spec.rb' -- './ee/spec/models/incident_management/oncall_schedule_spec.rb' -- './ee/spec/models/incident_management/oncall_shift_spec.rb' -- './ee/spec/models/incident_management/pending_escalations/alert_spec.rb' -- './ee/spec/models/incident_management/pending_escalations/issue_spec.rb' - './ee/spec/models/instance_security_dashboard_spec.rb' - './ee/spec/models/integrations/chat_message/vulnerability_message_spec.rb' - './ee/spec/models/integrations/github/remote_project_spec.rb' @@ -2069,8 +1997,6 @@ - './ee/spec/models/snippet_spec.rb' - './ee/spec/models/software_license_policy_spec.rb' - './ee/spec/models/software_license_spec.rb' -- './ee/spec/models/status_page/project_setting_spec.rb' -- './ee/spec/models/status_page/published_incident_spec.rb' - './ee/spec/models/storage_shard_spec.rb' - './ee/spec/models/uploads/local_spec.rb' - './ee/spec/models/upload_spec.rb' @@ -2133,9 +2059,6 @@ - './ee/spec/policies/group_hook_policy_spec.rb' - './ee/spec/policies/group_policy_spec.rb' - './ee/spec/policies/identity_provider_policy_spec.rb' -- './ee/spec/policies/incident_management/oncall_rotation_policy_spec.rb' -- './ee/spec/policies/incident_management/oncall_schedule_policy_spec.rb' -- './ee/spec/policies/incident_management/oncall_shift_policy_spec.rb' - './ee/spec/policies/instance_security_dashboard_policy_spec.rb' - './ee/spec/policies/issuable_policy_spec.rb' - './ee/spec/policies/issue_policy_spec.rb' @@ -2267,7 +2190,6 @@ - './ee/spec/requests/api/graphql/group/epics_spec.rb' - './ee/spec/requests/api/graphql/group/external_audit_event_destinations_spec.rb' - './ee/spec/requests/api/graphql/group_query_spec.rb' -- './ee/spec/requests/api/graphql/incident_management/issuable_resource_links_spec.rb' - './ee/spec/requests/api/graphql/instance_security_dashboard_spec.rb' - './ee/spec/requests/api/graphql/iterations/cadences_spec.rb' - './ee/spec/requests/api/graphql/iterations/iterations_spec.rb' @@ -2321,16 +2243,6 @@ - './ee/spec/requests/api/graphql/mutations/epics/update_spec.rb' - './ee/spec/requests/api/graphql/mutations/epic_tree/reorder_spec.rb' - './ee/spec/requests/api/graphql/mutations/gitlab_subscriptions/activate_spec.rb' -- './ee/spec/requests/api/graphql/mutations/incident_management/escalation_policy/create_spec.rb' -- './ee/spec/requests/api/graphql/mutations/incident_management/escalation_policy/destroy_spec.rb' -- './ee/spec/requests/api/graphql/mutations/incident_management/escalation_policy/update_spec.rb' -- './ee/spec/requests/api/graphql/mutations/incident_management/issuable_resource_link/create_spec.rb' -- './ee/spec/requests/api/graphql/mutations/incident_management/issuable_resource_link/destroy_spec.rb' -- './ee/spec/requests/api/graphql/mutations/incident_management/oncall_rotation/create_spec.rb' -- './ee/spec/requests/api/graphql/mutations/incident_management/oncall_rotation/update_spec.rb' -- './ee/spec/requests/api/graphql/mutations/incident_management/oncall_schedule/create_spec.rb' -- './ee/spec/requests/api/graphql/mutations/incident_management/oncall_schedule/destroy_spec.rb' -- './ee/spec/requests/api/graphql/mutations/incident_management/oncall_schedule/update_spec.rb' - './ee/spec/requests/api/graphql/mutations/issues/create_spec.rb' - './ee/spec/requests/api/graphql/mutations/issues/promote_to_epic_spec.rb' - './ee/spec/requests/api/graphql/mutations/issues/set_epic_spec.rb' @@ -2378,11 +2290,6 @@ - './ee/spec/requests/api/graphql/project/dast_site_profile_spec.rb' - './ee/spec/requests/api/graphql/project/dast_site_profiles_spec.rb' - './ee/spec/requests/api/graphql/project/dast_site_validations_spec.rb' -- './ee/spec/requests/api/graphql/project/incident_management/escalation_policies_spec.rb' -- './ee/spec/requests/api/graphql/project/incident_management/escalation_policy/rules_spec.rb' -- './ee/spec/requests/api/graphql/project/incident_management/oncall_participants_spec.rb' -- './ee/spec/requests/api/graphql/project/incident_management/oncall_schedules_spec.rb' -- './ee/spec/requests/api/graphql/project/incident_management/oncall_shifts_spec.rb' - './ee/spec/requests/api/graphql/project/issues_spec.rb' - './ee/spec/requests/api/graphql/project/merge_requests_spec.rb' - './ee/spec/requests/api/graphql/project/path_locks_spec.rb' @@ -2501,14 +2408,12 @@ - './ee/spec/requests/groups/roadmap_controller_spec.rb' - './ee/spec/requests/groups/security/credentials_controller_spec.rb' - './ee/spec/requests/groups/settings/reporting_controller_spec.rb' -- './ee/spec/requests/groups/usage_quotas_spec.rb' - './ee/spec/requests/jwt_controller_spec.rb' - './ee/spec/requests/lfs_http_spec.rb' - './ee/spec/requests/lfs_locks_api_spec.rb' - './ee/spec/requests/omniauth_kerberos_spec.rb' - './ee/spec/requests/projects/analytics/code_reviews_controller_spec.rb' - './ee/spec/requests/projects/audit_events_spec.rb' -- './ee/spec/requests/projects/incidents_controller_spec.rb' - './ee/spec/requests/projects/issue_feature_flags_controller_spec.rb' - './ee/spec/requests/projects/issues_controller_spec.rb' - './ee/spec/requests/projects/merge_requests_controller_spec.rb' @@ -2588,8 +2493,6 @@ - './ee/spec/serializers/fork_namespace_entity_spec.rb' - './ee/spec/serializers/geo_project_registry_entity_spec.rb' - './ee/spec/serializers/group_vulnerability_autocomplete_entity_spec.rb' -- './ee/spec/serializers/incident_management/escalation_policy_entity_spec.rb' -- './ee/spec/serializers/incident_management/oncall_schedule_entity_spec.rb' - './ee/spec/serializers/integrations/field_entity_spec.rb' - './ee/spec/serializers/integrations/jira_serializers/issue_detail_entity_spec.rb' - './ee/spec/serializers/integrations/jira_serializers/issue_entity_spec.rb' @@ -2620,10 +2523,6 @@ - './ee/spec/serializers/security/license_policy_entity_spec.rb' - './ee/spec/serializers/security/vulnerability_report_data_entity_spec.rb' - './ee/spec/serializers/security/vulnerability_report_data_serializer_spec.rb' -- './ee/spec/serializers/status_page/incident_comment_entity_spec.rb' -- './ee/spec/serializers/status_page/incident_entity_spec.rb' -- './ee/spec/serializers/status_page/incident_serializer_spec.rb' -- './ee/spec/serializers/status_page/renderer_spec.rb' - './ee/spec/serializers/storage_shard_entity_spec.rb' - './ee/spec/serializers/test_reports_comparer_entity_spec.rb' - './ee/spec/serializers/test_reports_comparer_serializer_spec.rb' @@ -2836,9 +2735,6 @@ - './ee/spec/services/ee/groups/deploy_tokens/revoke_service_spec.rb' - './ee/spec/services/ee/groups/import_export/export_service_spec.rb' - './ee/spec/services/ee/groups/import_export/import_service_spec.rb' -- './ee/spec/services/ee/incident_management/issuable_escalation_statuses/after_update_service_spec.rb' -- './ee/spec/services/ee/incident_management/issuable_escalation_statuses/create_service_spec.rb' -- './ee/spec/services/ee/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb' - './ee/spec/services/ee/integrations/test/project_service_spec.rb' - './ee/spec/services/ee/ip_restrictions/update_service_spec.rb' - './ee/spec/services/ee/issuable/bulk_update_service_spec.rb' @@ -2956,7 +2852,6 @@ - './ee/spec/services/geo/cache_invalidation_event_store_spec.rb' - './ee/spec/services/geo/container_repository_sync_service_spec.rb' - './ee/spec/services/geo/container_repository_sync_spec.rb' -- './ee/spec/services/geo/container_repository_updated_event_store_spec.rb' - './ee/spec/services/geo/design_repository_sync_service_spec.rb' - './ee/spec/services/geo/event_service_spec.rb' - './ee/spec/services/geo/file_registry_removal_service_spec.rb' @@ -3030,26 +2925,6 @@ - './ee/spec/services/groups/update_service_spec.rb' - './ee/spec/services/historical_user_data/csv_service_spec.rb' - './ee/spec/services/ide/schemas_config_service_spec.rb' -- './ee/spec/services/incident_management/create_incident_sla_exceeded_label_service_spec.rb' -- './ee/spec/services/incident_management/escalation_policies/create_service_spec.rb' -- './ee/spec/services/incident_management/escalation_policies/destroy_service_spec.rb' -- './ee/spec/services/incident_management/escalation_policies/update_service_spec.rb' -- './ee/spec/services/incident_management/escalation_rules/destroy_service_spec.rb' -- './ee/spec/services/incident_management/incidents/create_sla_service_spec.rb' -- './ee/spec/services/incident_management/incidents/upload_metric_service_spec.rb' -- './ee/spec/services/incident_management/issuable_resource_links/create_service_spec.rb' -- './ee/spec/services/incident_management/issuable_resource_links/destroy_service_spec.rb' -- './ee/spec/services/incident_management/oncall_rotations/create_service_spec.rb' -- './ee/spec/services/incident_management/oncall_rotations/destroy_service_spec.rb' -- './ee/spec/services/incident_management/oncall_rotations/edit_service_spec.rb' -- './ee/spec/services/incident_management/oncall_rotations/remove_participant_service_spec.rb' -- './ee/spec/services/incident_management/oncall_rotations/remove_participants_service_spec.rb' -- './ee/spec/services/incident_management/oncall_schedules/create_service_spec.rb' -- './ee/spec/services/incident_management/oncall_schedules/destroy_service_spec.rb' -- './ee/spec/services/incident_management/oncall_schedules/update_service_spec.rb' -- './ee/spec/services/incident_management/oncall_shifts/read_service_spec.rb' -- './ee/spec/services/incident_management/pending_escalations/create_service_spec.rb' -- './ee/spec/services/incident_management/pending_escalations/process_service_spec.rb' - './ee/spec/services/issuable/destroy_label_links_service_spec.rb' - './ee/spec/services/issue_feature_flags/list_service_spec.rb' - './ee/spec/services/issues/build_service_spec.rb' @@ -3233,13 +3108,6 @@ - './ee/spec/services/software_license_policies/create_service_spec.rb' - './ee/spec/services/software_license_policies/update_service_spec.rb' - './ee/spec/services/start_pull_mirroring_service_spec.rb' -- './ee/spec/services/status_page/mark_for_publication_service_spec.rb' -- './ee/spec/services/status_page/publish_attachments_service_spec.rb' -- './ee/spec/services/status_page/publish_details_service_spec.rb' -- './ee/spec/services/status_page/publish_list_service_spec.rb' -- './ee/spec/services/status_page/publish_service_spec.rb' -- './ee/spec/services/status_page/trigger_publish_service_spec.rb' -- './ee/spec/services/status_page/unpublish_details_service_spec.rb' - './ee/spec/services/system_notes/epics_service_spec.rb' - './ee/spec/services/system_note_service_spec.rb' - './ee/spec/services/system_notes/escalations_service_spec.rb' @@ -3512,15 +3380,6 @@ - './ee/spec/workers/group_wikis/git_garbage_collect_worker_spec.rb' - './ee/spec/workers/historical_data_worker_spec.rb' - './ee/spec/workers/import_software_licenses_worker_spec.rb' -- './ee/spec/workers/incident_management/apply_incident_sla_exceeded_label_worker_spec.rb' -- './ee/spec/workers/incident_management/incident_sla_exceeded_check_worker_spec.rb' -- './ee/spec/workers/incident_management/oncall_rotations/persist_all_rotations_shifts_job_spec.rb' -- './ee/spec/workers/incident_management/oncall_rotations/persist_shifts_job_spec.rb' -- './ee/spec/workers/incident_management/pending_escalations/alert_check_worker_spec.rb' -- './ee/spec/workers/incident_management/pending_escalations/alert_create_worker_spec.rb' -- './ee/spec/workers/incident_management/pending_escalations/issue_check_worker_spec.rb' -- './ee/spec/workers/incident_management/pending_escalations/issue_create_worker_spec.rb' -- './ee/spec/workers/incident_management/pending_escalations/schedule_check_cron_worker_spec.rb' - './ee/spec/workers/iterations/cadences/create_iterations_worker_spec.rb' - './ee/spec/workers/iterations/cadences/schedule_create_iterations_worker_spec.rb' - './ee/spec/workers/iterations/roll_over_issues_worker_spec.rb' @@ -3559,7 +3418,6 @@ - './ee/spec/workers/security/sync_scan_policies_worker_spec.rb' - './ee/spec/workers/security/track_secure_scans_worker_spec.rb' - './ee/spec/workers/set_user_status_based_on_user_cap_setting_worker_spec.rb' -- './ee/spec/workers/status_page/publish_worker_spec.rb' - './ee/spec/workers/store_security_reports_worker_spec.rb' - './ee/spec/workers/sync_seat_link_request_worker_spec.rb' - './ee/spec/workers/sync_seat_link_worker_spec.rb' @@ -3787,7 +3645,6 @@ - './spec/controllers/projects/hooks_controller_spec.rb' - './spec/controllers/projects/import/jira_controller_spec.rb' - './spec/controllers/projects/imports_controller_spec.rb' -- './spec/controllers/projects/incidents_controller_spec.rb' - './spec/controllers/projects/issue_links_controller_spec.rb' - './spec/controllers/projects/issues_controller_spec.rb' - './spec/controllers/projects/jobs_controller_spec.rb' @@ -4081,12 +3938,6 @@ - './spec/features/ide/user_commits_changes_spec.rb' - './spec/features/ide/user_opens_merge_request_spec.rb' - './spec/features/import/manifest_import_spec.rb' -- './spec/features/incidents/incident_details_spec.rb' -- './spec/features/incidents/incidents_list_spec.rb' -- './spec/features/incidents/incident_timeline_events_spec.rb' -- './spec/features/incidents/user_creates_new_incident_spec.rb' -- './spec/features/incidents/user_filters_incidents_by_status_spec.rb' -- './spec/features/incidents/user_searches_incidents_spec.rb' - './spec/features/invites_spec.rb' - './spec/features/issuables/issuable_list_spec.rb' - './spec/features/issuables/markdown_references/internal_references_spec.rb' @@ -4114,7 +3965,6 @@ - './spec/features/issues/form_spec.rb' - './spec/features/issues/gfm_autocomplete_spec.rb' - './spec/features/issues/group_label_sidebar_spec.rb' -- './spec/features/issues/incident_issue_spec.rb' - './spec/features/issues/issue_detail_spec.rb' - './spec/features/issues/issue_header_spec.rb' - './spec/features/issues/issue_sidebar_spec.rb' @@ -4408,7 +4258,6 @@ - './spec/features/projects/integrations/user_activates_assembla_spec.rb' - './spec/features/projects/integrations/user_activates_atlassian_bamboo_ci_spec.rb' - './spec/features/projects/integrations/user_activates_emails_on_push_spec.rb' -- './spec/features/projects/integrations/user_activates_flowdock_spec.rb' - './spec/features/projects/integrations/user_activates_irker_spec.rb' - './spec/features/projects/integrations/user_activates_issue_tracker_spec.rb' - './spec/features/projects/integrations/user_activates_jetbrains_teamcity_ci_spec.rb' @@ -4730,7 +4579,6 @@ - './spec/finders/groups/projects_requiring_authorizations_refresh/on_direct_membership_finder_spec.rb' - './spec/finders/groups/projects_requiring_authorizations_refresh/on_transfer_finder_spec.rb' - './spec/finders/groups/user_groups_finder_spec.rb' -- './spec/finders/incident_management/timeline_events_finder_spec.rb' - './spec/finders/issuables/crm_contact_filter_spec.rb' - './spec/finders/issuables/crm_organization_filter_spec.rb' - './spec/finders/issues_finder_spec.rb' @@ -4907,10 +4755,6 @@ - './spec/graphql/mutations/discussions/toggle_resolve_spec.rb' - './spec/graphql/mutations/environments/canary_ingress/update_spec.rb' - './spec/graphql/mutations/groups/update_spec.rb' -- './spec/graphql/mutations/incident_management/timeline_event/create_spec.rb' -- './spec/graphql/mutations/incident_management/timeline_event/destroy_spec.rb' -- './spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb' -- './spec/graphql/mutations/incident_management/timeline_event/update_spec.rb' - './spec/graphql/mutations/issues/create_spec.rb' - './spec/graphql/mutations/issues/move_spec.rb' - './spec/graphql/mutations/issues/set_assignees_spec.rb' @@ -5023,7 +4867,6 @@ - './spec/graphql/resolvers/group_packages_resolver_spec.rb' - './spec/graphql/resolvers/group_resolver_spec.rb' - './spec/graphql/resolvers/groups_resolver_spec.rb' -- './spec/graphql/resolvers/incident_management/timeline_events_resolver_spec.rb' - './spec/graphql/resolvers/issues_resolver_spec.rb' - './spec/graphql/resolvers/issue_status_counts_resolver_spec.rb' - './spec/graphql/resolvers/kas/agent_configurations_resolver_spec.rb' @@ -5218,8 +5061,6 @@ - './spec/graphql/types/group_member_relation_enum_spec.rb' - './spec/graphql/types/group_member_type_spec.rb' - './spec/graphql/types/group_type_spec.rb' -- './spec/graphql/types/incident_management/escalation_status_enum_spec.rb' -- './spec/graphql/types/incident_management/timeline_event_type_spec.rb' - './spec/graphql/types/invitation_interface_spec.rb' - './spec/graphql/types/issuable_searchable_field_enum_spec.rb' - './spec/graphql/types/issuable_severity_enum_spec.rb' @@ -5470,7 +5311,6 @@ - './spec/helpers/projects/cluster_agents_helper_spec.rb' - './spec/helpers/projects/error_tracking_helper_spec.rb' - './spec/helpers/projects_helper_spec.rb' -- './spec/helpers/projects/incidents_helper_spec.rb' - './spec/helpers/projects/pipeline_helper_spec.rb' - './spec/helpers/projects/project_members_helper_spec.rb' - './spec/helpers/projects/security/configuration_helper_spec.rb' @@ -5739,7 +5579,6 @@ - './spec/lib/banzai/pipeline/emoji_pipeline_spec.rb' - './spec/lib/banzai/pipeline/full_pipeline_spec.rb' - './spec/lib/banzai/pipeline/gfm_pipeline_spec.rb' -- './spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb' - './spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb' - './spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb' - './spec/lib/banzai/pipeline/post_process_pipeline_spec.rb' @@ -7118,7 +6957,6 @@ - './spec/lib/gitlab/import/set_async_jid_spec.rb' - './spec/lib/gitlab/import_sources_spec.rb' - './spec/lib/gitlab/inactive_projects_deletion_warning_tracker_spec.rb' -- './spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb' - './spec/lib/gitlab/incoming_email_spec.rb' - './spec/lib/gitlab/insecure_key_fingerprint_spec.rb' - './spec/lib/gitlab/instrumentation_helper_spec.rb' @@ -7598,7 +7436,6 @@ - './spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb' - './spec/lib/gitlab/tracking/destinations/snowplow_spec.rb' - './spec/lib/gitlab/tracking/event_definition_spec.rb' -- './spec/lib/gitlab/tracking/incident_management_spec.rb' - './spec/lib/gitlab/tracking/snowplow_schema_validation_spec.rb' - './spec/lib/gitlab/tracking_spec.rb' - './spec/lib/gitlab/tracking/standard_context_spec.rb' @@ -7902,7 +7739,6 @@ - './spec/migrations/20211207125331_remove_jobs_for_recalculate_vulnerabilities_occurrences_uuid_spec.rb' - './spec/migrations/20211207135331_schedule_recalculate_uuid_on_vulnerabilities_occurrences4_spec.rb' - './spec/migrations/20211210140629_encrypt_static_object_token_spec.rb' -- './spec/migrations/20211214012507_backfill_incident_issue_escalation_statuses_spec.rb' - './spec/migrations/20211217174331_mark_recalculate_finding_signatures_as_completed_spec.rb' - './spec/migrations/20220106111958_add_insert_or_update_vulnerability_reads_trigger_spec.rb' - './spec/migrations/20220106112043_add_update_vulnerability_reads_trigger_spec.rb' @@ -7959,7 +7795,6 @@ - './spec/migrations/20220627090231_schedule_disable_legacy_open_source_license_for_inactive_public_projects_spec.rb' - './spec/migrations/20220627152642_queue_update_delayed_project_removal_to_null_for_user_namespace_spec.rb' - './spec/migrations/20220628012902_finalise_project_namespace_members_spec.rb' -- './spec/migrations/20220629184402_unset_escalation_policies_for_alert_incidents_spec.rb' - './spec/migrations/20220715163254_update_notes_in_past_spec.rb' - './spec/migrations/20220721031446_schedule_disable_legacy_open_source_license_for_one_member_no_repo_projects_spec.rb' - './spec/migrations/20220722084543_schedule_disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb' @@ -8421,9 +8256,6 @@ - './spec/models/identity_spec.rb' - './spec/models/import_export_upload_spec.rb' - './spec/models/import_failure_spec.rb' -- './spec/models/incident_management/issuable_escalation_status_spec.rb' -- './spec/models/incident_management/project_incident_management_setting_spec.rb' -- './spec/models/incident_management/timeline_event_spec.rb' - './spec/models/instance_configuration_spec.rb' - './spec/models/instance_metadata/kas_spec.rb' - './spec/models/instance_metadata_spec.rb' @@ -8455,7 +8287,6 @@ - './spec/models/integrations/ewm_spec.rb' - './spec/models/integrations/external_wiki_spec.rb' - './spec/models/integrations/field_spec.rb' -- './spec/models/integrations/flowdock_spec.rb' - './spec/models/integrations/hangouts_chat_spec.rb' - './spec/models/integrations/harbor_spec.rb' - './spec/models/integrations/irker_spec.rb' @@ -8795,7 +8626,6 @@ - './spec/policies/group_member_policy_spec.rb' - './spec/policies/group_policy_spec.rb' - './spec/policies/identity_provider_policy_spec.rb' -- './spec/policies/incident_management/timeline_event_policy_spec.rb' - './spec/policies/instance_metadata_policy_spec.rb' - './spec/policies/integration_policy_spec.rb' - './spec/policies/issuable_policy_spec.rb' @@ -9067,10 +8897,6 @@ - './spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb' - './spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb' - './spec/requests/api/graphql/mutations/groups/update_spec.rb' -- './spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb' -- './spec/requests/api/graphql/mutations/incident_management/timeline_event/destroy_spec.rb' -- './spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb' -- './spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb' - './spec/requests/api/graphql/mutations/issues/create_spec.rb' - './spec/requests/api/graphql/mutations/issues/move_spec.rb' - './spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb' @@ -9164,7 +8990,6 @@ - './spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb' - './spec/requests/api/graphql/project/fork_targets_spec.rb' - './spec/requests/api/graphql/project/grafana_integration_spec.rb' -- './spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb' - './spec/requests/api/graphql/project/issue/design_collection/version_spec.rb' - './spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb' - './spec/requests/api/graphql/project/issue/designs/designs_spec.rb' @@ -9395,7 +9220,6 @@ - './spec/requests/projects/harbor/artifacts_controller_spec.rb' - './spec/requests/projects/harbor/repositories_controller_spec.rb' - './spec/requests/projects/harbor/tags_controller_spec.rb' -- './spec/requests/projects/incident_management/pagerduty_incidents_spec.rb' - './spec/requests/projects/integrations/shimos_controller_spec.rb' - './spec/requests/projects/issue_links_controller_spec.rb' - './spec/requests/projects/issues_controller_spec.rb' @@ -9981,15 +9805,6 @@ - './spec/services/import/gitlab_projects/file_acquisition_strategies/remote_file_spec.rb' - './spec/services/import/prepare_service_spec.rb' - './spec/services/import/validate_remote_git_endpoint_service_spec.rb' -- './spec/services/incident_management/incidents/create_service_spec.rb' -- './spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb' -- './spec/services/incident_management/issuable_escalation_statuses/build_service_spec.rb' -- './spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb' -- './spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb' -- './spec/services/incident_management/pager_duty/process_webhook_service_spec.rb' -- './spec/services/incident_management/timeline_events/create_service_spec.rb' -- './spec/services/incident_management/timeline_events/destroy_service_spec.rb' -- './spec/services/incident_management/timeline_events/update_service_spec.rb' - './spec/services/integrations/propagate_service_spec.rb' - './spec/services/integrations/test/project_service_spec.rb' - './spec/services/issuable/bulk_update_service_spec.rb' @@ -10379,8 +10194,6 @@ - './spec/services/system_notes/commit_service_spec.rb' - './spec/services/system_notes/design_management_service_spec.rb' - './spec/services/system_note_service_spec.rb' -- './spec/services/system_notes/incident_service_spec.rb' -- './spec/services/system_notes/incidents_service_spec.rb' - './spec/services/system_notes/issuables_service_spec.rb' - './spec/services/system_notes/merge_requests_service_spec.rb' - './spec/services/system_notes/time_tracking_service_spec.rb' @@ -10599,7 +10412,6 @@ - './spec/uploaders/records_uploads_spec.rb' - './spec/uploaders/terraform/state_uploader_spec.rb' - './spec/uploaders/uploader_helper_spec.rb' -- './spec/uploaders/workers/object_storage/background_move_worker_spec.rb' - './spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb' - './spec/validators/addressable_url_validator_spec.rb' - './spec/validators/any_field_validator_spec.rb' @@ -10945,10 +10757,6 @@ - './spec/workers/hashed_storage/project_rollback_worker_spec.rb' - './spec/workers/hashed_storage/rollbacker_worker_spec.rb' - './spec/workers/import_issues_csv_worker_spec.rb' -- './spec/workers/incident_management/add_severity_system_note_worker_spec.rb' -- './spec/workers/incident_management/close_incident_worker_spec.rb' -- './spec/workers/incident_management/pager_duty/process_incident_worker_spec.rb' -- './spec/workers/incident_management/process_alert_worker_v2_spec.rb' - './spec/workers/integrations/create_external_cross_reference_worker_spec.rb' - './spec/workers/integrations/execute_worker_spec.rb' - './spec/workers/integrations/irker_worker_spec.rb' diff --git a/spec/support/shared_contexts/disable_user_tracking.rb b/spec/support/shared_contexts/disable_user_tracking.rb new file mode 100644 index 00000000000..e6689c41d4a --- /dev/null +++ b/spec/support/shared_contexts/disable_user_tracking.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +RSpec.shared_context 'when user tracking is disabled' do + before do + # rubocop:disable RSpec/AnyInstanceOf + allow_any_instance_of(User).to receive(:update_tracked_fields!) + allow_any_instance_of(Users::ActivityService).to receive(:execute) + # rubocop:enable RSpec/AnyInstanceOf + end +end diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb index 086cdf50e9d..12d4af5170b 100644 --- a/spec/support/shared_contexts/email_shared_context.rb +++ b/spec/support/shared_contexts/email_shared_context.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_context :email_shared_context do +RSpec.shared_context 'email shared context' do let(:mail_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' } let(:receiver) { Gitlab::Email::Receiver.new(email_raw) } let(:markdown) { '![image](uploads/image.png)' } @@ -27,7 +27,7 @@ def service_desk_fixture(path, slug: nil, key: 'mykey') fixture_file(path).gsub('project_slug', slug).gsub('project_key', key) end -RSpec.shared_examples :reply_processing_shared_examples do +RSpec.shared_examples 'reply processing shared examples' do context 'when the user could not be found' do before do user.destroy! @@ -49,7 +49,7 @@ RSpec.shared_examples :reply_processing_shared_examples do end end -RSpec.shared_examples :checks_permissions_on_noteable_examples do +RSpec.shared_examples 'checks permissions on noteable examples' do context 'when user has access' do before do project.add_reporter(user) @@ -67,7 +67,7 @@ RSpec.shared_examples :checks_permissions_on_noteable_examples do end end -RSpec.shared_examples :note_handler_shared_examples do |forwardable| +RSpec.shared_examples 'note handler shared examples' do |forwardable| context 'when the noteable could not be found' do before do noteable.destroy! @@ -157,7 +157,7 @@ RSpec.shared_examples :note_handler_shared_examples do |forwardable| noteable.update_attribute(:discussion_locked, true) end - it_behaves_like :checks_permissions_on_noteable_examples + it_behaves_like 'checks permissions on noteable examples' end context 'when everything is fine' do diff --git a/spec/support/shared_contexts/models/ci/job_token_scope.rb b/spec/support/shared_contexts/models/ci/job_token_scope.rb new file mode 100644 index 00000000000..51f671b139d --- /dev/null +++ b/spec/support/shared_contexts/models/ci/job_token_scope.rb @@ -0,0 +1,21 @@ +# 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) } + 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/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb index a6226fe903b..f6ac98c7669 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -14,7 +14,7 @@ RSpec.shared_context 'GroupPolicy context' do %i[ read_group read_counts read_label read_issue_board_list read_milestone read_issue_board - ] + ] end let(:guest_permissions) do @@ -22,32 +22,32 @@ RSpec.shared_context 'GroupPolicy context' do read_label read_group upload_file read_namespace read_group_activity read_group_issues read_group_boards read_group_labels read_group_milestones read_group_merge_requests - ] + ] end let(:reporter_permissions) do %i[ - admin_label - admin_milestone - admin_issue_board - read_container_image - read_harbor_registry - read_metrics_dashboard_annotation - read_prometheus - read_crm_contact - read_crm_organization - ] + admin_label + admin_milestone + admin_issue_board + read_container_image + read_harbor_registry + read_metrics_dashboard_annotation + read_prometheus + read_crm_contact + read_crm_organization + ] end let(:developer_permissions) do %i[ - create_metrics_dashboard_annotation - delete_metrics_dashboard_annotation - update_metrics_dashboard_annotation - create_custom_emoji - create_package - read_cluster - ] + create_metrics_dashboard_annotation + delete_metrics_dashboard_annotation + update_metrics_dashboard_annotation + create_custom_emoji + create_package + read_cluster + ] end let(:maintainer_permissions) do diff --git a/spec/support/shared_contexts/rack_attack_shared_context.rb b/spec/support/shared_contexts/rack_attack_shared_context.rb index e7b2ee76c3c..12625ead72b 100644 --- a/spec/support/shared_contexts/rack_attack_shared_context.rb +++ b/spec/support/shared_contexts/rack_attack_shared_context.rb @@ -6,7 +6,7 @@ RSpec.shared_context 'rack attack cache store' do Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # Make time-dependent tests deterministic - Timecop.freeze { example.run } + freeze_time { example.run } Rack::Attack.cache.store = Rails.cache end 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 3974338238a..7c37e5189f1 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 @@ -28,6 +28,10 @@ RSpec.shared_context 'conan api setup' do ) end + let(:snowplow_gitlab_standard_context) do + { user: user, project: project, namespace: project.namespace, property: 'i_package_conan_user' } + end + before do project.add_developer(user) allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret) diff --git a/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb index 89f290d8d68..1e50505162d 100644 --- a/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb +++ b/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb @@ -17,6 +17,7 @@ RSpec.shared_context 'npm api setup' do let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let(:package_name) { package.name } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_npm_user' } } before do # create a duplicated package without triggering model validation errors diff --git a/spec/support/shared_contexts/rubocop_default_rspec_language_config_context.rb b/spec/support/shared_contexts/rubocop_default_rspec_language_config_context.rb deleted file mode 100644 index a207c6ae9d1..00000000000 --- a/spec/support/shared_contexts/rubocop_default_rspec_language_config_context.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -# From https://github.com/rubocop/rubocop-rspec/blob/master/spec/shared/default_rspec_language_config_context.rb -# This can be removed once we have https://github.com/rubocop/rubocop-rspec/pull/1377 - -RSpec.shared_context 'with default RSpec/Language config' do - include_context 'config' - - # Deep duplication is needed to prevent config leakage between examples - let(:other_cops) do - default_language = RuboCop::ConfigLoader - .default_configuration['RSpec']['Language'] - default_include = RuboCop::ConfigLoader - .default_configuration['RSpec']['Include'] - { 'RSpec' => - { - 'Include' => default_include, - 'Language' => deep_dup(default_language) - } } - end - - def deep_dup(object) - case object - when Array - object.map { |item| deep_dup(item) } - when Hash - object.transform_values { |value| deep_dup(value) } - else - object # only collections undergo modifications and need duping - end - end -end diff --git a/spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb b/spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb index e26b8cd8b37..7db479bcfd2 100644 --- a/spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb +++ b/spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb @@ -23,7 +23,7 @@ RSpec.shared_context 'container repository delete tags service shared context' d end def stub_delete_reference_requests(tags) - tags = Array.wrap(tags).to_h { |tag| [tag, 200] } unless tags.is_a?(Hash) + tags = Array.wrap(tags).index_with { 200 } unless tags.is_a?(Hash) tags.each do |tag, status| stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/#{tag}") diff --git a/spec/support/shared_examples/boards/destroy_service_shared_examples.rb b/spec/support/shared_examples/boards/destroy_service_shared_examples.rb index b1cb58a736f..578ed0e6260 100644 --- a/spec/support/shared_examples/boards/destroy_service_shared_examples.rb +++ b/spec/support/shared_examples/boards/destroy_service_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples 'board destroy service' do expect do expect(service.execute(board)).to be_success - end.to change(boards, :count).by(-1) + end.to change { boards.count }.by(-1) end end @@ -23,7 +23,7 @@ RSpec.shared_examples 'board destroy service' do it 'does remove board' do expect do service.execute(board) - end.to change(boards, :count).by(-1) + end.to change { boards.count }.by(-1) end end end diff --git a/spec/support/shared_examples/ci/log_downstream_pipeline_shared_examples.rb b/spec/support/shared_examples/ci/log_downstream_pipeline_shared_examples.rb index db724dcfe99..0c3b9ec4151 100644 --- a/spec/support/shared_examples/ci/log_downstream_pipeline_shared_examples.rb +++ b/spec/support/shared_examples/ci/log_downstream_pipeline_shared_examples.rb @@ -13,10 +13,8 @@ RSpec.shared_examples 'logs downstream pipeline creation' do end it 'logs details' do - pipeline = nil - log_entry = record_downstream_pipeline_logs do - pipeline = subject + downstream_pipeline end expect(log_entry).to be_present @@ -24,7 +22,7 @@ RSpec.shared_examples 'logs downstream pipeline creation' do message: "downstream pipeline created", class: described_class.name, root_pipeline_id: expected_root_pipeline.id, - downstream_pipeline_id: pipeline.id, + downstream_pipeline_id: downstream_pipeline.id, downstream_pipeline_relationship: expected_downstream_relationship, hierarchy_size: expected_hierarchy_size, root_pipeline_plan: expected_root_pipeline.project.actual_plan_name, diff --git a/spec/support/shared_examples/ci/retryable_shared_examples.rb b/spec/support/shared_examples/ci/retryable_shared_examples.rb index 4622dbe4e31..dc34ea8bb3c 100644 --- a/spec/support/shared_examples/ci/retryable_shared_examples.rb +++ b/spec/support/shared_examples/ci/retryable_shared_examples.rb @@ -10,7 +10,7 @@ RSpec.shared_examples 'a retryable job' do describe '#set_enqueue_immediately!' do it 'changes #enqueue_immediately? to true' do expect { subject.set_enqueue_immediately! } - .to change(subject, :enqueue_immediately?).from(false).to(true) + .to change { subject.enqueue_immediately? }.from(false).to(true) end end end diff --git a/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb b/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb index 4fcea18393c..dd0a57c6b6d 100644 --- a/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb +++ b/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb @@ -30,6 +30,6 @@ end RSpec.shared_examples 'job is unchanged' do it 'does not change status' do - expect { service.execute }.not_to change(job, :status) + expect { service.execute }.not_to change { job.status } end end diff --git a/spec/support/shared_examples/controllers/destroy_hook_shared_examples.rb b/spec/support/shared_examples/controllers/destroy_hook_shared_examples.rb index 710aa333dec..420973b6882 100644 --- a/spec/support/shared_examples/controllers/destroy_hook_shared_examples.rb +++ b/spec/support/shared_examples/controllers/destroy_hook_shared_examples.rb @@ -9,7 +9,7 @@ RSpec.shared_examples 'Web hook destroyer' do delete :destroy, params: params expect(response).to have_gitlab_http_status(:found) - expect(flash[:notice]).to eq("#{hook.model_name.human} was deleted") + expect(flash[:notice]).to eq('Webhook was deleted') end it 'displays a message about async delete', :aggregate_failures do @@ -20,7 +20,7 @@ RSpec.shared_examples 'Web hook destroyer' do delete :destroy, params: params expect(response).to have_gitlab_http_status(:found) - expect(flash[:notice]).to eq("#{hook.model_name.human} was scheduled for deletion") + expect(flash[:notice]).to eq('Webhook was scheduled for deletion') end it 'displays an error if deletion failed', :aggregate_failures do diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb index bbbe93a644f..5506b05ca55 100644 --- a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb @@ -356,7 +356,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).and_return(double(execute: project)) - expect { post :create, params: { target_namespace: provider_repo[:name] }, format: :json }.to change(Namespace, :count).by(1) + expect { post :create, params: { target_namespace: provider_repo[:name] }, format: :json }.to change { Namespace.count }.by(1) end it "takes the new namespace" do @@ -377,7 +377,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).and_return(double(execute: project)) - expect { post :create, format: :json }.not_to change(Namespace, :count) + expect { post :create, format: :json }.not_to change { Namespace.count } end it "takes the current user's namespace" do 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 6749ebd471f..7e99066110d 100644 --- a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb +++ b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb @@ -16,12 +16,14 @@ RSpec.shared_examples 'Snowplow event tracking' do |overrides: {}| let(:extra) { {} } - it 'is not emitted if FF is disabled' do - stub_feature_flags(feature_flag_name => false) + if try(:feature_flag_name) + it 'is not emitted if FF is disabled' do + stub_feature_flags(feature_flag_name => false) - subject + subject - expect_no_snowplow_event(category: category, action: action) + expect_no_snowplow_event(category: category, action: action) + end end it 'is emitted' do diff --git a/spec/support/shared_examples/controllers/variables_shared_examples.rb b/spec/support/shared_examples/controllers/variables_shared_examples.rb index 34632993cf0..d979683cce7 100644 --- a/spec/support/shared_examples/controllers/variables_shared_examples.rb +++ b/spec/support/shared_examples/controllers/variables_shared_examples.rb @@ -19,13 +19,15 @@ RSpec.shared_examples 'PATCH #update updates variables' do { id: variable.id, key: variable.key, secret_value: variable.value, - protected: variable.protected?.to_s } + protected: variable.protected?.to_s, + raw: (!variable.raw?).to_s } end let(:new_variable_attributes) do { key: 'new_key', secret_value: 'dummy_value', - protected: 'false' } + protected: 'false', + raw: 'true' } end let(:variables_scope) { owner.variables } @@ -86,7 +88,13 @@ RSpec.shared_examples 'PATCH #update updates variables' do end it 'updates the existing variable' do - expect { subject }.to change { variable.reload.value }.to('other_value') + old_raw = variable.raw + + subject + + variable.reload + expect(variable.value).to eq('other_value') + expect(variable.raw?).not_to be(old_raw) end it 'creates the new variable' do diff --git a/spec/support/shared_examples/csp.rb b/spec/support/shared_examples/csp.rb index 91242ae9f37..725d0a832c2 100644 --- a/spec/support/shared_examples/csp.rb +++ b/spec/support/shared_examples/csp.rb @@ -29,7 +29,8 @@ RSpec.shared_examples 'setting CSP' do |rule_name| context 'when feature is enabled' do it "appends to #{rule_name}" do - is_expected.to eql("#{rule_name} #{default_csp_values} #{allowlisted_url}") + is_expected.to include("#{rule_name} #{default_csp_values}") + is_expected.to include(allowlisted_url) end end @@ -37,7 +38,7 @@ RSpec.shared_examples 'setting CSP' do |rule_name| include_context 'disable feature' it "keeps original #{rule_name}" do - is_expected.to eql("#{rule_name} #{default_csp_values}") + is_expected.to include("#{rule_name} #{default_csp_values}") end end end @@ -47,7 +48,8 @@ RSpec.shared_examples 'setting CSP' do |rule_name| context 'when feature is enabled' do it "uses default-src values in #{rule_name}" do - is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{allowlisted_url}") + is_expected.to include("default-src #{default_csp_values}") + is_expected.to include(allowlisted_url) end end @@ -55,7 +57,7 @@ RSpec.shared_examples 'setting CSP' do |rule_name| include_context 'disable feature' it "does not add #{rule_name}" do - is_expected.to eql("default-src #{default_csp_values}") + is_expected.to include("default-src #{default_csp_values}") end end end @@ -65,7 +67,8 @@ RSpec.shared_examples 'setting CSP' do |rule_name| context 'when feature is enabled' do it "uses default-src values in #{rule_name}" do - is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{allowlisted_url}") + is_expected.to include("font-src #{default_csp_values}") + is_expected.not_to include("#{rule_name} #{default_csp_values}") end end @@ -73,7 +76,7 @@ RSpec.shared_examples 'setting CSP' do |rule_name| include_context 'disable feature' it "does not add #{rule_name}" do - is_expected.to eql("font-src #{default_csp_values}") + is_expected.to include("font-src #{default_csp_values}") end end end diff --git a/spec/support/shared_examples/features/container_registry_shared_examples.rb b/spec/support/shared_examples/features/container_registry_shared_examples.rb index 784f82fdda1..06b2b8c621c 100644 --- a/spec/support/shared_examples/features/container_registry_shared_examples.rb +++ b/spec/support/shared_examples/features/container_registry_shared_examples.rb @@ -7,19 +7,3 @@ RSpec.shared_examples 'handling feature network errors with the container regist expect(page).to have_content 'We are having trouble connecting to the Container Registry' end end - -RSpec.shared_examples 'rejecting tags destruction for an importing repository on' do |tags: []| - it 'rejects the tag destruction operation' do - service = instance_double('Projects::ContainerRepository::DeleteTagsService') - expect(service).to receive(:execute).with(container_repository) { { status: :error, message: 'repository importing' } } - expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(container_repository.project, user, tags: tags) { service } - - first('[data-testid="additional-actions"]').click - first('[data-testid="single-delete-button"]').click - expect(find('.modal .modal-title')).to have_content _('Remove tag') - find('.modal .modal-footer .btn-danger').click - - expect(page).to have_content('Tags temporarily cannot be marked for deletion. Please try again in a few minutes.') - expect(page).to have_link('More details', href: help_page_path('user/packages/container_registry/index', anchor: 'tags-temporarily-cannot-be-marked-for-deletion')) - end -end diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb index f01e3c88dad..efdf7513b2d 100644 --- a/spec/support/shared_examples/features/content_editor_shared_examples.rb +++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb @@ -327,7 +327,7 @@ RSpec.shared_examples 'edits content using the content editor' do end def dropdown_scroll_top - evaluate_script("document.querySelector('#{suggestions_dropdown} .gl-new-dropdown-inner').scrollTop") + evaluate_script("document.querySelector('#{suggestions_dropdown} .gl-dropdown-inner').scrollTop") 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 2cfe353d5d7..7f31ea8f9be 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 @@ -64,7 +64,7 @@ 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 .dropdown-content a', text: target_project.full_path).click + find('.dropdown-target-project li', text: target_project.full_path).click wait_for_requests diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb index 68c0d06e7d0..d6f1efc09fc 100644 --- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb +++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb @@ -19,6 +19,8 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name find('.js-comment-button').click + wait_for_all_requests + expect(page).to have_content(comment) new_comment = all(comments_selector).last @@ -301,7 +303,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re if resource_name == 'merge request' let(:note_id) { find("#{comments_selector} .note:first-child", match: :first)['data-note-id'] } - let(:reply_id) { find("#{comments_selector} .note:last-of-type", match: :first)['data-note-id'] } + let(:reply_id) { all("#{comments_selector} [data-note-id]")[1]['data-note-id'] } it 'can be replied to after resolving' do find('button[data-testid="resolve-discussion-button"]').click diff --git a/spec/support/shared_examples/features/inviting_members_shared_examples.rb b/spec/support/shared_examples/features/inviting_members_shared_examples.rb index 277ec6a7fa7..2eca2a72997 100644 --- a/spec/support/shared_examples/features/inviting_members_shared_examples.rb +++ b/spec/support/shared_examples/features/inviting_members_shared_examples.rb @@ -81,7 +81,7 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label| invite_member(user2.name, role: 'Developer') - invite_member(user2.name, role: 'Reporter', refresh: false) + invite_member(user2.name, role: 'Reporter') expect(page).not_to have_selector(invite_modal_selector) @@ -101,7 +101,7 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label| invite_member(email, role: 'Developer') - invite_member(email, role: 'Reporter', refresh: false) + invite_member(email, role: 'Reporter') expect(page).not_to have_selector(invite_modal_selector) @@ -127,7 +127,7 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label| it 'adds the user as a member on sub-entity with higher access level', :js do visit subentity_members_page_path - invite_member(user2.name, role: role, refresh: false) + invite_member(user2.name, role: role) expect(page).not_to have_selector(invite_modal_selector) @@ -145,7 +145,7 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label| it 'fails with an error', :js do visit subentity_members_page_path - invite_member(user2.name, role: role, refresh: false) + invite_member(user2.name, role: role) invite_modal = page.find(invite_modal_selector) expect(invite_modal).to have_content "#{user2.name}: Access level should be greater than or equal to " \ @@ -177,7 +177,7 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label| visit subentity_members_page_path - invite_member([user2.name, user3.name, user4.name, user6.name, user7.name], role: role, refresh: false) + invite_member([user2.name, user3.name, user4.name, user6.name, user7.name], role: role) # we have more than 2 errors, so one will be hidden invite_modal = page.find(invite_modal_selector) @@ -266,7 +266,7 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label| it 'only shows the error for an invalid formatted email and does not display other member errors', :js do visit subentity_members_page_path - invite_member([user2.name, user3.name, 'bad@email'], role: role, refresh: false) + invite_member([user2.name, user3.name, 'bad@email'], role: role) invite_modal = page.find(invite_modal_selector) expect(invite_modal).to have_text('email contains an invalid email address') 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 288e1df9b2a..c35f711111b 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 admin', href: abuse_report_path) + expect(dropdown).to have_link('Report abuse to administrator', href: abuse_report_path) if type == 'issue' || type == 'merge_request' expect(dropdown).to have_button('Delete comment') @@ -33,7 +33,7 @@ RSpec.shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - dropdown.click_link('Report abuse to admin') + dropdown.click_link('Report abuse to administrator') expect(find('#user_name')['value']).to match(note.author.username) expect(find('#abuse_report_message')['value']).to match(noteable_note_url(note)) diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb index a7bc19da45f..20078243cfb 100644 --- a/spec/support/shared_examples/features/runners_shared_examples.rb +++ b/spec/support/shared_examples/features/runners_shared_examples.rb @@ -63,10 +63,13 @@ RSpec.shared_examples 'shows and resets runner registration token' do end RSpec.shared_examples 'shows no runners registered' do - it 'shows counts with 0' do - expect(page).to have_text "#{s_('Runners|Online')} 0" - expect(page).to have_text "#{s_('Runners|Offline')} 0" - expect(page).to have_text "#{s_('Runners|Stale')} 0" + it 'shows total count with 0' do + expect(find('[data-testid="runner-type-tabs"]')).to have_text "#{s_('Runners|All')} 0" + + # No stats are shown + expect(page).not_to have_text s_('Runners|Online') + expect(page).not_to have_text s_('Runners|Offline') + expect(page).not_to have_text s_('Runners|Stale') end it 'shows "no runners" message' do diff --git a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb index cc74c977064..b73f40ff28c 100644 --- a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb +++ b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb @@ -6,7 +6,7 @@ RSpec.shared_examples 'search timeouts' do |scope| context 'when search times out' do before do allow_next_instance_of(SearchService) do |service| - allow(service).to receive(:search_objects).and_raise(ActiveRecord::QueryCanceled) + allow(service).to receive(:search_results).and_raise(ActiveRecord::QueryCanceled) end visit(search_path(search: 'test', scope: scope, **additional_params)) 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 281a70e46c4..95b306fdaaa 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 @@ -17,7 +17,7 @@ RSpec.shared_examples 'labels sidebar widget' do end it 'shows labels list in the dropdown' do - expect(labels_widget.find('.gl-new-dropdown-contents')).to have_selector('li.gl-new-dropdown-item', count: 4) + expect(labels_widget.find('.gl-dropdown-contents')).to have_selector('li.gl-dropdown-item', count: 4) end it 'adds a label' do @@ -57,8 +57,8 @@ RSpec.shared_examples 'labels sidebar widget' do expect(page).to have_css('.labels-fetch-loading') wait_for_all_requests - expect(page).to have_css('[data-testid="dropdown-content"] .gl-new-dropdown-item') - expect(page.all(:css, '[data-testid="dropdown-content"] .gl-new-dropdown-item').length).to eq(1) + expect(page).to have_css('[data-testid="dropdown-content"] .gl-dropdown-item') + expect(page.all(:css, '[data-testid="dropdown-content"] .gl-dropdown-item').length).to eq(1) find_field('Search').native.send_keys(:enter) click_button 'Close' diff --git a/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb index da730240e8e..f8982c242f8 100644 --- a/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb +++ b/spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb @@ -20,15 +20,15 @@ RSpec.shared_examples 'milestone sidebar widget' do it 'shows milestones list in the dropdown' do # 5 milestones + "No milestone" = 6 items - expect(milestone_widget.find('.gl-new-dropdown-contents')).to have_selector('li.gl-new-dropdown-item', count: 6) + expect(milestone_widget.find('.gl-dropdown-contents')).to have_selector('li.gl-dropdown-item', count: 6) end it 'shows expired milestone at the bottom of the list and milestone due earliest at the top of the list', :aggregate_failures do - within(milestone_widget, '.gl-new-dropdown-contents') do + within(milestone_widget, '.gl-dropdown-contents') do expect(page.find('li:last-child')).to have_content milestone_expired.title [milestone3, milestone2, milestone1, milestone_no_duedate].each_with_index do |m, i| - expect(page.all('li.gl-new-dropdown-item')[i + 1]).to have_content m.title + expect(page.all('li.gl-dropdown-item')[i + 1]).to have_content m.title end end end diff --git a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb index 8d1502bed84..7a3b94ad81d 100644 --- a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb @@ -55,7 +55,7 @@ RSpec.shared_examples 'wiki file attachments' do wait_for_requests expect(page.find('#wiki_content').value) - .to match(%r{\!\[dk\]\(uploads/\h{32}/dk\.png\)$}) + .to match(%r{!\[dk\]\(uploads/\h{32}/dk\.png\)$}) end it 'the links point to the wiki root url' do 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 8b3a344a841..9d1f05d5543 100644 --- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb +++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb @@ -109,20 +109,77 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context end context 'filtering by release' do - context 'when the release tag is none' do + context 'when filter by none' do let(:params) { { release_tag: 'none' } } it 'returns items without releases' do expect(items).to contain_exactly(item2, item3, item4, item5) end + + context 'when sort by milestone' do + let(:params) { { release_tag: 'none', sort: 'milestone_due_desc' } } + + it 'returns items without any releases' do + expect(items).to contain_exactly(item2, item3, item4, item5) + end + end + end + + context 'when filter by any' do + let(:params) { { release_tag: 'any' } } + + it 'returns items with any releases' do + expect(items).to contain_exactly(item1) + end + + context 'when sort by milestone' do + let(:params) { { release_tag: 'any', sort: 'milestone_due_desc' } } + + it 'returns items without any releases' do + expect(items).to contain_exactly(item1) + end + end end - context 'when the release tag exists' do + context 'when filter by a release_tag' do let(:params) { { project_id: project1.id, release_tag: release.tag } } - it 'returns the items associated with that release' do + it 'returns the items associated with the release tag' do expect(items).to contain_exactly(item1) end + + context 'when sort by milestone' do + let(:params) { { project_id: project1.id, release_tag: release.tag, sort: 'milestone_due_desc' } } + + it 'returns the items associated with the release tag' do + expect(items).to contain_exactly(item1) + end + end + end + + context 'when filter by a negated release_tag' do + let_it_be(:another_release) { create(:release, project: project1, tag: 'v2.0.0') } + let_it_be(:another_milestone) { create(:milestone, project: project1, releases: [another_release]) } + let_it_be(:another_item) do + create(factory, + project: project1, + milestone: another_milestone, + title: 'another item') + end + + let(:params) { { not: { release_tag: release.tag, project_id: project1.id } } } + + it 'returns the items not associated with the release' do + expect(items).to contain_exactly(another_item) + end + + context 'when sort by milestone' do + let(:params) { { not: { release_tag: release.tag, project_id: project1.id }, sort: 'milestone_due_desc' } } + + it 'returns the items not associated with the release' do + expect(items).to contain_exactly(another_item) + end + end end end @@ -864,12 +921,15 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context context 'filtering by item type' do let_it_be(:incident_item) { create(factory, issue_type: :incident, project: project1) } + let_it_be(:objective) { create(factory, issue_type: :objective, project: project1) } + let_it_be(:key_result) { create(factory, issue_type: :key_result, project: project1) } context 'no type given' do let(:params) { { issue_types: [] } } it 'returns all items' do - expect(items).to contain_exactly(incident_item, item1, item2, item3, item4, item5) + expect(items) + .to contain_exactly(incident_item, item1, item2, item3, item4, item5, objective, key_result) end end @@ -881,6 +941,22 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context end end + context 'objective type' do + let(:params) { { issue_types: ['objective'] } } + + it 'returns incident items' do + expect(items).to contain_exactly(objective) + end + end + + context 'key_result type' do + let(:params) { { issue_types: ['key_result'] } } + + it 'returns incident items' do + expect(items).to contain_exactly(key_result) + end + end + context 'item type' do let(:params) { { issue_types: ['issue'] } } diff --git a/spec/support/shared_examples/finders/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/finders/snippet_visibility_shared_examples.rb index 601a53ed913..f00d6e776ec 100644 --- a/spec/support/shared_examples/finders/snippet_visibility_shared_examples.rb +++ b/spec/support/shared_examples/finders/snippet_visibility_shared_examples.rb @@ -237,8 +237,8 @@ RSpec.shared_examples 'snippet visibility' do if project.private? project.add_developer(external) unless member - else - member.delete if member + elsif member + member.delete end end end diff --git a/spec/support/shared_examples/graphql/label_fields.rb b/spec/support/shared_examples/graphql/label_fields.rb index 4159e4e03ab..030a2feafcd 100644 --- a/spec/support/shared_examples/graphql/label_fields.rb +++ b/spec/support/shared_examples/graphql/label_fields.rb @@ -42,9 +42,7 @@ RSpec.shared_examples 'querying a GraphQL type with labels' do make_query( [ query_graphql_field(:label, label_params, all_graphql_fields_for(Label)), - query_graphql_field(:labels, labels_params, [ - query_graphql_field(:nodes, nil, all_graphql_fields_for(Label)) - ]) + query_graphql_field(:labels, labels_params, [query_graphql_field(:nodes, nil, all_graphql_fields_for(Label))]) ] ) end diff --git a/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb index cd591248ff6..030aca5bd1c 100644 --- a/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/incident_management_timeline_events_shared_examples.rb @@ -7,7 +7,7 @@ require 'spec_helper' # * Defined expected timeline event via `let(:expected_timeline_event) { instance_double(...) }` RSpec.shared_examples 'creating an incident timeline event' do it 'creates a timeline event' do - expect { resolve }.to change(IncidentManagement::TimelineEvent, :count).by(1) + expect { resolve }.to change { IncidentManagement::TimelineEvent.count }.by(1) end it 'responds with a timeline event', :aggregate_failures do diff --git a/spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb index f28348fb945..c6402a89f02 100644 --- a/spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb @@ -1,6 +1,17 @@ # frozen_string_literal: true RSpec.shared_examples 'issuable supports timelog creation mutation' do + let(:mutation_response) { graphql_mutation_response(:timelog_create) } + let(:mutation) do + variables = { + 'time_spent' => time_spent, + 'spent_at' => '2022-11-16T12:59:35+0100', + 'summary' => 'Test summary', + 'issuable_id' => issuable.to_global_id.to_s + } + graphql_mutation(:timelogCreate, variables) + end + context 'when the user is anonymous' do before do post_graphql_mutation(mutation, current_user: current_user) @@ -32,13 +43,14 @@ RSpec.shared_examples 'issuable supports timelog creation mutation' do it 'creates the timelog' do expect do post_graphql_mutation(mutation, current_user: current_user) - end.to change(Timelog, :count).by(1) + end.to change { Timelog.count }.by(1) expect(response).to have_gitlab_http_status(:success) expect(mutation_response['errors']).to be_empty expect(mutation_response['timelog']).to include( 'timeSpent' => 3600, - 'spentAt' => '2022-07-08T00:00:00Z', + # This also checks that the ISO time was converted to UTC + 'spentAt' => '2022-11-16T11:59:35Z', 'summary' => 'Test summary' ) end @@ -50,10 +62,11 @@ RSpec.shared_examples 'issuable supports timelog creation mutation' do it 'returns an error' do expect do post_graphql_mutation(mutation, current_user: current_user) - end.to change(Timelog, :count).by(0) + end.to change { Timelog.count }.by(0) expect(response).to have_gitlab_http_status(:success) - expect(mutation_response['errors']).to match_array(['Time spent can\'t be blank']) + expect(mutation_response['errors']).to match_array( + ['Time spent must be formatted correctly. For example: 1h 30m.']) expect(mutation_response['timelog']).to be_nil end end @@ -61,6 +74,17 @@ RSpec.shared_examples 'issuable supports timelog creation mutation' do end RSpec.shared_examples 'issuable does not support timelog creation mutation' do + let(:mutation_response) { graphql_mutation_response(:timelog_create) } + let(:mutation) do + variables = { + 'time_spent' => time_spent, + 'spent_at' => '2022-11-16T12:59:35+0100', + 'summary' => 'Test summary', + 'issuable_id' => issuable.to_global_id.to_s + } + graphql_mutation(:timelogCreate, variables) + end + context 'when the user is anonymous' do before do post_graphql_mutation(mutation, current_user: current_user) 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 56c2ca22e15..f672ec7f5ac 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 @@ -5,7 +5,7 @@ RSpec.shared_examples 'update work item description widget' do expect do post_graphql_mutation(mutation, current_user: current_user) work_item.reload - end.to change(work_item, :description).from(nil).to(new_description) + end.to change { work_item.description }.from(nil).to(new_description) expect(response).to have_gitlab_http_status(:success) expect(mutation_response['workItem']['widgets']).to include( diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb index bdd4dbfe209..3ff93371c19 100644 --- a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb +++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb @@ -20,7 +20,7 @@ RSpec.shared_examples 'a Note mutation when the user does not have permission' d it_behaves_like 'a Note mutation that does not create a Note' it_behaves_like 'a mutation that returns top-level errors', - errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action'] + errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action'] end RSpec.shared_examples 'a Note mutation when there are active record validation errors' do |model: Note| @@ -74,7 +74,7 @@ RSpec.shared_examples 'a Note mutation when there are rate limit validation erro it_behaves_like 'a Note mutation that does not create a Note' it_behaves_like 'a mutation that returns top-level errors', - errors: ['This endpoint has been requested too many times. Try again later.'] + errors: ['This endpoint has been requested too many times. Try again later.'] context 'when the user is in the allowlist' do before do @@ -97,3 +97,55 @@ RSpec.shared_examples 'a Note mutation with confidential notes' do expect(mutation_response['note']['internal']).to eq(true) end end + +RSpec.shared_examples 'a Note mutation updates a note successfully' do + it 'updates the Note' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(note.reload.note).to eq(updated_body) + end + + it 'returns the updated Note' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['note']['body']).to eq(updated_body) + end +end + +RSpec.shared_examples 'a Note mutation update with errors' do + context 'when there are ActiveRecord validation errors' do + let(:params) { { body: '', confidential: true } } + + it_behaves_like 'a mutation that returns errors in the response', + errors: ["Note can't be blank", 'Confidential can not be changed for existing notes'] + + it 'does not update the Note' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(note.reload.note).to eq(original_body) + expect(note.confidential).to be_falsey + end + + it 'returns the original Note' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['note']['body']).to eq(original_body) + expect(mutation_response['note']['confidential']).to be_falsey + end + end +end + +RSpec.shared_examples 'a Note mutation update only with quick actions' do + context 'when body only contains quick actions' do + let(:updated_body) { '/close' } + + it 'returns a nil note and empty errors' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response).to include( + 'errors' => [], + 'note' => nil + ) + end + end +end diff --git a/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb b/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb index afa495fc9a4..553e9f10b0d 100644 --- a/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb +++ b/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb @@ -152,4 +152,11 @@ RSpec.shared_examples 'a request using Gitlab::UrlBlocker' do expect(request_stub).to have_been_requested end end + + context 'when a non HTTP/HTTPS URL is provided' do + it 'raises an error' do + expect { make_request('ssh://example.com') } + .to raise_error(ArgumentError) + end + end end diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_shared_examples.rb index 459d4f5cd3e..7141492bd49 100644 --- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_shared_examples.rb @@ -16,20 +16,20 @@ RSpec.shared_examples 'backfill migration for project repositories' do |storage| projects.create!(name: "foo-#{index}", path: "foo-#{index}", namespace_id: group.id, storage_version: storage_version) end - expect { described_class.new.perform(1, projects.last.id) }.to change(project_repositories, :count).by(2) + expect { described_class.new.perform(1, projects.last.id) }.to change { project_repositories.count }.by(2) end it "does nothing for projects on #{storage} storage that have already a project_repository row" do projects.create!(id: 1, name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage_version) project_repositories.create!(project_id: 1, disk_path: 'phony/foo/bar', shard_id: shard.id) - expect { described_class.new.perform(1, projects.last.id) }.not_to change(project_repositories, :count) + expect { described_class.new.perform(1, projects.last.id) }.not_to change { project_repositories.count } end it "does nothing for projects on #{storage == :legacy ? 'hashed' : 'legacy'} storage" do projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage == :legacy ? 1 : nil) - expect { described_class.new.perform(1, projects.last.id) }.not_to change(project_repositories, :count) + expect { described_class.new.perform(1, projects.last.id) }.not_to change { project_repositories.count } end it 'inserts rows in a single query' do diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb index 9ffc55f7e7e..d471a758f3e 100644 --- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb +++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb @@ -62,14 +62,14 @@ RSpec.shared_examples 'deployment metrics examples' do describe '#deployment_frequency' do subject { stage_summary.fourth[:value] } - it 'includes the unit: `/day`' do - expect(stage_summary.fourth[:unit]).to eq _('/day') - end - before do travel_to(5.days.ago) { create_deployment(project: project) } end + it 'includes the unit: `/day`' do + expect(stage_summary.fourth[:unit]).to eq _('/day') + end + it 'returns 0.0 when there were deploys but the frequency was too low' do options[:from] = 30.days.ago diff --git a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb index a28fefcfc58..286f10a186d 100644 --- a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb @@ -28,7 +28,7 @@ RSpec.shared_examples 'finalized background migration' do |worker_class| .new .select do |scheduled| scheduled.klass == worker_class.name && - scheduled.args.first == job_class_name + scheduled.args.first == job_class_name end expect(queued.size).to eq(0) end diff --git a/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb index 40deaa27955..16b048ae325 100644 --- a/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb @@ -25,10 +25,12 @@ RSpec.shared_examples 'handling all upload parameters conditions' do end it 'builds UploadedFiles' do - expect_uploaded_files([ - { filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file1) }, - { filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(file2) } - ]) + expect_uploaded_files( + [ + { filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file1) }, + { filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(file2) } + ] + ) subject end @@ -61,10 +63,12 @@ RSpec.shared_examples 'handling all upload parameters conditions' do end it 'builds UploadedFiles' do - expect_uploaded_files([ - { filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar) }, - { filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user screenshot) } - ]) + expect_uploaded_files( + [ + { filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar) }, + { filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user screenshot) } + ] + ) subject end @@ -101,10 +105,12 @@ RSpec.shared_examples 'handling all upload parameters conditions' do end it 'builds UploadedFiles' do - expect_uploaded_files([ - { filepath: uploaded_file, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar bananas) }, - { filepath: uploaded_file2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user friend ananas) } - ]) + expect_uploaded_files( + [ + { filepath: uploaded_file, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar bananas) }, + { filepath: uploaded_file2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user friend ananas) } + ] + ) subject end @@ -133,11 +139,13 @@ RSpec.shared_examples 'handling all upload parameters conditions' do end it 'builds UploadedFiles' do - expect_uploaded_files([ - { filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file) }, - { filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user avatar) }, - { filepath: uploaded_filepath3, original_filename: filename3, remote_id: remote_id3, size: uploaded_file3.size, params_path: %w(user friend avatar) } - ]) + expect_uploaded_files( + [ + { filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file) }, + { filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user avatar) }, + { filepath: uploaded_filepath3, original_filename: filename3, remote_id: remote_id3, size: uploaded_file3.size, params_path: %w(user friend avatar) } + ] + ) subject end diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb index 481e11bcf0e..d4802a19202 100644 --- a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb @@ -1,38 +1,5 @@ # frozen_string_literal: true -RSpec.shared_examples 'a daily tracked issuable event' do - before do - stub_application_setting(usage_ping_enabled: true) - end - - def count_unique(date_from: 1.minute.ago, date_to: 1.minute.from_now) - Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to) - end - - specify do - aggregate_failures do - expect(track_action(author: user1)).to be_truthy - expect(track_action(author: user1)).to be_truthy - expect(track_action(author: user2)).to be_truthy - expect(count_unique).to eq(2) - end - end - - it 'does not track edit actions if author is not present' do - expect(track_action(author: nil)).to be_nil - end -end - -RSpec.shared_examples 'does not track when feature flag is disabled' do |feature_flag| - context "when feature flag #{feature_flag} is disabled" do - it 'does not track action' do - stub_feature_flags(feature_flag => false) - - expect(track_action(author: user1)).to be_nil - end - end -end - RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events for given event params' do before do stub_application_setting(usage_ping_enabled: true) @@ -76,15 +43,27 @@ end RSpec.shared_examples 'daily tracked issuable snowplow and service ping events with project' do it_behaves_like 'a daily tracked issuable snowplow and service ping events for given event params' do + let(:context) do + Gitlab::Tracking::ServicePingContext + .new(data_source: :redis_hll, event: event_property) + .to_h + end + let(:track_params) { { project: project } } - let(:event_params) { track_params.merge(label: event_label, property: event_property, namespace: project.namespace) } + let(:event_params) { track_params.merge(label: event_label, property: event_property, namespace: project.namespace, context: [context]) } end end RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events with namespace' do it_behaves_like 'a daily tracked issuable snowplow and service ping events for given event params' do + let(:context) do + Gitlab::Tracking::ServicePingContext + .new(data_source: :redis_hll, event: event_property) + .to_h + end + let(:track_params) { { namespace: namespace } } - let(:event_params) { track_params.merge(label: event_label, property: event_property) } + let(:event_params) { track_params.merge(label: event_label, property: event_property, context: [context]) } end end diff --git a/spec/support/shared_examples/mailers/notify_shared_examples.rb b/spec/support/shared_examples/mailers/notify_shared_examples.rb index 919311adc96..b0cbf0b0d65 100644 --- a/spec/support/shared_examples/mailers/notify_shared_examples.rb +++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb @@ -75,7 +75,7 @@ RSpec.shared_examples 'a new thread email with reply-by-email enabled' do aggregate_failures do is_expected.to have_header('Message-ID', "<#{route_key}@#{host}>") - is_expected.to have_header('References', /\A<reply\-.*@#{host}>\Z/ ) + is_expected.to have_header('References', /\A<reply-.*@#{host}>\Z/ ) end end end @@ -91,7 +91,7 @@ RSpec.shared_examples 'a thread answer email with reply-by-email enabled' do aggregate_failures do is_expected.to have_header('Message-ID', /\A<.*@#{host}>\Z/) is_expected.to have_header('In-Reply-To', "<#{route_key}@#{host}>") - is_expected.to have_header('References', /\A<reply\-.*@#{host}> <#{route_key}@#{host}>\Z/ ) + is_expected.to have_header('References', /\A<reply-.*@#{host}> <#{route_key}@#{host}>\Z/ ) is_expected.to have_subject(/^Re: /) end end diff --git a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb index ef4b08c7865..c07d1552ba2 100644 --- a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb +++ b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb @@ -129,8 +129,8 @@ RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1, { db_config_name: db_config_name }) expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_cached_count_total".to_sym, 1, { db_config_name: db_config_name }) if record_cached_query end - else - expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1, { db_config_name: db_config_name }) if db_role + elsif db_role + expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_wal_count_total".to_sym, 1, { db_config_name: db_config_name }) end subscriber.sql(event) 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 a658d02f09a..a20bb794095 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 @@ -6,21 +6,18 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| Gitlab::ApplicationContext.push(feature_category: 'test', caller_id: 'caller') end - it 'defines a Redis counter_key' do - expect(model.counter_key(:counter_name)) - .to eq("project:{#{model.project_id}}:counters:CounterAttributeModel:#{model.id}:counter_name") - end - it 'defines a method to store counters' do - expect(model.class.counter_attributes.to_a).to eq(counter_attributes) + registered_attributes = model.class.counter_attributes.map { |e| e[:attribute] } # rubocop:disable Rails/Pluck + expect(registered_attributes).to contain_exactly(*counter_attributes) end counter_attributes.each do |attribute| describe attribute do - describe '#delayed_increment_counter', :redis do + describe '#increment_counter', :redis do let(:increment) { 10 } + let(:counter_key) { model.counter(attribute).key } - subject { model.delayed_increment_counter(attribute, increment) } + subject { model.increment_counter(attribute, increment) } context 'when attribute is a counter attribute' do where(:increment) { [10, -3] } @@ -44,7 +41,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| subject Gitlab::Redis::SharedState.with do |redis| - counter = redis.get(model.counter_key(attribute)) + counter = redis.get(counter_key) expect(counter).to eq(increment.to_s) end end @@ -55,7 +52,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| it 'schedules a worker to flush counter increments asynchronously' do expect(FlushCounterIncrementsWorker).to receive(:perform_in) - .with(CounterAttribute::WORKER_DELAY, model.class.name, model.id, attribute) + .with(Gitlab::Counters::BufferedCounter::WORKER_DELAY, model.class.name, model.id, attribute) .and_call_original subject @@ -73,128 +70,13 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| end end end - - context 'when attribute is not a counter attribute' do - it 'raises ArgumentError' do - expect { model.delayed_increment_counter(:unknown_attribute, 10) } - .to raise_error(ArgumentError, 'unknown_attribute is not a counter attribute') - end - end - end - end - end - - describe '.flush_increments_to_database!', :redis do - let(:incremented_attribute) { counter_attributes.first } - - subject { model.flush_increments_to_database!(incremented_attribute) } - - it 'obtains an exclusive lease during processing' do - expect(model) - .to receive(:in_lock) - .with(model.counter_lock_key(incremented_attribute), ttl: described_class::WORKER_LOCK_TTL) - .and_call_original - - subject - end - - context 'when there is a counter to flush' do - before do - model.delayed_increment_counter(incremented_attribute, 10) - model.delayed_increment_counter(incremented_attribute, -3) - end - - it 'updates the record and logs it', :aggregate_failures do - expect(Gitlab::AppLogger).to receive(:info).with( - hash_including( - message: 'Acquiring lease for project statistics update', - attributes: [incremented_attribute] - ) - ) - - expect(Gitlab::AppLogger).to receive(:info).with( - hash_including( - message: 'Flush counter attribute to database', - attribute: incremented_attribute, - project_id: model.project_id, - increment: 7, - previous_db_value: 0, - new_db_value: 7, - 'correlation_id' => an_instance_of(String), - 'meta.feature_category' => 'test', - 'meta.caller_id' => 'caller' - ) - ) - - expect { subject }.to change { model.reset.read_attribute(incremented_attribute) }.by(7) - end - - it 'removes the increment entry from Redis' do - Gitlab::Redis::SharedState.with do |redis| - key_exists = redis.exists?(model.counter_key(incremented_attribute)) - expect(key_exists).to be_truthy - end - - subject - - Gitlab::Redis::SharedState.with do |redis| - key_exists = redis.exists?(model.counter_key(incremented_attribute)) - expect(key_exists).to be_falsey - end - end - end - - context 'when there are no counters to flush' do - context 'when there are no counters in the relative :flushed key' do - it 'does not change the record' do - expect { subject }.not_to change { model.reset.attributes } - end - end - - # This can be the case where updating counters in the database fails with error - # and retrying the worker will retry flushing the counters but the main key has - # disappeared and the increment has been moved to the "<...>:flushed" key. - context 'when there are counters in the relative :flushed key' do - before do - Gitlab::Redis::SharedState.with do |redis| - redis.incrby(model.counter_flushed_key(incremented_attribute), 10) - end - end - - it 'updates the record' do - expect { subject }.to change { model.reset.read_attribute(incremented_attribute) }.by(10) - end - - it 'deletes the relative :flushed key' do - subject - - Gitlab::Redis::SharedState.with do |redis| - key_exists = redis.exists?(model.counter_flushed_key(incremented_attribute)) - expect(key_exists).to be_falsey - end - end - end - end - - context 'when deleting :flushed key fails' do - before do - Gitlab::Redis::SharedState.with do |redis| - redis.incrby(model.counter_flushed_key(incremented_attribute), 10) - - expect(redis).to receive(:del).and_raise('could not delete key') - end - end - - it 'does a rollback of the counter update' do - expect { subject }.to raise_error('could not delete key') - - expect(model.reset.read_attribute(incremented_attribute)).to eq(0) end end end describe '#reset_counter!' do let(:attribute) { counter_attributes.first } + let(:counter_key) { model.counter(attribute).key } before do model.update!(attribute => 123) @@ -207,7 +89,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| expect { subject }.to change { model.reload.send(attribute) }.from(123).to(0) Gitlab::Redis::SharedState.with do |redis| - key_exists = redis.exists?(model.counter_key(attribute)) + key_exists = redis.exists?(counter_key) expect(key_exists).to be_falsey end end diff --git a/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb index f0581333b28..2e528f7996c 100644 --- a/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb @@ -5,14 +5,16 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| let_it_be(:project) { create(:project, :repository, :wiki_repo) } let_it_be(:integration) { create(factory, branches_to_be_notified: 'all', project: project) } - before do - stub_request(:post, integration.webhook) + def usage_tracking_key(action) + prefix = integration.send(:metrics_key_prefix) + + "#{prefix}_#{action}_notification" end it 'uses only known events', :aggregate_failures do described_class::SUPPORTED_EVENTS_FOR_USAGE_LOG.each do |action| expect( - Gitlab::UsageDataCounters::HLLRedisCounter.known_event?("i_ecosystem_slack_service_#{action}_notification") + Gitlab::UsageDataCounters::HLLRedisCounter.known_event?(usage_tracking_key(action)) ).to be true end end @@ -20,7 +22,9 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| context 'when hook data includes a user object' do let_it_be(:user) { create_default(:user) } - shared_examples 'increases the usage data counter' do |event_name| + shared_examples 'increases the usage data counter' do |event| + let(:event_name) { usage_tracking_key(event) } + subject(:execute) { integration.execute(data) } it 'increases the usage data counter' do @@ -30,7 +34,7 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| execute end - it_behaves_like 'Snowplow event tracking' do + it_behaves_like 'Snowplow event tracking with RedisHLL context' do let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:category) { described_class.to_s } let(:action) { 'perform_integrations_action' } @@ -47,7 +51,7 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| it 'does not increase the usage data counter' do expect(Gitlab::UsageDataCounters::HLLRedisCounter) - .not_to receive(:track_event).with('i_ecosystem_slack_service_pipeline_notification', values: user.id) + .not_to receive(:track_event).with(usage_tracking_key(:pipeline), values: user.id) integration.execute(data) end @@ -58,13 +62,13 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| let(:data) { issue.to_hook_data(user) } - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_issue_notification' + it_behaves_like 'increases the usage data counter', :issue end context 'for push notification' do let(:data) { Gitlab::DataBuilder::Push.build_sample(project, user) } - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_push_notification' + it_behaves_like 'increases the usage data counter', :push end context 'for deployment notification' do @@ -72,7 +76,7 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.current) } - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_deployment_notification' + it_behaves_like 'increases the usage data counter', :deployment end context 'for wiki_page notification' do @@ -88,7 +92,7 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| allow(project.wiki).to receive(:after_wiki_activity) end - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_wiki_page_notification' + it_behaves_like 'increases the usage data counter', :wiki_page end context 'for merge_request notification' do @@ -96,7 +100,7 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| let(:data) { merge_request.to_hook_data(user) } - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_merge_request_notification' + it_behaves_like 'increases the usage data counter', :merge_request end context 'for note notification' do @@ -104,7 +108,7 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| let(:data) { Gitlab::DataBuilder::Note.build(issue_note, user) } - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_note_notification' + it_behaves_like 'increases the usage data counter', :note end context 'for tag_push notification' do @@ -115,7 +119,7 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| Git::TagHooksService.new(project, user, change: { oldrev: oldrev, newrev: newrev, ref: ref }).send(:push_data) end - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_tag_push_notification' + it_behaves_like 'increases the usage data counter', :tag_push end context 'for confidential note notification' do @@ -125,7 +129,7 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| let(:data) { Gitlab::DataBuilder::Note.build(confidential_issue_note, user) } - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_note_notification' + it_behaves_like 'increases the usage data counter', :confidential_note end context 'for confidential issue notification' do @@ -133,7 +137,7 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| let(:data) { issue.to_hook_data(user) } - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_issue_notification' + it_behaves_like 'increases the usage data counter', :confidential_issue end end diff --git a/spec/support/shared_examples/models/concerns/sanitizable_shared_examples.rb b/spec/support/shared_examples/models/concerns/sanitizable_shared_examples.rb index ed94a71892d..aedbfe4deb3 100644 --- a/spec/support/shared_examples/models/concerns/sanitizable_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/sanitizable_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'sanitizable' do |factory, fields| - let(:attributes) { fields.to_h { |field| [field, input] } } + let(:attributes) { fields.index_with { input } } it 'includes Sanitizable' do expect(described_class).to include(Sanitizable) diff --git a/spec/support/shared_examples/models/concerns/signature_type_shared_examples.rb b/spec/support/shared_examples/models/concerns/signature_type_shared_examples.rb new file mode 100644 index 00000000000..728855b74f8 --- /dev/null +++ b/spec/support/shared_examples/models/concerns/signature_type_shared_examples.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +METHODS = %i[ + gpg? + ssh? + x509? +].freeze + +RSpec.shared_examples 'signature with type checking' do |type| + describe 'signature type checkers' do + where(:method, :expected) do + METHODS.map do |method| + [method, method == "#{type}?".to_sym] + end + end + + with_them do + specify { expect(subject.public_send(method)).to eq(expected) } + end + end +end diff --git a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb index a764d47d7c0..a1269b158a2 100644 --- a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb +++ b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb @@ -65,7 +65,7 @@ RSpec.shared_examples Integrations::HasWebHook do end it 'creates or updates a service hook' do - expect { call }.to change(ServiceHook, :count).by(1) + expect { call }.to change { ServiceHook.count }.by(1) expect(integration.service_hook.url).to eq(hook_url) integration.service_hook.update!(url: 'http://other.com') @@ -98,10 +98,10 @@ RSpec.shared_examples Integrations::HasWebHook do it 'creates the webhook if necessary and executes it' do expect_next(ServiceHook).to receive(:execute).with(*args) - expect { call }.to change(ServiceHook, :count).by(1) + expect { call }.to change { ServiceHook.count }.by(1) expect(integration.service_hook).to receive(:execute).with(*args) - expect { call }.not_to change(ServiceHook, :count) + expect { call }.not_to change { ServiceHook.count } end it 'raises an error if the service hook could not be saved' do diff --git a/spec/support/shared_examples/models/label_note_shared_examples.rb b/spec/support/shared_examples/models/label_note_shared_examples.rb index f61007f57fd..3facd533d7a 100644 --- a/spec/support/shared_examples/models/label_note_shared_examples.rb +++ b/spec/support/shared_examples/models/label_note_shared_examples.rb @@ -30,6 +30,8 @@ RSpec.shared_examples 'label note created from events' do expect(note.noteable).to eq event.issuable expect(note.note).to be_present expect(note.note_html).to be_present + expect(note.created_at).to eq create_event.created_at + expect(note.updated_at).to eq create_event.created_at end it 'updates markdown cache if reference is not set yet' do diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb index 287b046cbec..f8cff5c5558 100644 --- a/spec/support/shared_examples/models/member_shared_examples.rb +++ b/spec/support/shared_examples/models/member_shared_examples.rb @@ -491,7 +491,7 @@ RSpec.shared_examples_for "bulk member creation" do :developer, tasks_to_be_done: %w(issues), tasks_project_id: task_project.id) - end.not_to change(MemberTask, :count) + end.not_to change { MemberTask.count } member.reset expect(member.tasks_to_be_done).to match_array([:code, :ci]) @@ -505,7 +505,7 @@ RSpec.shared_examples_for "bulk member creation" do :developer, tasks_to_be_done: %w(issues), tasks_project_id: task_project.id) - end.to change(MemberTask, :count).by(1) + end.to change { MemberTask.count }.by(1) member = source.members.find_by(user_id: user1.id) expect(member.tasks_to_be_done).to match_array([:issues]) diff --git a/spec/support/shared_examples/models/update_highest_role_shared_examples.rb b/spec/support/shared_examples/models/update_highest_role_shared_examples.rb index 34c4ada1718..1fdd0962fbb 100644 --- a/spec/support/shared_examples/models/update_highest_role_shared_examples.rb +++ b/spec/support/shared_examples/models/update_highest_role_shared_examples.rb @@ -25,7 +25,7 @@ RSpec.shared_examples 'update highest role with exclusive lease' do expect(UpdateHighestRoleWorker).to receive(:perform_in).with(described_class::HIGHEST_ROLE_JOB_DELAY, user_id).and_call_original - expect { subject }.to change(UpdateHighestRoleWorker.jobs, :size).by(1) + expect { subject }.to change { UpdateHighestRoleWorker.jobs.size }.by(1) end end @@ -33,7 +33,7 @@ RSpec.shared_examples 'update highest role with exclusive lease' do it 'only schedules one job' do stub_exclusive_lease_taken(lease_key, timeout: described_class::HIGHEST_ROLE_LEASE_TIMEOUT) - expect { subject }.not_to change(UpdateHighestRoleWorker.jobs, :size) + expect { subject }.not_to change { UpdateHighestRoleWorker.jobs.size } end end end diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb index b81bd514d0a..eb742921d35 100644 --- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb +++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples 'UpdateProjectStatistics' do |with_counter_attribute| def read_pending_increment Gitlab::Redis::SharedState.with do |redis| - key = project.statistics.counter_key(project_statistics_name) + key = project.statistics.counter(project_statistics_name).key redis.get(key).to_i end end @@ -25,7 +25,7 @@ RSpec.shared_examples 'UpdateProjectStatistics' do |with_counter_attribute| def expect_flush_counter_increments_worker_performed expect(FlushCounterIncrementsWorker) .to receive(:perform_in) - .with(CounterAttribute::WORKER_DELAY, project.statistics.class.name, project.statistics.id, project_statistics_name) + .with(Gitlab::Counters::BufferedCounter::WORKER_DELAY, project.statistics.class.name, project.statistics.id, project_statistics_name) yield diff --git a/spec/support/shared_examples/models/with_debian_distributions_shared_examples.rb b/spec/support/shared_examples/models/with_debian_distributions_shared_examples.rb index e86f1e77447..d6071b20dca 100644 --- a/spec/support/shared_examples/models/with_debian_distributions_shared_examples.rb +++ b/spec/support/shared_examples/models/with_debian_distributions_shared_examples.rb @@ -9,9 +9,9 @@ RSpec.shared_examples 'model with Debian distributions' do it 'removes distribution files on removal' do distribution_file_paths = distributions.map do |distribution| [distribution.file.path] + - distribution.component_files.map do |component_file| - component_file.file.path - end + distribution.component_files.map do |component_file| + component_file.file.path + end end.flatten expect { subject.destroy! } diff --git a/spec/support/shared_examples/observability/csp_shared_examples.rb b/spec/support/shared_examples/observability/csp_shared_examples.rb new file mode 100644 index 00000000000..868d7023d14 --- /dev/null +++ b/spec/support/shared_examples/observability/csp_shared_examples.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +# Verifies that the proper CSP rules for Observabilty UI are applied to a given controller/path +# +# The path under test needs to be declared with `let(:tested_path) { .. }` in the context including this example +# +# ``` +# it_behaves_like "observability csp policy" do +# let(:tested_path) { ....the path under test } +# end +# ``` +# +# It optionally supports specifying the controller class handling the tested path as a parameter, e.g. +# +# ``` +# it_behaves_like "observability csp policy", Groups::ObservabilityController +# ``` +# (If not specified it will default to `described_class`) +# +RSpec.shared_examples 'observability csp policy' do |controller_class = described_class| + include ContentSecurityPolicyHelpers + + let(:observability_url) { Gitlab::Observability.observability_url } + let(:signin_url) do + Gitlab::Utils.append_path(Gitlab.config.gitlab.url, + '/users/sign_in') + end + + let(:oauth_url) do + Gitlab::Utils.append_path(Gitlab.config.gitlab.url, + '/oauth/authorize') + end + + before do + setup_csp_for_controller(controller_class, csp, any_time: true) + end + + subject do + get tested_path + response.headers['Content-Security-Policy'] + end + + context 'when there is no CSP config' do + let(:csp) { ActionDispatch::ContentSecurityPolicy.new } + + it 'does not add any csp header' do + expect(subject).to be_blank + end + end + + context 'when frame-src exists in the CSP config' do + let(:csp) do + ActionDispatch::ContentSecurityPolicy.new do |p| + p.frame_src 'https://something.test' + end + end + + it 'appends the proper url to frame-src CSP directives' do + expect(subject).to include( + "frame-src https://something.test #{observability_url} #{signin_url} #{oauth_url}") + end + end + + context 'when signin is already present in the policy' do + let(:csp) do + ActionDispatch::ContentSecurityPolicy.new do |p| + p.frame_src signin_url + end + end + + it 'does not append signin again' do + expect(subject).to include( + "frame-src #{signin_url} #{observability_url} #{oauth_url};") + end + end + + context 'when oauth is already present in the policy' do + let(:csp) do + ActionDispatch::ContentSecurityPolicy.new do |p| + p.frame_src oauth_url + end + end + + it 'does not append oauth again' do + expect(subject).to include( + "frame-src #{oauth_url} #{observability_url} #{signin_url};") + end + end + + context 'when default-src exists in the CSP config' do + let(:csp) do + ActionDispatch::ContentSecurityPolicy.new do |p| + p.default_src 'https://something.test' + end + end + + it 'does not change default-src' do + expect(subject).to include( + "default-src https://something.test;") + end + + it 'appends the proper url to frame-src CSP directives' do + expect(subject).to include( + "frame-src https://something.test #{observability_url} #{signin_url} #{oauth_url}") + end + end + + context 'when frame-src and default-src exist in the CSP config' do + let(:csp) do + ActionDispatch::ContentSecurityPolicy.new do |p| + p.default_src 'https://something_default.test' + p.frame_src 'https://something.test' + end + end + + it 'appends to frame-src CSP directives' do + expect(subject).to include( + "frame-src https://something.test #{observability_url} #{signin_url} #{oauth_url}") + expect(subject).to include( + "default-src https://something_default.test") + end + end +end diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb index cfcc3615e13..15d56c402d1 100644 --- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -2,13 +2,13 @@ RSpec.shared_examples 'archived project policies' do let(:feature_write_abilities) do - described_class.readonly_features.flat_map do |feature| + described_class.archived_features.flat_map do |feature| described_class.create_update_admin_destroy(feature) end + additional_maintainer_permissions end let(:other_write_abilities) do - described_class.readonly_abilities + described_class.archived_abilities end context 'when the project is archived' do diff --git a/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb index f7731af8dc6..f70621673d5 100644 --- a/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb +++ b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'when regex matching everything is specified' do - |service_response_extra: {}, supports_caching: false, delete_expectations:| + |delete_expectations:, service_response_extra: {}, supports_caching: false| let(:params) do { 'name_regex_delete' => '.*' } end @@ -23,6 +23,19 @@ RSpec.shared_examples 'when regex matching everything is specified' do end end +RSpec.shared_examples 'when regex matching everything is specified and latest is not kept' do + |delete_expectations:, service_response_extra: {}, supports_caching: false| + + let(:params) do + { 'name_regex_delete' => '.*', 'keep_latest' => false } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations +end + RSpec.shared_examples 'when delete regex matching specific tags is used' do |service_response_extra: {}, supports_caching: false| let(:params) do @@ -65,7 +78,7 @@ RSpec.shared_examples 'when delete regex matching specific tags is used with ove end RSpec.shared_examples 'with allow regex value' do - |service_response_extra: {}, supports_caching: false, delete_expectations:| + |delete_expectations:, service_response_extra: {}, supports_caching: false| let(:params) do { 'name_regex_delete' => '.*', @@ -80,7 +93,7 @@ RSpec.shared_examples 'with allow regex value' do end RSpec.shared_examples 'when keeping only N tags' do - |service_response_extra: {}, supports_caching: false, delete_expectations:| + |delete_expectations:, service_response_extra: {}, supports_caching: false| let(:params) do { 'name_regex' => 'A|B.*|C', @@ -99,7 +112,7 @@ RSpec.shared_examples 'when keeping only N tags' do end RSpec.shared_examples 'when not keeping N tags' do - |service_response_extra: {}, supports_caching: false, delete_expectations:| + |delete_expectations:, service_response_extra: {}, supports_caching: false| let(:params) do { 'name_regex' => 'A|B.*|C' } end @@ -115,7 +128,7 @@ RSpec.shared_examples 'when not keeping N tags' do end RSpec.shared_examples 'when removing keeping only 3' do - |service_response_extra: {}, supports_caching: false, delete_expectations:| + |delete_expectations:, service_response_extra: {}, supports_caching: false| let(:params) do { 'name_regex_delete' => '.*', 'keep_n' => 3 } @@ -128,7 +141,7 @@ RSpec.shared_examples 'when removing keeping only 3' do end RSpec.shared_examples 'when removing older than 1 day' do - |service_response_extra: {}, supports_caching: false, delete_expectations:| + |delete_expectations:, service_response_extra: {}, supports_caching: false| let(:params) do { 'name_regex_delete' => '.*', @@ -143,7 +156,7 @@ RSpec.shared_examples 'when removing older than 1 day' do end RSpec.shared_examples 'when combining all parameters' do - |service_response_extra: {}, supports_caching: false, delete_expectations:| + |delete_expectations:, service_response_extra: {}, supports_caching: false| let(:params) do { 'name_regex_delete' => '.*', @@ -159,7 +172,7 @@ RSpec.shared_examples 'when combining all parameters' do end RSpec.shared_examples 'when running a container_expiration_policy' do - |service_response_extra: {}, supports_caching: false, delete_expectations:| + |delete_expectations:, service_response_extra: {}, supports_caching: false| let(:user) { nil } context 'with valid container_expiration_policy param' do @@ -191,7 +204,7 @@ RSpec.shared_examples 'not removing anything' do |service_response_extra: {}, su end RSpec.shared_examples 'removing the expected tags' do - |service_response_extra: {}, supports_caching: false, delete_expectations:| + |delete_expectations:, service_response_extra: {}, supports_caching: false| it 'removes the expected tags' do delete_expectations.each { |expectation| expect_delete(expectation) } expect_no_caching unless supports_caching diff --git a/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb index 4d142199c95..1cd529aa50b 100644 --- a/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb @@ -1,21 +1,23 @@ # frozen_string_literal: true RSpec.shared_examples 'issuable quick actions' do - QuickAction = Struct.new(:action_text, :expectation, :before_action, keyword_init: true) do - # Pass a block as :before_action if - # issuable state needs to be changed before - # the quick action is executed. - def call_before_action - before_action.call if before_action - end + before do + stub_const('QuickAction', Struct.new(:action_text, :expectation, :before_action, keyword_init: true) do + # Pass a block as :before_action if + # issuable state needs to be changed before + # the quick action is executed. + def call_before_action + before_action.call if before_action + end - def skip_access_check - action_text["/todo"] || - action_text["/done"] || - action_text["/subscribe"] || - action_text["/shrug"] || - action_text["/tableflip"] - end + def skip_access_check + action_text["/todo"] || + action_text["/done"] || + action_text["/subscribe"] || + action_text["/shrug"] || + action_text["/tableflip"] + end + end) end let(:unlabel_expectation) do @@ -145,6 +147,12 @@ RSpec.shared_examples 'issuable quick actions' do } ), QuickAction.new( + action_text: "/labels ~feature", + expectation: ->(noteable, can_use_quick_action) { + expect(noteable.labels&.last&.id == feature_label.id).to eq(can_use_quick_action) + } + ), + QuickAction.new( action_text: "/unlabel", expectation: unlabel_expectation ), diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb index 18304951e41..56a1cee44c8 100644 --- a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb @@ -22,6 +22,12 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| end end + def open_create_timelog_form + page.within time_tracker_selector do + find('[data-testid="add-time-entry-button"]').click + end + end + it 'renders the sidebar component empty state' do page.within '[data-testid="noTrackingPane"]' do expect(page).to have_content 'No estimate or time spent' @@ -74,11 +80,13 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| end end - it 'shows the help state when icon is clicked' do - page.within time_tracker_selector do - find('[data-testid="helpButton"]').click - expect(page).to have_content 'Track time with quick actions' - expect(page).to have_content 'Learn more' + it 'shows the create timelog form when add button is clicked' do + open_create_timelog_form + + page.within '[data-testid="create-timelog-modal"]' do + expect(page).to have_content 'Add time entry' + expect(page).to have_content 'Time spent' + expect(page).to have_content 'Spent at' end end @@ -123,24 +131,6 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| expect(page).to have_content '1d' end end - - it 'hides the help state when close icon is clicked' do - page.within time_tracker_selector do - find('[data-testid="helpButton"]').click - find('[data-testid="closeHelpButton"]').click - - expect(page).not_to have_content 'Track time with quick actions' - expect(page).not_to have_content 'Learn more' - end - end - - it 'displays the correct help url' do - page.within time_tracker_selector do - find('[data-testid="helpButton"]').click - - expect(find_link('Learn more')[:href]).to have_content('/help/user/project/time_tracking.md') - end - end end def submit_time(quick_action) diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb index 629d93676eb..c76bc7c3107 100644 --- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb @@ -593,7 +593,7 @@ RSpec.shared_examples 'delete package endpoint' do project.add_maintainer(user) end - it_behaves_like 'a gitlab tracking event', 'API::ConanPackages', 'delete_package' + it_behaves_like 'a package tracking event', 'API::ConanPackages', 'delete_package' it 'deletes a package' do expect { subject }.to change { Packages::Package.count }.from(2).to(1) @@ -708,7 +708,7 @@ RSpec.shared_examples 'package file download endpoint' do context 'tracking the conan_package.tgz download' do let(:package_file) { package.package_files.find_by(file_name: ::Packages::Conan::FileMetadatum::PACKAGE_BINARY) } - it_behaves_like 'a gitlab tracking event', 'API::ConanPackages', 'pull_package' + it_behaves_like 'a package tracking event', 'API::ConanPackages', 'pull_package' end end @@ -781,7 +781,7 @@ RSpec.shared_examples 'workhorse package file upload endpoint' do context 'tracking the conan_package.tgz upload' do let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY } - it_behaves_like 'a gitlab tracking event', 'API::ConanPackages', 'push_package' + it_behaves_like 'a package tracking event', 'API::ConanPackages', 'push_package' end end @@ -849,12 +849,6 @@ RSpec.shared_examples 'uploads a package file' do expect(package_file.file_name).to eq(params[:file].original_filename) end - it "doesn't attempt to migrate file to object storage" do - expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async) - - subject - end - context 'with existing package' do let!(:existing_package) { create(:conan_package, name: 'foo', version: 'bar', project: project) } @@ -936,8 +930,6 @@ RSpec.shared_examples 'uploads a package file' do end end end - - it_behaves_like 'background upload schedules a file migration' 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 5469fd80a4f..d4479e462af 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 @@ -1,6 +1,15 @@ # frozen_string_literal: true RSpec.shared_examples 'graphql issue list request spec' do + let(:issue_ids) { graphql_dig_at(issues_data, :id) } + let(:fields) do + <<~QUERY + nodes { + #{all_graphql_fields_for('issues'.classify)} + } + QUERY + end + it_behaves_like 'a working graphql query' do before do post_query @@ -109,10 +118,57 @@ RSpec.shared_examples 'graphql issue list request spec' do let(:ids) { issue_ids } end end + + context 'when filtering by confidentiality' do + context 'when fetching confidential issues' do + let(:issue_filter_params) { { confidential: true } } + + it 'returns only confidential issues' do + post_query + + expect(issue_ids).to match_array(to_gid_list(confidential_issues)) + end + + context 'when user cannot see confidential issues' do + it 'returns an empty list' do + post_query(external_user) + + expect(issue_ids).to be_empty + end + end + end + + context 'when fetching non-confidential issues' do + let(:issue_filter_params) { { confidential: false } } + + it 'returns only non-confidential issues' do + post_query + + expect(issue_ids).to match_array(to_gid_list(non_confidential_issues)) + end + + context 'when user cannot see confidential issues' do + it 'returns an empty list' do + post_query(external_user) + + expect(issue_ids).to match_array(to_gid_list(public_non_confidential_issues)) + end + end + end + end end describe 'sorting and pagination' do context 'when sorting by severity' do + let(:expected_severity_sorted_asc) { [issue_c, issue_a, issue_b, issue_e, issue_d] } + + before_all do + create(:issuable_severity, issue: issue_a, severity: :unknown) + create(:issuable_severity, issue: issue_b, severity: :low) + create(:issuable_severity, issue: issue_d, severity: :critical) + create(:issuable_severity, issue: issue_e, severity: :high) + end + context 'when ascending' do it_behaves_like 'sorted paginated query' do let(:sort_param) { :SEVERITY_ASC } @@ -147,6 +203,459 @@ RSpec.shared_examples 'graphql issue list request spec' do end end end + + context 'when sorting by due date' do + context 'when ascending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :DUE_DATE_ASC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_due_date_sorted_asc) } + end + end + + context 'when descending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :DUE_DATE_DESC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_due_date_sorted_desc) } + end + end + end + + context 'when sorting by relative position' do + context 'when ascending' do + it_behaves_like 'sorted paginated query', is_reversible: true do + let(:sort_param) { :RELATIVE_POSITION_ASC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_relative_position_sorted_asc) } + end + end + end + + context 'when sorting by label priority' do + context 'when ascending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :LABEL_PRIORITY_ASC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_label_priority_sorted_asc) } + end + end + + context 'when descending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :LABEL_PRIORITY_DESC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_label_priority_sorted_desc) } + end + end + end + + context 'when sorting by milestone due date' do + context 'when ascending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :MILESTONE_DUE_ASC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_milestone_sorted_asc) } + end + end + + context 'when descending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :MILESTONE_DUE_DESC } + let(:first_param) { 2 } + let(:all_records) { to_gid_list(expected_milestone_sorted_desc) } + end + end + end + end + + describe 'N+1 query checks' do + let(:extra_iid_for_second_query) { issue_b.iid.to_s } + let(:search_params) { { iids: [issue_a.iid.to_s] } } + let(:issue_filter_params) { search_params } + let(:fields) do + <<~QUERY + nodes { + id + #{requested_fields} + } + QUERY + end + + def execute_query + post_query + end + + context 'when requesting `user_notes_count` and `user_discussions_count`' do + let(:requested_fields) { 'userNotesCount userDiscussionsCount' } + + before do + create_list(:note_on_issue, 2, noteable: issue_a, project: issue_a.project) + create(:note_on_issue, noteable: issue_b, project: issue_b.project) + end + + include_examples 'N+1 query check' + end + + context 'when requesting `merge_requests_count`' do + let(:requested_fields) { 'mergeRequestsCount' } + + before do + create_list(:merge_requests_closing_issues, 2, issue: issue_a) + create_list(:merge_requests_closing_issues, 3, issue: issue_b) + end + + include_examples 'N+1 query check' + end + + context 'when requesting `timelogs`' do + let(:requested_fields) { 'timelogs { nodes { timeSpent } }' } + + before do + create_list(:issue_timelog, 2, issue: issue_a) + create(:issue_timelog, issue: issue_b) + end + + include_examples 'N+1 query check' + end + + context 'when requesting `closed_as_duplicate_of`' do + let(:requested_fields) { 'closedAsDuplicateOf { id }' } + let(:issue_a_dup) { create(:issue, project: issue_a.project) } + let(:issue_b_dup) { create(:issue, project: issue_b.project) } + + before do + issue_a.update!(duplicated_to_id: issue_a_dup) + issue_b.update!(duplicated_to_id: issue_a_dup) + end + + include_examples 'N+1 query check' + end + + context 'when award emoji votes' do + let(:requested_fields) { 'upvotes downvotes' } + + before do + create_list(:award_emoji, 2, name: 'thumbsup', awardable: issue_a) + create_list(:award_emoji, 2, name: 'thumbsdown', awardable: issue_b) + end + + include_examples 'N+1 query check' + end + + context 'when requesting participants' do + let(:search_params) { { iids: [issue_a.iid.to_s, issue_c.iid.to_s] } } + let(:requested_fields) { 'participants { nodes { name } }' } + + before do + create(:award_emoji, :upvote, awardable: issue_a) + create(:award_emoji, :upvote, awardable: issue_b) + create(:award_emoji, :upvote, awardable: issue_c) + + note_with_emoji_a = create(:note_on_issue, noteable: issue_a, project: issue_a.project) + note_with_emoji_b = create(:note_on_issue, noteable: issue_b, project: issue_b.project) + note_with_emoji_c = create(:note_on_issue, noteable: issue_c, project: issue_c.project) + + create(:award_emoji, :upvote, awardable: note_with_emoji_a) + create(:award_emoji, :upvote, awardable: note_with_emoji_b) + create(:award_emoji, :upvote, awardable: note_with_emoji_c) + end + + # Executes 3 extra queries to fetch participant_attrs + include_examples 'N+1 query check', threshold: 3 + end + + context 'when requesting labels', :use_sql_query_cache do + let(:requested_fields) { 'labels { nodes { id } }' } + let(:extra_iid_for_second_query) { same_project_issue2.iid.to_s } + let(:search_params) { { iids: [same_project_issue1.iid.to_s] } } + + before do + current_project = same_project_issue1.project + project_labels = create_list(:label, 2, project: current_project) + group_labels = create_list(:group_label, 2, group: current_project.group) + + same_project_issue1.update!(labels: [project_labels.first, group_labels.first].flatten) + same_project_issue2.update!(labels: [project_labels, group_labels].flatten) + end + + include_examples 'N+1 query check', skip_cached: false + end + end + + context 'when confidential issues exist' do + context 'when user can see confidential issues' do + it 'includes confidential issues' do + post_query + + all_issues = confidential_issues + non_confidential_issues + + expect(issue_ids).to match_array(to_gid_list(all_issues)) + expect(issues_data.pluck('confidential')).to match_array(all_issues.map(&:confidential)) + end + end + + context 'when user cannot see confidential issues' do + let(:current_user) { external_user } + + it 'does not include confidential issues' do + post_query + + expect(issue_ids).to match_array(to_gid_list(public_non_confidential_issues)) + end + end + end + + context 'when limiting the number of results' do + let(:issue_limit) { 1 } + let(:issue_filter_params) { { first: issue_limit } } + + it_behaves_like 'a working graphql query' do + before do + post_query + end + + it 'only returns N issues' do + expect(issues_data.size).to eq(issue_limit) + end + end + + context 'when no limit is provided' do + let(:issue_limit) { nil } + + it 'returns all issues' do + post_query + + expect(issues_data.size).to be > 1 + end + end + + it 'is expected to check permissions on the first issue only' do + allow(Ability).to receive(:allowed?).and_call_original + # Newest first, we only want to see the newest checked + expect(Ability).not_to receive(:allowed?).with(current_user, :read_issue, issues.first) + + post_query + end + end + + context 'when the user does not have access to the issue' do + let(:current_user) { external_user } + + it 'returns no issues' do + public_projects.each do |public_project| + public_project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) + end + + post_query + + expect(issues_data).to eq([]) + end + end + + context 'when fetching escalation status' do + let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue_a) } + + let(:fields) do + <<~QUERY + nodes { + id + escalationStatus + } + QUERY + end + + before do + issue_a.update_columns(issue_type: Issue.issue_types[:incident]) + end + + it 'returns the escalation status values' do + post_query + + statuses = issues_data.pluck('escalationStatus') + + expect(statuses).to contain_exactly(escalation_status.status_name.upcase.to_s, nil, nil, nil, nil) + end + + it 'avoids N+1 queries', :aggregate_failures do + control = ActiveRecord::QueryRecorder.new { run_with_clean_state(query, context: { current_user: current_user }) } + + new_incident = create(:incident, project: public_projects.first) + create(:incident_management_issuable_escalation_status, issue: new_incident) + + expect { run_with_clean_state(query, context: { current_user: current_user }) }.not_to exceed_query_limit(control) + end + end + + context 'when fetching alert management alert' do + let(:fields) do + <<~QUERY + nodes { + iid + alertManagementAlert { + title + } + alertManagementAlerts { + nodes { + title + } + } + } + QUERY + end + + it 'avoids N+1 queries' do + control = ActiveRecord::QueryRecorder.new { post_query } + + create(:alert_management_alert, :with_incident, project: public_projects.first) + + expect { post_query }.not_to exceed_query_limit(control) + end + + it 'returns the alert data' do + post_query + + alert_titles = issues_data.map { |issue| issue.dig('alertManagementAlert', 'title') } + expected_titles = issues.map { |issue| issue.alert_management_alerts.first&.title } + + expect(alert_titles).to contain_exactly(*expected_titles) + end + + it 'returns the alerts data' do + post_query + + alert_titles = issues_data.map { |issue| issue.dig('alertManagementAlerts', 'nodes') } + expected_titles = issues.map do |issue| + issue.alert_management_alerts.map { |alert| { 'title' => alert.title } } + end + + expect(alert_titles).to contain_exactly(*expected_titles) + end + end + + context 'when fetching customer_relations_contacts' do + let(:fields) do + <<~QUERY + nodes { + id + customerRelationsContacts { + nodes { + firstName + } + } + } + QUERY + end + + def clean_state_query + run_with_clean_state(query, context: { current_user: current_user }) + end + + it 'avoids N+1 queries' do + create(:issue_customer_relations_contact, :for_issue, issue: issue_a) + + control = ActiveRecord::QueryRecorder.new(skip_cached: false) { clean_state_query } + + create(:issue_customer_relations_contact, :for_issue, issue: issue_a) + + expect { clean_state_query }.not_to exceed_all_query_limit(control) + end + end + + context 'when fetching labels' do + let(:fields) do + <<~QUERY + nodes { + id + labels { + nodes { + id + } + } + } + QUERY + end + + before do + issues.each do |issue| + # create a label for each issue we have to properly test N+1 + label = create(:label, project: issue.project) + issue.update!(labels: [label]) + end + end + + def response_label_ids(response_data) + response_data.map do |node| + node['labels']['nodes'].pluck('id') + end.flatten + end + + def labels_as_global_ids(issues) + issues.map(&:labels).flatten.map(&:to_global_id).map(&:to_s) + end + + it 'avoids N+1 queries', :aggregate_failures do + control = ActiveRecord::QueryRecorder.new { post_query } + expect(issues_data.count).to eq(5) + expect(response_label_ids(issues_data)).to match_array(labels_as_global_ids(issues)) + + public_project = public_projects.first + new_issues = issues + [ + create(:issue, project: public_project, labels: [create(:label, project: public_project)]) + ] + + expect { post_query }.not_to exceed_query_limit(control) + + expect(issues_data.count).to eq(6) + expect(response_label_ids(issues_data)).to match_array(labels_as_global_ids(new_issues)) + end + end + + context 'when fetching assignees' do + let(:fields) do + <<~QUERY + nodes { + id + assignees { + nodes { + id + } + } + } + QUERY + end + + before do + issues.each do |issue| + # create an assignee for each issue we have to properly test N+1 + assignee = create(:user) + issue.update!(assignees: [assignee]) + end + end + + def response_assignee_ids(response_data) + response_data.map do |node| + node['assignees']['nodes'].pluck('id') + end.flatten + end + + def assignees_as_global_ids(issues) + issues.map(&:assignees).flatten.map(&:to_global_id).map(&:to_s) + end + + it 'avoids N+1 queries', :aggregate_failures do + control = ActiveRecord::QueryRecorder.new { post_query } + expect(issues_data.count).to eq(5) + expect(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(issues)) + + public_project = public_projects.first + new_issues = issues + [create(:issue, project: public_project, assignees: [create(:user)])] + + expect { post_query }.not_to exceed_query_limit(control) + + expect(issues_data.count).to eq(6) + expect(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(new_issues)) + end end it 'includes a web_url' do @@ -167,4 +676,8 @@ RSpec.shared_examples 'graphql issue list request spec' do def to_gid_list(instance_list) instance_list.map { |instance| instance.to_gid.to_s } end + + def issues_data + graphql_data.dig(*issue_nodes_path) + end end diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb index fb4aacfd7a9..f5835460a77 100644 --- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb @@ -62,6 +62,21 @@ RSpec.shared_examples 'group and project packages query' do it 'returns the count of the packages' do expect(packages_count).to eq(4) end + + context '_links' do + let_it_be(:errored_package) { create(:maven_package, :error, project: project1) } + + let(:package_web_paths) { graphql_data_at(resource_type, :packages, :nodes, :_links, :web_path) } + + it 'does not contain the web path of errored package' do + expect(package_web_paths.compact).to contain_exactly( + "/#{project1.full_path}/-/packages/#{npm_package.id}", + "/#{project1.full_path}/-/packages/#{maven_package.id}", + "/#{project2.full_path}/-/packages/#{debian_package.id}", + "/#{project2.full_path}/-/packages/#{composer_package.id}" + ) + end + end end context 'when the user does not have access to the resource' do @@ -139,7 +154,7 @@ RSpec.shared_examples 'group and project packages query' do end it 'throws an error' do - expect_graphql_errors_to_include(/Argument \'sort\' on Field \'packages\' has an invalid value/) + expect_graphql_errors_to_include(/Argument 'sort' on Field 'packages' has an invalid value/) end end diff --git a/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb index 54cc13fac94..6b4d8cae2ce 100644 --- a/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -RSpec.shared_examples 'perform graphql requests for AccessLevel type objects' do |access_level_kind| +RSpec.shared_examples 'a GraphQL query for access levels' do |access_level_kind| include GraphqlHelpers let_it_be(:project) { create(:project) } let_it_be(:current_user) { create(:user, maintainer_projects: [project]) } let_it_be(:variables) { { path: project.full_path } } - let(:fields) { all_graphql_fields_for("#{access_level_kind.to_s.classify}AccessLevel", max_depth: 2) } + let(:fields) { all_graphql_fields_for("#{access_level_kind.to_s.classify}AccessLevel") } let(:access_levels) { protected_branch.public_send("#{access_level_kind}_access_levels") } let(:access_levels_count) { access_levels.size } let(:maintainer_access_level) { access_levels.for_role.first } @@ -61,17 +61,35 @@ RSpec.shared_examples 'perform graphql requests for AccessLevel type objects' do create(:protected_branch, "maintainers_can_#{access_level_kind}", project: project) end - before do - post_graphql(query, current_user: current_user, variables: variables) + describe 'query' do + it 'avoids N+1 queries' do + control = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: current_user, variables: variables) + end + expect_graphql_errors_to_be_empty + + create("protected_branch_#{access_level_kind}_access_level", protected_branch: protected_branch) + + expect do + post_graphql(query, current_user: current_user, variables: variables) + end.not_to exceed_all_query_limit(control) + expect_graphql_errors_to_be_empty + end end - it_behaves_like 'a working graphql query' + describe 'response' do + before do + post_graphql(query, current_user: current_user, variables: variables) + end + + it_behaves_like 'a working graphql query' - it 'returns all the access level attributes' do - expect(maintainer_access_level_data['accessLevel']).to eq(maintainer_access_level.access_level) - expect(maintainer_access_level_data['accessLevelDescription']).to eq(maintainer_access_level.humanize) - expect(maintainer_access_level_data.dig('group', 'name')).to be_nil - expect(maintainer_access_level_data.dig('user', 'name')).to be_nil + it 'returns all the access level attributes' do + expect(maintainer_access_level_data['accessLevel']).to eq(maintainer_access_level.access_level) + expect(maintainer_access_level_data['accessLevelDescription']).to eq(maintainer_access_level.humanize) + expect(maintainer_access_level_data.dig('group', 'name')).to be_nil + expect(maintainer_access_level_data.dig('user', 'name')).to be_nil + end end end end diff --git a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb index 8bf6b162508..7803f0ff04d 100644 --- a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb @@ -156,25 +156,13 @@ RSpec.shared_examples 'process helm upload' do |user_type, status| end context 'and direct upload disabled' do - context 'and background upload disabled' do - let(:fog_connection) do - stub_package_file_object_storage(direct_upload: false, background_upload: false) - end - - it_behaves_like 'creates helm package files' + let(:fog_connection) do + stub_package_file_object_storage(direct_upload: false) end - context 'and background upload enabled' do - let(:fog_connection) do - stub_package_file_object_storage(direct_upload: false, background_upload: true) - end - - it_behaves_like 'creates helm package files' - end + it_behaves_like 'creates helm package files' end end - - it_behaves_like 'background upload schedules a file migration' end end diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb index 11f9565989f..efe5ed3bcf9 100644 --- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb @@ -159,7 +159,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| expect do post api(uri, user), params: { body: 'hi!' } - end.to change(Event, :count).by(1) + end.to change { Event.count }.by(1) end context 'setting created_at' do diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb index 85ac2b5e1ea..b55639a6b82 100644 --- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb @@ -323,6 +323,171 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project| end end +RSpec.shared_examples 'handling audit request' do |path:, scope: :project| + using RSpec::Parameterized::TableSyntax + + let(:headers) { {} } + let(:params) do + ActiveSupport::Gzip.compress( + Gitlab::Json.dump({ + '@gitlab-org/npm-test': ['1.0.6'], + 'call-bind': ['1.0.2'] + }) + ) + end + + let(:default_headers) do + { 'HTTP_CONTENT_ENCODING' => 'gzip', 'CONTENT_TYPE' => 'application/json' } + end + + subject { post(url, headers: headers.merge(default_headers), params: params) } + + shared_examples 'accept audit request' do |status:| + it 'accepts the audit request' do + subject + + expect(response).to have_gitlab_http_status(status) + expect(response.media_type).to eq('application/json') + expect(json_response).to eq([]) + end + end + + shared_examples 'reject audit request' do |status:| + it 'rejects the audit request' do + subject + + expect(response).to have_gitlab_http_status(status) + end + end + + shared_examples 'redirect audit request' do |status:| + it 'redirects audit request' do + subject + + expect(response).to have_gitlab_http_status(status) + expect(response.headers['Location']).to eq("https://registry.npmjs.org/-/npm/v1/security/#{path}") + end + end + + shared_examples 'handling all conditions' do + include_context 'dependency proxy helpers context' + + where(:auth, :request_forward, :visibility, :user_role, :expected_result, :expected_status) do + nil | true | :public | nil | :reject | :unauthorized + nil | false | :public | nil | :reject | :unauthorized + nil | true | :private | nil | :reject | :unauthorized + nil | false | :private | nil | :reject | :unauthorized + nil | true | :internal | nil | :reject | :unauthorized + nil | false | :internal | nil | :reject | :unauthorized + + :oauth | true | :public | :guest | :redirect | :temporary_redirect + :oauth | true | :public | :reporter | :redirect | :temporary_redirect + :oauth | false | :public | :guest | :accept | :ok + :oauth | false | :public | :reporter | :accept | :ok + :oauth | true | :private | :reporter | :redirect | :temporary_redirect + :oauth | false | :private | :guest | :reject | :forbidden + :oauth | false | :private | :reporter | :accept | :ok + :oauth | true | :private | :guest | :redirect | :temporary_redirect + :oauth | true | :internal | :guest | :redirect | :temporary_redirect + :oauth | true | :internal | :reporter | :redirect | :temporary_redirect + :oauth | false | :internal | :guest | :accept | :ok + :oauth | false | :internal | :reporter | :accept | :ok + + :personal_access_token | true | :public | :guest | :redirect | :temporary_redirect + :personal_access_token | true | :public | :reporter | :redirect | :temporary_redirect + :personal_access_token | false | :public | :guest | :accept | :ok + :personal_access_token | false | :public | :reporter | :accept | :ok + :personal_access_token | true | :private | :guest | :redirect | :temporary_redirect + :personal_access_token | true | :private | :reporter | :redirect | :temporary_redirect + :personal_access_token | false | :private | :guest | :reject | :forbidden # instance might fail + :personal_access_token | false | :private | :reporter | :accept | :ok + :personal_access_token | true | :internal | :guest | :redirect | :temporary_redirect + :personal_access_token | true | :internal | :reporter | :redirect | :temporary_redirect + :personal_access_token | false | :internal | :guest | :accept | :ok + :personal_access_token | false | :internal | :reporter | :accept | :ok + + :job_token | true | :public | :developer | :redirect | :temporary_redirect + :job_token | false | :public | :developer | :accept | :ok + :job_token | true | :private | :developer | :redirect | :temporary_redirect + :job_token | false | :private | :developer | :accept | :ok + :job_token | true | :internal | :developer | :redirect | :temporary_redirect + :job_token | false | :internal | :developer | :accept | :ok + + :deploy_token | true | :public | nil | :redirect | :temporary_redirect + :deploy_token | false | :public | nil | :accept | :ok + :deploy_token | true | :private | nil | :redirect | :temporary_redirect + :deploy_token | false | :private | nil | :accept | :ok + :deploy_token | true | :internal | nil | :redirect | :temporary_redirect + :deploy_token | false | :internal | nil | :accept | :ok + end + + with_them do + let(:headers) do + case auth + when :oauth + build_token_auth_header(token.plaintext_token) + when :personal_access_token + build_token_auth_header(personal_access_token.token) + when :job_token + build_token_auth_header(job.token) + when :deploy_token + build_token_auth_header(deploy_token.token) + else + {} + end + end + + before do + project.send("add_#{user_role}", user) if user_role + project.update!(visibility: visibility.to_s) + + if scope == :instance + allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward) + else + allow_fetch_cascade_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward) + end + end + + example_name = "#{params[:expected_result]} audit request" + status = params[:expected_status] + + if scope == :instance && params[:expected_status] != :unauthorized + if params[:request_forward] + example_name = 'redirect audit request' + status = :temporary_redirect + else + example_name = 'reject audit request' + status = :not_found + end + end + + it_behaves_like example_name, status: status + end + end + + context 'with a group namespace' do + it_behaves_like 'handling all conditions' + end + + context 'with a developer' do + let(:headers) { build_token_auth_header(personal_access_token.token) } + + before do + project.add_developer(user) + end + + context 'with a job token' do + let(:headers) { build_token_auth_header(job.token) } + + before do + job.update!(status: :success) + end + + it_behaves_like 'reject audit request', status: :unauthorized + end + end +end + RSpec.shared_examples 'handling get dist tags requests' do |scope: :project| using RSpec::Parameterized::TableSyntax include_context 'set package name from package name type' diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb index fdd55893deb..bace570e47a 100644 --- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb @@ -224,25 +224,13 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = end context 'and direct upload disabled' do - context 'and background upload disabled' do - let(:fog_connection) do - stub_package_file_object_storage(direct_upload: false, background_upload: false) - end - - it_behaves_like 'creates nuget package files' + let(:fog_connection) do + stub_package_file_object_storage(direct_upload: false) end - context 'and background upload enabled' do - let(:fog_connection) do - stub_package_file_object_storage(direct_upload: false, background_upload: true) - end - - it_behaves_like 'creates nuget package files' - end + it_behaves_like 'creates nuget package files' end end - - it_behaves_like 'background upload schedules a file migration' end end @@ -507,7 +495,7 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false| let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } let(:headers) { user_headers.merge(workhorse_headers) } - let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace } } + let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace, property: 'i_package_nuget_user' } } before do update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false)) diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb index 860cb1b1d86..98264baa61d 100644 --- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb @@ -142,15 +142,26 @@ RSpec.shared_examples 'job token for package uploads' do |authorize_endpoint: fa end end -RSpec.shared_examples 'a package tracking event' do |category, action| +RSpec.shared_examples 'a package tracking event' do |category, action, service_ping_context = true| before do stub_feature_flags(collect_package_events: true) end + let(:context) do + [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, + event: snowplow_gitlab_standard_context[:property]).to_h] + end + it "creates a gitlab tracking event #{action}", :snowplow, :aggregate_failures do expect { subject }.to change { Packages::Event.count }.by(1) - expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context) + if service_ping_context + expect_snowplow_event(category: category, action: action, + label: "redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly", + context: context, **snowplow_gitlab_standard_context) + else + expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context) + end end end diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb index a9b44015206..a267476b7cb 100644 --- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb @@ -95,25 +95,13 @@ RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member end context 'and direct upload disabled' do - context 'and background upload disabled' do - let(:fog_connection) do - stub_package_file_object_storage(direct_upload: false, background_upload: false) - end - - it_behaves_like 'creating pypi package files' + let(:fog_connection) do + stub_package_file_object_storage(direct_upload: false) end - context 'and background upload enabled' do - let(:fog_connection) do - stub_package_file_object_storage(direct_upload: false, background_upload: true) - end - - it_behaves_like 'creating pypi package files' - end + it_behaves_like 'creating pypi package files' end end - - it_behaves_like 'background upload schedules a file migration' end end @@ -285,7 +273,7 @@ RSpec.shared_examples 'pypi simple API endpoint' do let(:url) { "/projects/#{project.id}/packages/pypi/simple/my-package" } let(:headers) { basic_auth_header(user.username, personal_access_token.token) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: group, property: 'i_package_pypi_user' } } it_behaves_like 'PyPI package versions', :developer, :success end diff --git a/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb b/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb index 2d036cb2aa3..2154a76d765 100644 --- a/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb @@ -71,11 +71,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| get_container_repository_storage_moves json_ids = json_response.map { |storage_move| storage_move['id'] } - expect(json_ids).to eq([ - storage_move.id, - storage_move_middle.id, - storage_move_oldest.id - ]) + expect(json_ids).to eq([storage_move.id, storage_move_middle.id, storage_move_oldest.id]) end describe 'permissions' do diff --git a/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb index f075927e7bf..da09d70c777 100644 --- a/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb @@ -122,21 +122,11 @@ RSpec.shared_examples 'process rubygems upload' do |user_type, status, add_membe end context 'and direct upload disabled' do - context 'and background upload disabled' do - let(:fog_connection) do - stub_package_file_object_storage(direct_upload: false, background_upload: false) - end - - it_behaves_like 'creates rubygems package files' + let(:fog_connection) do + stub_package_file_object_storage(direct_upload: false) end - context 'and background upload enabled' do - let(:fog_connection) do - stub_package_file_object_storage(direct_upload: false, background_upload: true) - end - - it_behaves_like 'creates rubygems package files' - end + it_behaves_like 'creates rubygems package files' end end end diff --git a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb index bdff2c65691..ae2855083f6 100644 --- a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb @@ -264,21 +264,11 @@ RSpec.shared_examples 'process terraform module upload' do |user_type, status, a end context 'and direct upload disabled' do - context 'and background upload disabled' do - let(:fog_connection) do - stub_package_file_object_storage(direct_upload: false, background_upload: false) - end - - it_behaves_like 'creates terraform module package files' + let(:fog_connection) do + stub_package_file_object_storage(direct_upload: false) end - context 'and background upload enabled' do - let(:fog_connection) do - stub_package_file_object_storage(direct_upload: false, background_upload: true) - end - - it_behaves_like 'creates terraform module package files' - end + it_behaves_like 'creates terraform module package files' end end end diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb index 11759b6671f..82ed6eb4c95 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -68,6 +68,7 @@ RSpec.shared_examples 'rate-limited token requests' do # Set low limits settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds + travel_back end after do @@ -220,6 +221,7 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do # Set low limits settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds + travel_back end after do @@ -436,6 +438,7 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do # Set low limits settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds + travel_back end context 'when the throttle is enabled' do diff --git a/spec/support/shared_examples/security_training_providers_importer.rb b/spec/support/shared_examples/security_training_providers_importer.rb index 568e3e1a4f2..69d92964270 100644 --- a/spec/support/shared_examples/security_training_providers_importer.rb +++ b/spec/support/shared_examples/security_training_providers_importer.rb @@ -8,7 +8,7 @@ RSpec.shared_examples 'security training providers importer' do end it 'upserts security training providers' do - expect { 2.times { subject } }.to change(security_training_providers, :count).from(0).to(2) + expect { 2.times { subject } }.to change { security_training_providers.count }.from(0).to(2) expect(security_training_providers.all.map(&:name)).to match_array(['Kontra', 'Secure Code Warrior']) end end diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb index 0db9519f760..6a9da91eaa7 100644 --- a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb +++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb @@ -11,7 +11,7 @@ RSpec.shared_examples 'creates an alert management alert or errors' do it 'creates AlertManagement::Alert' do expect(Gitlab::AppLogger).not_to receive(:warn) - expect { subject }.to change(AlertManagement::Alert, :count).by(1) + expect { subject }.to change { AlertManagement::Alert.count }.by(1) end it 'executes the alert service hooks' do @@ -118,7 +118,7 @@ end RSpec.shared_examples 'does not create an alert management alert' do specify do - expect { subject }.not_to change(AlertManagement::Alert, :count) + expect { subject }.not_to change { AlertManagement::Alert.count } end end diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb index 1973577d742..2740b6bf59d 100644 --- a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb +++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb @@ -14,7 +14,7 @@ RSpec.shared_examples 'closes related incident if enabled' do specify do expect { Sidekiq::Testing.inline! { subject } } .to change { alert.issue.reload.closed? }.from(false).to(true) - .and change(ResourceStateEvent, :count).by(1) + .and change { ResourceStateEvent.count }.by(1) end end diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb index 57d598c0259..2d0815ba27c 100644 --- a/spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb +++ b/spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb @@ -19,7 +19,7 @@ RSpec.shared_examples 'creates expected system notes for alert' do |*notes| end it "for #{notes.join(', ')}" do - expect { subject }.to change(Note, :count).by(expected_note_count) + expect { subject }.to change { Note.count }.by(expected_note_count) expected_notes.each_value.with_index do |value, index| expect(new_notes[index]).to include(value) @@ -29,6 +29,6 @@ end RSpec.shared_examples 'does not create a system note for alert' do specify do - expect { subject }.not_to change(Note, :count) + expect { subject }.not_to change { Note.count } end end diff --git a/spec/support/shared_examples/services/alert_management_shared_examples.rb b/spec/support/shared_examples/services/alert_management_shared_examples.rb index b46ace1824a..b8fc2eb5475 100644 --- a/spec/support/shared_examples/services/alert_management_shared_examples.rb +++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb @@ -68,8 +68,8 @@ RSpec.shared_examples 'processes one firing and one resolved prometheus alerts' expect(Gitlab::AppLogger).not_to receive(:warn) expect { subject } - .to change(AlertManagement::Alert, :count).by(1) - .and change(Note, :count).by(1) + .to change { AlertManagement::Alert.count }.by(1) + .and change { Note.count }.by(1) expect(subject).to be_success expect(subject.payload).to eq({}) diff --git a/spec/support/shared_examples/services/approval_state_updated_trigger_shared_examples.rb b/spec/support/shared_examples/services/approval_state_updated_trigger_shared_examples.rb new file mode 100644 index 00000000000..455fd308be8 --- /dev/null +++ b/spec/support/shared_examples/services/approval_state_updated_trigger_shared_examples.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'triggers GraphQL subscription mergeRequestApprovalStateUpdated' do + specify do + expect(GraphqlTriggers).to receive(:merge_request_approval_state_updated).with(merge_request) + + action + end +end + +RSpec.shared_examples 'does not trigger GraphQL subscription mergeRequestApprovalStateUpdated' do + specify do + expect(GraphqlTriggers).not_to receive(:merge_request_approval_state_updated) + + action + end +end diff --git a/spec/support/shared_examples/services/boards/boards_create_service_shared_examples.rb b/spec/support/shared_examples/services/boards/boards_create_service_shared_examples.rb index f28c78aec97..67a5af587e9 100644 --- a/spec/support/shared_examples/services/boards/boards_create_service_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/boards_create_service_shared_examples.rb @@ -3,7 +3,7 @@ RSpec.shared_examples 'boards create service' do context 'when parent does not have a board' do it 'creates a new board' do - expect { service.execute }.to change(Board, :count).by(1) + expect { service.execute }.to change { Board.count }.by(1) end it 'creates the default lists' do @@ -23,7 +23,7 @@ RSpec.shared_examples 'boards create service' do it 'does not create a new board' do expect(service).to receive(:can_create_board?) { false } - expect { service.execute }.not_to change(parent.boards, :count) + expect { service.execute }.not_to change { parent.boards.count } end end end diff --git a/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb b/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb index fd832d4484d..80d5c771abd 100644 --- a/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb @@ -2,7 +2,7 @@ RSpec.shared_examples 'boards list service' do it 'does not create a new board' do - expect { service.execute }.not_to change(parent.boards, :count) + expect { service.execute }.not_to change { parent.boards.count } end it 'returns parent boards' do diff --git a/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb b/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb index 68ea460dabc..8bf01ad84ff 100644 --- a/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb @@ -5,7 +5,7 @@ RSpec.shared_examples 'boards recent visit' do describe '#visited' do it 'creates a visit if one does not exists' do - expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1) + expect { described_class.visited!(user, board) }.to change { described_class.count }.by(1) end shared_examples 'was visited previously' do diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb b/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb index af88644ced7..52d427d6684 100644 --- a/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb @@ -5,7 +5,7 @@ RSpec.shared_examples 'lists destroy service' do it 'removes list from board' do service = described_class.new(parent, user) - expect { service.execute(list) }.to change(board.lists, :count).by(-1) + expect { service.execute(list) }.to change { board.lists.count }.by(-1) end it 'decrements position of higher lists' do @@ -24,6 +24,6 @@ RSpec.shared_examples 'lists destroy service' do it 'does not remove list from board when list type is closed' do service = described_class.new(parent, user) - expect { service.execute(closed_list) }.not_to change(board.lists, :count) + expect { service.execute(closed_list) }.not_to change { board.lists.count } end end diff --git a/spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb b/spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb index e1143562661..b9f28fab558 100644 --- a/spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb @@ -5,7 +5,7 @@ RSpec.shared_examples 'lists list service' do let!(:backlog_list) { create_backlog_list(board) } it 'does not create a backlog list' do - expect { service.execute(board) }.not_to change(board.lists, :count) + expect { service.execute(board) }.not_to change { board.lists.count } end it "returns board's lists" do @@ -35,11 +35,11 @@ RSpec.shared_examples 'lists list service' do context 'when the board does not have a backlog list' do it 'creates a backlog list' do - expect { service.execute(board) }.to change(board.lists, :count).by(1) + expect { service.execute(board) }.to change { board.lists.count }.by(1) end it 'does not create a backlog list when create_default_lists is false' do - expect { service.execute(board, create_default_lists: false) }.not_to change(board.lists, :count) + expect { service.execute(board, create_default_lists: false) }.not_to change { board.lists.count } end it "returns board's lists" do diff --git a/spec/support/shared_examples/services/container_expiration_policy_shared_examples.rb b/spec/support/shared_examples/services/container_expiration_policy_shared_examples.rb index 28bf46a57d5..38e19e58706 100644 --- a/spec/support/shared_examples/services/container_expiration_policy_shared_examples.rb +++ b/spec/support/shared_examples/services/container_expiration_policy_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'updating the container expiration policy attributes' do |mode:, from: {}, to:| +RSpec.shared_examples 'updating the container expiration policy attributes' do |mode:, to:, from: {}| if mode == :create it 'creates a new container expiration policy' do expect { subject } diff --git a/spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb b/spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb index f6692646ca8..dcc9c3d898f 100644 --- a/spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb +++ b/spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'updating the dependency proxy image ttl policy attributes' do |from: {}, to:| +RSpec.shared_examples 'updating the dependency proxy image ttl policy attributes' do |to:, from: {}| it_behaves_like 'not creating the dependency proxy image ttl policy' it 'updates the dependency proxy image ttl policy' do diff --git a/spec/support/shared_examples/services/incident_shared_examples.rb b/spec/support/shared_examples/services/incident_shared_examples.rb index b533b095aac..a87e7c1f801 100644 --- a/spec/support/shared_examples/services/incident_shared_examples.rb +++ b/spec/support/shared_examples/services/incident_shared_examples.rb @@ -55,7 +55,7 @@ RSpec.shared_examples 'incident management label service' do shared_examples 'existing label' do it 'returns the existing label' do - expect { execute }.not_to change(Label, :count) + expect { execute }.not_to change { Label.count } expect(execute).to be_success expect(execute.payload).to eq(label: label) @@ -64,7 +64,7 @@ RSpec.shared_examples 'incident management label service' do shared_examples 'new label' do it 'creates a new label' do - expect { execute }.to change(Label, :count).by(1) + expect { execute }.to change { Label.count }.by(1) label = project.reload.labels.last expect(execute).to be_success diff --git a/spec/support/shared_examples/services/issuable/update_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/update_service_shared_examples.rb index 3d90885dd6f..ff7acc7e907 100644 --- a/spec/support/shared_examples/services/issuable/update_service_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable/update_service_shared_examples.rb @@ -5,7 +5,7 @@ RSpec.shared_examples_for 'issuable update service updating last_edited_at value let(:update_params) { { title: 'updated title' } } it 'does not update last_edited values' do - expect { update_issuable }.to change(issuable, :title).from(issuable.title).to('updated title').and( + expect { update_issuable }.to change { issuable.title }.from(issuable.title).to('updated title').and( not_change(issuable, :last_edited_at) ).and( not_change(issuable, :last_edited_by) @@ -19,10 +19,10 @@ RSpec.shared_examples_for 'issuable update service updating last_edited_at value it 'updates last_edited values' do expect do update_issuable - end.to change(issuable, :description).from(issuable.description).to('updated description').and( - change(issuable, :last_edited_at) + end.to change { issuable.description }.from(issuable.description).to('updated description').and( + change { issuable.last_edited_at } ).and( - change(issuable, :last_edited_by) + change { issuable.last_edited_by } ) end end diff --git a/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb b/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb index 65351ac94ab..12f2b5d78a5 100644 --- a/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb @@ -24,7 +24,7 @@ RSpec.shared_examples 'issuable link creation' do end it 'no relationship is created' do - expect { subject }.not_to change(issuable_link_class, :count) + expect { subject }.not_to change { issuable_link_class.count } end end @@ -38,7 +38,7 @@ RSpec.shared_examples 'issuable link creation' do end it 'no relationship is created' do - expect { subject }.not_to change(issuable_link_class, :count) + expect { subject }.not_to change { issuable_link_class.count } end end @@ -54,7 +54,7 @@ RSpec.shared_examples 'issuable link creation' do end it 'no relationship is created' do - expect { subject }.not_to change(issuable_link_class, :count) + expect { subject }.not_to change { issuable_link_class.count } end end @@ -64,7 +64,7 @@ RSpec.shared_examples 'issuable link creation' do end it 'creates relationships' do - expect { subject }.to change(issuable_link_class, :count).by(2) + expect { subject }.to change { issuable_link_class.count }.by(2) expect(issuable_link_class.find_by!(target: issuable2)).to have_attributes(source: issuable, link_type: 'relates_to') expect(issuable_link_class.find_by!(target: issuable3)).to have_attributes(source: issuable, link_type: 'relates_to') diff --git a/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb b/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb index 5e80014da1d..cc170c6544d 100644 --- a/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb @@ -8,7 +8,7 @@ RSpec.shared_examples 'a destroyable issuable link' do end it 'removes related issue' do - expect { subject }.to change(issuable_link.class, :count).by(-1) + expect { subject }.to change { issuable_link.class.count }.by(-1) end it 'creates notes' do @@ -28,7 +28,7 @@ RSpec.shared_examples 'a destroyable issuable link' do context 'when failing to remove an issuable link' do it 'does not remove relation' do - expect { subject }.not_to change(issuable_link.class, :count).from(1) + expect { subject }.not_to change { issuable_link.class.count }.from(1) end it 'does not create notes' do diff --git a/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb b/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb index f7a6bd3676a..11a786fdefb 100644 --- a/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb +++ b/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'updating the namespace package setting attributes' do |from: {}, to:| +RSpec.shared_examples 'updating the namespace package setting attributes' do |to:, from: {}| it_behaves_like 'not creating the namespace package setting' it 'updates the namespace package setting' do diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb index ca4dea90c55..e0dd08ec50e 100644 --- a/spec/support/shared_examples/services/packages_shared_examples.rb +++ b/spec/support/shared_examples/services/packages_shared_examples.rb @@ -188,20 +188,6 @@ RSpec.shared_examples 'returns paginated packages' do end end -RSpec.shared_examples 'background upload schedules a file migration' do - context 'background upload enabled' do - before do - stub_package_file_object_storage(background_upload: true) - end - - it 'schedules migration of file to object storage' do - expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('Packages::PackageFileUploader', 'Packages::PackageFile', :file, kind_of(Numeric)) - - subject - end - end -end - RSpec.shared_context 'package filter context' do def package_filter_url(filter, param) "/projects/#{project.id}/packages?package_#{filter}=#{param}" diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb index 14af35e58b7..9f940d27341 100644 --- a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb +++ b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb @@ -71,7 +71,7 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| it 'does not enqueue a GC run' do expect { subject.execute } - .not_to change(Projects::GitGarbageCollectWorker.jobs, :count) + .not_to change { Projects::GitGarbageCollectWorker.jobs.count } end end @@ -84,12 +84,12 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| stub_application_setting(housekeeping_enabled: false) expect { subject.execute } - .not_to change(Projects::GitGarbageCollectWorker.jobs, :count) + .not_to change { Projects::GitGarbageCollectWorker.jobs.count } end it 'enqueues a GC run' do expect { subject.execute } - .to change(Projects::GitGarbageCollectWorker.jobs, :count).by(1) + .to change { Projects::GitGarbageCollectWorker.jobs.count }.by(1) end end end diff --git a/spec/support/shared_examples/services/repositories/housekeeping_shared_examples.rb b/spec/support/shared_examples/services/repositories/housekeeping_shared_examples.rb index 4c00faee56b..8a937303711 100644 --- a/spec/support/shared_examples/services/repositories/housekeeping_shared_examples.rb +++ b/spec/support/shared_examples/services/repositories/housekeeping_shared_examples.rb @@ -12,7 +12,7 @@ RSpec.shared_examples 'housekeeps repository' do expect(resource.git_garbage_collect_worker_klass).to receive(:perform_async).with(resource.id, :incremental_repack, :the_lease_key, :the_uuid).and_call_original Sidekiq::Testing.fake! do - expect { subject.execute }.to change(resource.git_garbage_collect_worker_klass.jobs, :size).by(1) + expect { subject.execute }.to change { resource.git_garbage_collect_worker_klass.jobs.size }.by(1) end end @@ -71,9 +71,6 @@ RSpec.shared_examples 'housekeeps repository' do # At push 10, 20, ... (except those above) expect(resource.git_garbage_collect_worker_klass).to receive(:perform_async).with(resource.id, :incremental_repack, :the_lease_key, :the_uuid) .exactly(16).times - # At push 6, 12, 18, ... (except those above) - expect(resource.git_garbage_collect_worker_klass).to receive(:perform_async).with(resource.id, :pack_refs, :the_lease_key, :the_uuid) - .exactly(27).times 201.times do subject.increment! @@ -82,6 +79,37 @@ RSpec.shared_examples 'housekeeps repository' do expect(resource.pushes_since_gc).to eq(1) end + + context 'when optimized_repository feature flag is disabled' do + before do + stub_feature_flags(optimized_housekeeping: false) + end + + it 'calls also the garbage collect worker with pack_refs every 6 commits' do + allow(subject).to receive(:try_obtain_lease).and_return(:the_uuid) + allow(subject).to receive(:lease_key).and_return(:the_lease_key) + + # At push 200 + expect(resource.git_garbage_collect_worker_klass).to receive(:perform_async).with(resource.id, :gc, :the_lease_key, :the_uuid) + .once + # At push 50, 100, 150 + expect(resource.git_garbage_collect_worker_klass).to receive(:perform_async).with(resource.id, :full_repack, :the_lease_key, :the_uuid) + .exactly(3).times + # At push 10, 20, ... (except those above) + expect(resource.git_garbage_collect_worker_klass).to receive(:perform_async).with(resource.id, :incremental_repack, :the_lease_key, :the_uuid) + .exactly(16).times + # At push 6, 12, 18, ... (except those above) + expect(resource.git_garbage_collect_worker_klass).to receive(:perform_async).with(resource.id, :pack_refs, :the_lease_key, :the_uuid) + .exactly(27).times + + 201.times do + subject.increment! + subject.execute if subject.needed? + end + + expect(resource.pushes_since_gc).to eq(1) + end + end end it 'runs the task specifically requested' do @@ -107,6 +135,17 @@ RSpec.shared_examples 'housekeeps repository' do allow(resource).to receive(:pushes_since_gc).and_return(10) expect(subject.needed?).to eq(true) end + + context 'when optimized_housekeeping is disabled' do + before do + stub_feature_flags(optimized_housekeeping: false) + end + + it 'returns true pack refs is needed' do + allow(resource).to receive(:pushes_since_gc).and_return(described_class::PACK_REFS_PERIOD) + expect(subject.needed?).to eq(true) + end + end end describe '#increment!' do diff --git a/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb b/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb index 97304680316..acf15730180 100644 --- a/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb +++ b/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb @@ -11,7 +11,7 @@ RSpec.shared_examples 'moves repository shard in bulk' do describe '#execute' do it 'schedules container repository storage moves' do expect { subject.execute(source_storage_name, destination_storage_name) } - .to change(move_service_klass, :count).by(1) + .to change { move_service_klass.count }.by(1) storage_move = container.repository_storage_moves.last! @@ -29,7 +29,7 @@ RSpec.shared_examples 'moves repository shard in bulk' do expect(subject).to receive(:log_info) .with(/Container #{container.full_path} \(#{container.id}\) was skipped: #{container.class} is read-only/) expect { subject.execute(source_storage_name, destination_storage_name) } - .to change(move_service_klass, :count).by(0) + .to change { move_service_klass.count }.by(0) end end end diff --git a/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb b/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb index 31919a4263d..e72e8e79411 100644 --- a/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb +++ b/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb @@ -7,5 +7,5 @@ RSpec.shared_examples 'issue_edit snowplow tracking' do let(:namespace) { project.namespace } let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } - it_behaves_like 'Snowplow event tracking' + it_behaves_like 'Snowplow event tracking with RedisHLL context' end diff --git a/spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb b/spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb index 53c42ec0e00..00d4224f021 100644 --- a/spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb +++ b/spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb @@ -1,6 +1,12 @@ # frozen_string_literal: true RSpec.shared_examples 'issuable supports timelog creation service' do + let_it_be(:time_spent) { 3600 } + let_it_be(:spent_at) { Time.now } + let_it_be(:summary) { "Test summary" } + + let(:service) { described_class.new(issuable, time_spent, spent_at, summary, user) } + shared_examples 'success_response' do it 'sucessfully saves the timelog' do is_expected.to be_success @@ -9,7 +15,7 @@ RSpec.shared_examples 'issuable supports timelog creation service' do expect(timelog).to be_persisted expect(timelog.time_spent).to eq(time_spent) - expect(timelog.spent_at).to eq('Fri, 08 Jul 2022 00:00:00.000000000 UTC +00:00') + expect(timelog.spent_at).to eq(spent_at) expect(timelog.summary).to eq(summary) expect(timelog.issuable).to eq(issuable) end @@ -34,6 +40,39 @@ RSpec.shared_examples 'issuable supports timelog creation service' do users_container.add_reporter(user) end + context 'when spent_at is in the future' do + let_it_be(:spent_at) { Time.now + 2.hours } + + it 'returns an error' do + is_expected.to be_error + + expect(subject.message).to eq("Spent at can't be a future date and time.") + expect(subject.http_status).to eq(404) + end + end + + context 'when time_spent is zero' do + let_it_be(:time_spent) { 0 } + + it 'returns an error' do + is_expected.to be_error + + expect(subject.message).to eq("Time spent can't be zero.") + expect(subject.http_status).to eq(404) + end + end + + context 'when time_spent is nil' do + let_it_be(:time_spent) { nil } + + it 'returns an error' do + is_expected.to be_error + + expect(subject.message).to eq("Time spent can't be blank") + expect(subject.http_status).to eq(404) + end + end + context 'when the timelog save fails' do before do allow_next_instance_of(Timelog) do |timelog| @@ -54,6 +93,12 @@ RSpec.shared_examples 'issuable supports timelog creation service' do end RSpec.shared_examples 'issuable does not support timelog creation service' do + let_it_be(:time_spent) { 3600 } + let_it_be(:spent_at) { Time.now } + let_it_be(:summary) { "Test summary" } + + let(:service) { described_class.new(issuable, time_spent, spent_at, summary, user) } + shared_examples 'error_response' do it 'returns an error' do is_expected.to be_error diff --git a/spec/support/shared_examples/services/users/build_service_shared_examples.rb b/spec/support/shared_examples/services/users/build_service_shared_examples.rb index 6a8695e1786..e448f2f874b 100644 --- a/spec/support/shared_examples/services/users/build_service_shared_examples.rb +++ b/spec/support/shared_examples/services/users/build_service_shared_examples.rb @@ -84,9 +84,10 @@ RSpec.shared_examples_for 'current user not admin build items' do end end - context 'when "send_user_confirmation_email" application setting is true' do + context 'when "email_confirmation_setting" application setting is set to `hard`' do before do - stub_application_setting(send_user_confirmation_email: true, signup_enabled?: true) + stub_application_setting_enum('email_confirmation_setting', 'hard') + stub_application_setting(signup_enabled?: true) end it 'does not confirm the user' do @@ -94,9 +95,10 @@ RSpec.shared_examples_for 'current user not admin build items' do end end - context 'when "send_user_confirmation_email" application setting is false' do + context 'when "email_confirmation_setting" application setting is set to `off`' do before do - stub_application_setting(send_user_confirmation_email: false, signup_enabled?: true) + stub_application_setting_enum('email_confirmation_setting', 'off') + stub_application_setting(signup_enabled?: true) end it 'confirms the user' do diff --git a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb index 980a752cf86..ced49b3e481 100644 --- a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb +++ b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb @@ -75,7 +75,7 @@ RSpec.shared_examples 'WikiPages::CreateService#execute' do |container_type| end it 'does not record the activity' do - expect { service.execute }.not_to change(Event, :count) + expect { service.execute }.not_to change { Event.count } end it 'reports the error' do diff --git a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb index fd10dd4367e..5511843e681 100644 --- a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb +++ b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb @@ -79,7 +79,7 @@ RSpec.shared_examples 'WikiPages::UpdateService#execute' do |container_type| end it 'does not record the activity' do - expect { service.execute page }.not_to change(Event, :count) + expect { service.execute page }.not_to change { Event.count } end it 'reports the error' do diff --git a/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb b/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb index ac17915c15a..ac064ed4c33 100644 --- a/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb +++ b/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb @@ -23,7 +23,7 @@ RSpec.shared_examples "setting work item's milestone" do it "sets the work item's milestone" do expect { execute_callback } - .to change(work_item, :milestone) + .to change { work_item.milestone } .from(nil) .to(group_milestone) end @@ -34,7 +34,7 @@ RSpec.shared_examples "setting work item's milestone" do it "sets the work item's milestone" do expect { execute_callback } - .to change(work_item, :milestone) + .to change { work_item.milestone } .from(nil) .to(project_milestone) end diff --git a/spec/support/shared_examples/work_item_base_types_importer.rb b/spec/support/shared_examples/work_item_base_types_importer.rb index 593670ac4b8..b1011037584 100644 --- a/spec/support/shared_examples/work_item_base_types_importer.rb +++ b/spec/support/shared_examples/work_item_base_types_importer.rb @@ -4,7 +4,7 @@ RSpec.shared_examples 'work item base types importer' do it "creates all base work item types if they don't exist" do WorkItems::Type.delete_all - expect { subject }.to change(WorkItems::Type, :count).from(0).to(WorkItems::Type::BASE_TYPES.count) + expect { subject }.to change { WorkItems::Type.count }.from(0).to(WorkItems::Type::BASE_TYPES.count) types_in_db = WorkItems::Type.all.map { |type| type.slice(:base_type, :icon_name, :name).symbolize_keys } expected_types = WorkItems::Type::BASE_TYPES.map do |type, attributes| @@ -25,7 +25,7 @@ RSpec.shared_examples 'work item base types importer' do subject first_type.reload end.to not_change(WorkItems::Type, :count).and( - change(first_type, :name).from(original_name.upcase).to(original_name) + change { first_type.name }.from(original_name.upcase).to(original_name) ) end @@ -40,7 +40,7 @@ RSpec.shared_examples 'work item base types importer' do it 'inserts all types and does nothing if some already existed' do expect { subject }.to make_queries_matching(/INSERT/, 1).and( - change(WorkItems::Type, :count).by(1) + change { WorkItems::Type.count }.by(1) ) expect(WorkItems::Type.count).to eq(WorkItems::Type::BASE_TYPES.count) end diff --git a/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb b/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb new file mode 100644 index 00000000000..b75aa27b2b7 --- /dev/null +++ b/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'work item hierarchy restrictions importer' do + shared_examples_for 'adds restrictions' do + it "adds all restrictions if they don't exist" do + expect { subject }.to change { WorkItems::HierarchyRestriction.count }.from(0).to(4) + end + end + + context 'when restrictions are missing' do + before do + WorkItems::HierarchyRestriction.delete_all + end + + it_behaves_like 'adds restrictions' + end + + context 'when base types are missing' do + before do + WorkItems::Type.delete_all + end + + it_behaves_like 'adds restrictions' + end + + context 'when restrictions already exist' do + before do + Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions + end + + it 'upserts restrictions' do + restriction = WorkItems::HierarchyRestriction.first + depth = restriction.maximum_depth + + restriction.update!(maximum_depth: depth + 1) + + expect do + subject + restriction.reload + end.to not_change { WorkItems::HierarchyRestriction.count }.and( + change { restriction.maximum_depth }.from(depth + 1).to(depth) + ) + end + end + + context 'when some restrictions are missing' do + before do + Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions + WorkItems::HierarchyRestriction.limit(1).delete_all + end + + it 'inserts missing restrictions and does nothing if some already existed' do + expect { subject }.to make_queries_matching(/INSERT/, 1).and( + change { WorkItems::HierarchyRestriction.count }.by(1) + ) + expect(WorkItems::HierarchyRestriction.count).to eq(4) + 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 new file mode 100644 index 00000000000..ae29b76ee87 --- /dev/null +++ b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'batched background migrations execution worker' do + include ExclusiveLeaseHelpers + + it 'is a limited capacity worker' do + expect(described_class.new).to be_a(LimitedCapacity::Worker) + end + + describe 'defining the job attributes' do + it 'defines the data_consistency as always' do + expect(described_class.get_data_consistency).to eq(:always) + end + + it 'defines the feature_category as database' do + expect(described_class.get_feature_category).to eq(:database) + end + + it 'defines the idempotency as false' do + expect(described_class).not_to be_idempotent + end + + it 'does not retry failed jobs' do + expect(described_class.sidekiq_options['retry']).to eq(0) + end + + it 'does not deduplicate jobs' do + expect(described_class.get_deduplicate_strategy).to eq(:none) + end + + it 'defines the queue namespace' do + expect(described_class.queue_namespace).to eq('batched_background_migrations') + end + end + + describe '.perform_with_capacity' do + it 'enqueues jobs without modifying provided arguments' do + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:remove_failed_jobs) + end + + args = [['main', 123]] + + expect(described_class) + .to receive(:bulk_perform_async) + .with(args) + + described_class.perform_with_capacity(args) + end + end + + describe '.max_running_jobs' do + it 'returns MAX_RUNNING_MIGRATIONS' do + expect(described_class.max_running_jobs).to eq(described_class::MAX_RUNNING_MIGRATIONS) + end + end + + describe '#max_running_jobs' do + it 'returns MAX_RUNNING_MIGRATIONS' do + expect(described_class.new.max_running_jobs).to eq(described_class::MAX_RUNNING_MIGRATIONS) + end + end + + describe '#remaining_work_count' do + it 'returns 0' do + expect(described_class.new.remaining_work_count).to eq(0) + end + end + + describe '#perform_work' do + let(:database_name) { Gitlab::Database::MAIN_DATABASE_NAME.to_sym } + let(:base_model) { Gitlab::Database.database_base_models[database_name] } + let(:table_name) { :events } + let(:job_interval) { 5.minutes } + let(:lease_timeout) { job_interval * described_class::LEASE_TIMEOUT_MULTIPLIER } + let(:interval_variance) { described_class::INTERVAL_VARIANCE } + + subject(:worker) { described_class.new } + + context 'when the feature flag is disabled' do + let(:migration) do + create(:batched_background_migration, :active, interval: job_interval, table_name: table_name) + end + + before do + stub_feature_flags(execute_batched_migrations_on_schedule: false) + end + + it 'does nothing' do + expect(Gitlab::Database::BackgroundMigration::BatchedMigration).not_to receive(:find_executable) + expect(worker).not_to receive(:run_migration_job) + + worker.perform_work(database_name, migration.id) + end + end + + context 'when the feature flag is enabled' do + before do + stub_feature_flags(execute_batched_migrations_on_schedule: true) + end + + context 'when the provided database is sharing config' do + before do + skip_if_multiple_databases_not_setup + end + + it 'does nothing' do + ci_model = Gitlab::Database.database_base_models['ci'] + expect(Gitlab::Database).to receive(:db_config_share_with) + .with(ci_model.connection_db_config).and_return('main') + + expect(Gitlab::Database::BackgroundMigration::BatchedMigration).not_to receive(:find_executable) + expect(worker).not_to receive(:run_migration_job) + + worker.perform_work(:ci, 123) + end + end + + context 'when migration does not exist' do + it 'does nothing' do + expect(worker).not_to receive(:run_migration_job) + + worker.perform_work(database_name, non_existing_record_id) + end + end + + context 'when migration exist' do + let(:migration) do + create(:batched_background_migration, :active, interval: job_interval, table_name: table_name) + end + + before do + allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:find_executable) + .with(migration.id, connection: base_model.connection) + .and_return(migration) + end + + context 'when the migration is no longer active' do + it 'does not run the migration' do + expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(base_model.connection).and_yield + + expect(migration).to receive(:active?).and_return(false) + + expect(worker).not_to receive(:run_migration_job) + + worker.perform_work(database_name, migration.id) + end + end + + context 'when the interval has not elapsed' do + it 'does not run the migration' do + expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(base_model.connection).and_yield + expect(migration).to receive(:interval_elapsed?).with(variance: interval_variance).and_return(false) + expect(worker).not_to receive(:run_migration_job) + + worker.perform_work(database_name, migration.id) + end + end + + context 'when the migration is still active and the interval has elapsed' do + let(:table_name_lease_key) do + "#{described_class.name.underscore}:database_name:#{database_name}:" \ + "table_name:#{table_name}" + end + + context 'when can not obtain lease on the table name' do + it 'does nothing' do + stub_exclusive_lease_taken(table_name_lease_key, timeout: lease_timeout) + + expect(worker).not_to receive(:run_migration_job) + + worker.perform_work(database_name, migration.id) + end + end + + it 'always cleans up the exclusive lease' do + expect_to_obtain_exclusive_lease(table_name_lease_key, 'uuid-table-name', timeout: lease_timeout) + expect_to_cancel_exclusive_lease(table_name_lease_key, 'uuid-table-name') + + expect(worker).to receive(:run_migration_job).and_raise(RuntimeError, 'I broke') + + expect { worker.perform_work(database_name, migration.id) }.to raise_error(RuntimeError, 'I broke') + end + + it 'runs the migration' do + expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(base_model.connection).and_yield + + expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |instance| + expect(instance).to receive(:run_migration_job).with(migration) + end + + expect_to_obtain_exclusive_lease(table_name_lease_key, 'uuid-table-name', timeout: lease_timeout) + expect_to_cancel_exclusive_lease(table_name_lease_key, 'uuid-table-name') + + expect(worker).to receive(:run_migration_job).and_call_original + + worker.perform_work(database_name, migration.id) + end + end + end + end + end +end diff --git a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb index 0be55fd2a3e..09ebc495e61 100644 --- a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb +++ b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb @@ -125,10 +125,28 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d end context 'when no active migrations exist' do - it 'does nothing' do - expect(worker).not_to receive(:run_active_migration) + context 'when parallel execution is disabled' do + before do + stub_feature_flags(batched_migrations_parallel_execution: false) + end - worker.perform + it 'does nothing' do + expect(worker).not_to receive(:run_active_migration) + + worker.perform + end + end + + context 'when parallel execution is enabled' do + before do + stub_feature_flags(batched_migrations_parallel_execution: true) + end + + it 'does nothing' do + expect(worker).not_to receive(:queue_migrations_for_execution) + + worker.perform + end end end @@ -136,7 +154,6 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d let(:job_interval) { 5.minutes } let(:lease_timeout) { 15.minutes } let(:lease_key) { described_class.name.demodulize.underscore } - let(:interval_variance) { described_class::INTERVAL_VARIANCE } let(:migration_id) { 123 } let(:migration) do build( @@ -145,52 +162,86 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d ) end + let(:execution_worker_class) do + case tracking_database + when :main + Database::BatchedBackgroundMigration::MainExecutionWorker + when :ci + Database::BatchedBackgroundMigration::CiExecutionWorker + end + end + before do allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration) .with(connection: base_model.connection) .and_return(migration) - - allow(migration).to receive(:interval_elapsed?).with(variance: interval_variance).and_return(true) - allow(migration).to receive(:reload) end - context 'when the calculated timeout is less than the minimum allowed' do - let(:minimum_timeout) { described_class::MINIMUM_LEASE_TIMEOUT } - let(:job_interval) { 2.minutes } + context 'when parallel execution is disabled' do + before do + stub_feature_flags(batched_migrations_parallel_execution: false) + end + + let(:execution_worker) { instance_double(execution_worker_class) } - it 'sets the lease timeout to the minimum value' do - expect_to_obtain_exclusive_lease(lease_key, timeout: minimum_timeout) + context 'when the calculated timeout is less than the minimum allowed' do + let(:minimum_timeout) { described_class::MINIMUM_LEASE_TIMEOUT } + let(:job_interval) { 2.minutes } - expect_next_instance_of(Database::BatchedBackgroundMigration::ExecutionWorker) do |worker| - expect(worker).to receive(:perform).with(tracking_database, migration_id) + it 'sets the lease timeout to the minimum value' do + expect_to_obtain_exclusive_lease(lease_key, timeout: minimum_timeout) + + expect(execution_worker_class).to receive(:new).and_return(execution_worker) + expect(execution_worker).to receive(:perform_work).with(tracking_database, migration_id) + + expect(worker).to receive(:run_active_migration).and_call_original + + worker.perform end + end - expect(worker).to receive(:run_active_migration).and_call_original + it 'always cleans up the exclusive lease' do + lease = stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) + + expect(lease).to receive(:try_obtain).and_return(true) + + expect(worker).to receive(:run_active_migration).and_raise(RuntimeError, 'I broke') + expect(lease).to receive(:cancel) + + expect { worker.perform }.to raise_error(RuntimeError, 'I broke') + end + + it 'delegetes the execution to ExecutionWorker' do + base_model = Gitlab::Database.database_base_models[tracking_database] + + expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(base_model.connection).and_yield + expect(execution_worker_class).to receive(:new).and_return(execution_worker) + expect(execution_worker).to receive(:perform_work).with(tracking_database, migration_id) worker.perform end end - it 'always cleans up the exclusive lease' do - lease = stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) + context 'when parallel execution is enabled' do + before do + stub_feature_flags(batched_migrations_parallel_execution: true) + end - expect(lease).to receive(:try_obtain).and_return(true) + it 'delegetes the execution to ExecutionWorker' do + expect(Gitlab::Database::BackgroundMigration::BatchedMigration) + .to receive(:active_migrations_distinct_on_table).with( + connection: base_model.connection, + limit: execution_worker_class.max_running_jobs + ).and_return([migration]) - expect(worker).to receive(:run_active_migration).and_raise(RuntimeError, 'I broke') - expect(lease).to receive(:cancel) + expected_arguments = [ + [tracking_database.to_s, migration_id] + ] - expect { worker.perform }.to raise_error(RuntimeError, 'I broke') - end + expect(execution_worker_class).to receive(:perform_with_capacity).with(expected_arguments) - it 'delegetes the execution to ExecutionWorker' do - base_model = Gitlab::Database.database_base_models[tracking_database] - - expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(base_model.connection).and_yield - expect_next_instance_of(Database::BatchedBackgroundMigration::ExecutionWorker) do |worker| - expect(worker).to receive(:perform).with(tracking_database, migration_id) + worker.perform end - - worker.perform end end end @@ -249,6 +300,8 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d end before do + stub_feature_flags(execute_batched_migrations_on_schedule: true) + # Create example table populated with test data to migrate. # # Test data should have two records that won't be updated: @@ -269,80 +322,96 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d WHERE some_column = #{migration_records - 5}; SQL - stub_feature_flags(execute_batched_migrations_on_schedule: true) - stub_const('Gitlab::BackgroundMigration::ExampleDataMigration', migration_class) end - subject(:full_migration_run) do - # process all batches, then do an extra execution to mark the job as finished - (number_of_batches + 1).times do - described_class.new.perform + shared_examples 'batched background migration execution' do + subject(:full_migration_run) do + # process all batches, then do an extra execution to mark the job as finished + (number_of_batches + 1).times do + described_class.new.perform - travel_to((migration.interval + described_class::INTERVAL_VARIANCE).seconds.from_now) + travel_to((migration.interval + described_class::INTERVAL_VARIANCE).seconds.from_now) + end end - end - it 'marks the migration record as finished' do - expect { full_migration_run }.to change { migration.reload.status }.from(1).to(3) # active -> finished - end + it 'marks the migration record as finished' do + expect { full_migration_run }.to change { migration.reload.status }.from(1).to(3) # active -> finished + end - it 'creates job records for each processed batch', :aggregate_failures do - expect { full_migration_run }.to change { migration.reload.batched_jobs.count }.from(0) + it 'creates job records for each processed batch', :aggregate_failures do + expect { full_migration_run }.to change { migration.reload.batched_jobs.count }.from(0) - final_min_value = migration.batched_jobs.reduce(1) do |next_min_value, batched_job| - expect(batched_job.min_value).to eq(next_min_value) + final_min_value = migration.batched_jobs.order(id: :asc).reduce(1) do |next_min_value, batched_job| + expect(batched_job.min_value).to eq(next_min_value) - batched_job.max_value + 1 + batched_job.max_value + 1 + end + + final_max_value = final_min_value - 1 + expect(final_max_value).to eq(migration_records) end - final_max_value = final_min_value - 1 - expect(final_max_value).to eq(migration_records) - end + it 'marks all job records as succeeded', :aggregate_failures do + expect { full_migration_run }.to change { migration.reload.batched_jobs.count }.from(0) - it 'marks all job records as succeeded', :aggregate_failures do - expect { full_migration_run }.to change { migration.reload.batched_jobs.count }.from(0) + expect(migration.batched_jobs).to all(be_succeeded) + end - expect(migration.batched_jobs).to all(be_succeeded) - end + it 'updates matching records in the range', :aggregate_failures do + expect { full_migration_run } + .to change { example_data.where('status = 1 AND some_column <> 0').count } + .from(migration_records).to(1) - it 'updates matching records in the range', :aggregate_failures do - expect { full_migration_run } - .to change { example_data.where('status = 1 AND some_column <> 0').count } - .from(migration_records).to(1) + record_outside_range = example_data.last - record_outside_range = example_data.last + expect(record_outside_range.status).to eq(1) + expect(record_outside_range.some_column).not_to eq(0) + end - expect(record_outside_range.status).to eq(1) - expect(record_outside_range.some_column).not_to eq(0) - end + it 'does not update non-matching records in the range' do + expect { full_migration_run }.not_to change { example_data.where('status <> 1 AND some_column <> 0').count } + end - it 'does not update non-matching records in the range' do - expect { full_migration_run }.not_to change { example_data.where('status <> 1 AND some_column <> 0').count } - end + context 'health status' do + subject(:migration_run) { described_class.new.perform } + + it 'puts migration on hold when there is autovaccum activity on related tables' do + swapout_view_for_table(:postgres_autovacuum_activity, connection: connection) + create( + :postgres_autovacuum_activity, + table: migration.table_name, + table_identifier: "public.#{migration.table_name}" + ) + + expect { migration_run }.to change { migration.reload.on_hold? }.from(false).to(true) + end - context 'health status' do - subject(:migration_run) { described_class.new.perform } + it 'puts migration on hold when the pending WAL count is above the limit' do + sql = Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::WriteAheadLog::PENDING_WAL_COUNT_SQL + limit = Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::WriteAheadLog::LIMIT - it 'puts migration on hold when there is autovaccum activity on related tables' do - swapout_view_for_table(:postgres_autovacuum_activity, connection: connection) - create( - :postgres_autovacuum_activity, - table: migration.table_name, - table_identifier: "public.#{migration.table_name}" - ) + expect(connection).to receive(:execute).with(sql).and_return([{ 'pending_wal_count' => limit + 1 }]) - expect { migration_run }.to change { migration.reload.on_hold? }.from(false).to(true) + expect { migration_run }.to change { migration.reload.on_hold? }.from(false).to(true) + end end + end - it 'puts migration on hold when the pending WAL count is above the limit' do - sql = Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::WriteAheadLog::PENDING_WAL_COUNT_SQL - limit = Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::WriteAheadLog::LIMIT + context 'when parallel execution is disabled' do + before do + stub_feature_flags(batched_migrations_parallel_execution: false) + end - expect(connection).to receive(:execute).with(sql).and_return([{ 'pending_wal_count' => limit + 1 }]) + it_behaves_like 'batched background migration execution' + end - expect { migration_run }.to change { migration.reload.on_hold? }.from(false).to(true) + context 'when parallel execution is enabled', :sidekiq_inline do + before do + stub_feature_flags(batched_migrations_parallel_execution: true) end + + it_behaves_like 'batched background migration execution' end end end diff --git a/spec/support/shared_examples/workers/schedule_bulk_repository_shard_moves_shared_examples.rb b/spec/support/shared_examples/workers/schedule_bulk_repository_shard_moves_shared_examples.rb index 465aca63148..6707f65eb69 100644 --- a/spec/support/shared_examples/workers/schedule_bulk_repository_shard_moves_shared_examples.rb +++ b/spec/support/shared_examples/workers/schedule_bulk_repository_shard_moves_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples 'schedules bulk repository shard moves' do let(:job_args) { [source_storage_name, destination_storage_name] } it 'schedules container repository storage moves' do - expect { subject }.to change(move_service_klass, :count).by(1) + expect { subject }.to change { move_service_klass.count }.by(1) storage_move = container.repository_storage_moves.last! diff --git a/spec/support/shared_examples/workers/update_repository_move_shared_examples.rb b/spec/support/shared_examples/workers/update_repository_move_shared_examples.rb index babd7cfbbeb..c50dc6d5372 100644 --- a/spec/support/shared_examples/workers/update_repository_move_shared_examples.rb +++ b/spec/support/shared_examples/workers/update_repository_move_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples 'an update storage move worker' do expect do subject.perform(container.id, 'test_second_storage') - end.to change(repository_storage_move_klass, :count).by(1) + end.to change { repository_storage_move_klass.count }.by(1) storage_move = container.repository_storage_moves.last expect(storage_move).to have_attributes( @@ -32,7 +32,7 @@ RSpec.shared_examples 'an update storage move worker' do expect do subject.perform(nil, nil, repository_storage_move.id) - end.not_to change(repository_storage_move_klass, :count) + end.not_to change { repository_storage_move_klass.count } end end end |