diff options
Diffstat (limited to 'spec/support')
340 files changed, 8628 insertions, 4109 deletions
diff --git a/spec/support/ability_check.rb b/spec/support/ability_check.rb new file mode 100644 index 00000000000..213944506bb --- /dev/null +++ b/spec/support/ability_check.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'gitlab/utils/strong_memoize' + +module Support + module AbilityCheck + def self.inject(mod) + mod.prepend AbilityExtension + end + + module AbilityExtension + def before_check(policy, ability, user, subject, opts) + return super if Checker.ok?(policy, ability) + + ActiveSupport::Deprecation.warn(<<~WARNING) + Ability #{ability.inspect} in #{policy.class} not found. + user=#{user.inspect}, subject=#{subject}, opts=#{opts.inspect}" + + To exclude this check add this entry to #{Checker::TODO_YAML}: + #{policy.class}: + - #{ability} + WARNING + end + end + + module Checker + include Gitlab::Utils::StrongMemoize + extend self + + TODO_YAML = File.join(__dir__, 'ability_check_todo.yml') + + def ok?(policy, ability) + ignored?(policy, ability) || ability_found?(policy, ability) + end + + private + + def ignored?(policy, ability) + todo_list[policy.class.name]&.include?(ability.to_s) + end + + # Use Policy#has_ability? instead after it has been accepted and released. + # See https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/-/issues/25 + def ability_found?(policy, ability) + # NilPolicy has no abilities. Ignore it. + return true if policy.is_a?(DeclarativePolicy::NilPolicy) + + # Search in current policy first + return true if policy.class.ability_map.map.key?(ability) + + # Search recursively in all delegations otherwise. + # This is potentially slow. + # Stolen from: + # https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/-/blob/d691e/lib/declarative_policy/base.rb#L360-369 + policy.class.delegations.any? do |_, block| + new_subject = policy.instance_eval(&block) + new_policy = policy.policy_for(new_subject) + + ability_found?(new_policy, ability) + end + end + + def todo_list + hash = YAML.load_file(TODO_YAML) + return {} unless hash.is_a?(Hash) + + hash.transform_values(&:to_set) + end + + strong_memoize_attr :todo_list + end + end +end diff --git a/spec/support/ability_check_todo.yml b/spec/support/ability_check_todo.yml new file mode 100644 index 00000000000..eafd595b137 --- /dev/null +++ b/spec/support/ability_check_todo.yml @@ -0,0 +1,73 @@ +# This list tracks unknown abilities per policy. +# +# This file is used by `spec/support/ability_check.rb`. +# +# Each TODO entry means that an ability wasn't found in +# the particular policy class or its delegations. +# +# This could be one of the reasons: +# * The ability is misspelled. +# - Suggested action: Fix typo. +# * The ability has been removed from a policy but is still in use. +# - Remove production code in question. +# * The ability is defined in EE policy but is used in FOSS code. +# - Guard the check or move it to EE folder. +# - See https://docs.gitlab.com/ee/development/ee_features.html +# * The ability is defined in another policy but delegation is missing. +# - Add delegation policy or guard the check with a type check. +# - See https://docs.gitlab.com/ee/development/policies.html#delegation +# * The ability check is polymorphic (for example, Issuable) and some policies +# do not implement this ability. +# - Exclude TODO permanently below. +# - Guard the check with a type check. +# * The ability check is defined on GraphQL field which does not support +# authorization on resolved field values yet. +# See https://gitlab.com/gitlab-org/gitlab/-/issues/300922 +--- +# <Policy class>: +# - <ability name> +# - <ability name> +# ... + +# Temporary excludes: + +Ci::BridgePolicy: +- read_job_artifacts +CommitStatusPolicy: +- read_job_artifacts +EpicPolicy: +- create_timelog +- read_emoji +- set_issue_crm_contacts +GlobalPolicy: +- read_achievement +- read_on_demand_dast_scan +- update_max_pages_size +GroupPolicy: +- admin_merge_request +- change_push_rules +- manage_owners +IssuePolicy: +- create_test_case +MergeRequestPolicy: +- set_confidentiality +- set_issue_crm_contacts +Namespaces::UserNamespacePolicy: +- read_crm_contact +PersonalSnippetPolicy: +- read_internal_note +- read_project +ProjectMemberPolicy: +- override_project_member +ProjectPolicy: +- admin_feature_flags_issue_links +- admin_vulnerability +- create_requirement +- create_test_case +- read_group_saml_identity +UserPolicy: +- admin_observability +- admin_terraform_state +- read_observability + +# Permanent excludes (please provide a reason): diff --git a/spec/support/banzai/filter_timeout_shared_examples.rb b/spec/support/banzai/filter_timeout_shared_examples.rb deleted file mode 100644 index 1f2ebe6fef6..00000000000 --- a/spec/support/banzai/filter_timeout_shared_examples.rb +++ /dev/null @@ -1,37 +0,0 @@ -# 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/capybara.rb b/spec/support/capybara.rb index fe9bff827dc..0de1300bc50 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -7,7 +7,7 @@ require 'capybara-screenshot/rspec' require 'selenium-webdriver' # Give CI some extra time -timeout = ENV['CI'] || ENV['CI_SERVER'] ? 30 : 10 +timeout = ENV['CI'] || ENV['CI_SERVER'] ? 45 : 10 # Support running Capybara on a specific port to allow saving commonly used pages Capybara.server_port = ENV['CAPYBARA_PORT'] if ENV['CAPYBARA_PORT'] @@ -24,13 +24,21 @@ JS_CONSOLE_FILTER = Regexp.union( 'Download the Vue Devtools extension', 'Download the Apollo DevTools', "Unrecognized feature: 'interest-cohort'", - 'Does this page need fixes or improvements?' + 'Does this page need fixes or improvements?', + + # Needed after https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60933 + # which opts out gitlab from FloC by default + # see https://web.dev/floc/ for more info on FloC + "Origin trial controlled feature not enabled: 'interest-cohort'", + + # ERR_CONNECTION error could happen due to automated test session disabling browser network request + 'net::ERR_CONNECTION' ] ) CAPYBARA_WINDOW_SIZE = [1366, 768].freeze -SCREENSHOT_FILENAME_LENGTH = ENV['CI'] || ENV['CI_SERVER'] ? 255 : 99 +SCREENSHOT_FILENAME_LENGTH = ENV['CI'] || ENV['CI_SERVER'] ? 150 : 99 @blackhole_tcp_server = nil @@ -42,21 +50,20 @@ Capybara.register_server :puma_via_workhorse do |app, port, host, **options| file.close! # We just want the filename TestEnv.with_workhorse(host, port, socket_path) do + # In cases of multiple installations of chromedriver, prioritize the version installed by SeleniumManager + # selenium-manager doesn't work with Linux arm64 yet: + # https://github.com/SeleniumHQ/selenium/issues/11357 + if RUBY_PLATFORM =~ /x86_64-linux|darwin/ + chrome_options = Selenium::WebDriver::Chrome::Options.chrome + chromedriver_path = File.dirname(Selenium::WebDriver::SeleniumManager.driver_path(chrome_options)) + ENV['PATH'] = "#{chromedriver_path}:#{ENV['PATH']}" # rubocop:disable RSpec/EnvAssignment + end + Capybara.servers[:puma].call(app, nil, socket_path, **options) end end Capybara.register_driver :chrome do |app| - capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( - # This enables access to logs with `page.driver.manage.get_log(:browser)` - loggingPrefs: { - browser: "ALL", - client: "ALL", - driver: "ALL", - server: "ALL" - } - ) - options = Selenium::WebDriver::Chrome::Options.new # Force the browser's scale factor to prevent inconsistencies on high-res devices @@ -82,9 +89,6 @@ Capybara.register_driver :chrome do |app| options.add_preference("download.prompt_for_download", false) end - # Chrome 75 defaults to W3C mode which doesn't allow console log access - options.add_option(:w3c, false) - # Set up a proxy server to block all external traffic. @blackhole_tcp_server = TCPServer.new(0) Thread.new do @@ -99,18 +103,11 @@ Capybara.register_driver :chrome do |app| Capybara::Selenium::Driver.new( app, browser: :chrome, - desired_capabilities: capabilities, options: options ) end Capybara.register_driver :firefox do |app| - capabilities = Selenium::WebDriver::Remote::Capabilities.firefox( - log: { - level: :trace - } - ) - options = Selenium::WebDriver::Firefox::Options.new(log_level: :trace) options.add_argument("--window-size=#{CAPYBARA_WINDOW_SIZE.join(',')}") @@ -121,7 +118,6 @@ Capybara.register_driver :firefox do |app| Capybara::Selenium::Driver.new( app, browser: :firefox, - desired_capabilities: capabilities, options: options ) end @@ -213,20 +209,11 @@ RSpec.configure do |config| # fixed. If we raised the `JSException` the fixed test would be marked as # failed again. if example.exception && !example.exception.is_a?(RSpec::Core::Pending::PendingExampleFixedError) - begin - console = page.driver.browser.manage.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER } - - if console.present? - message = "Unexpected browser console output:\n" + console.map(&:message).join("\n") - raise JSConsoleError, message - end - rescue Selenium::WebDriver::Error::WebDriverError => error - if error.message =~ %r{unknown command: session/[0-9a-zA-Z]+(?:/se)?/log} - message = "Unable to access Chrome javascript console logs. You may be using an outdated version of ChromeDriver." - raise JSConsoleError, message - else - raise error - end + console = page.driver.browser.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER } + + if console.present? + message = "Unexpected browser console output:\n" + console.map(&:message).join("\n") + raise JSConsoleError, message end end diff --git a/spec/support/capybara_wait_for_all_requests.rb b/spec/support/capybara_wait_for_all_requests.rb new file mode 100644 index 00000000000..36b63619b08 --- /dev/null +++ b/spec/support/capybara_wait_for_all_requests.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative 'helpers/capybara_helpers' +require_relative 'helpers/wait_for_requests' + +module Capybara + class Session + module WaitForAllRequestsAfterVisitPage + include CapybaraHelpers + include WaitForRequests + + def visit(visit_uri) + super + + wait_for_all_requests + end + end + + prepend WaitForAllRequestsAfterVisitPage + end + + module Node + module Actions + include CapybaraHelpers + include WaitForRequests + + module WaitForAllRequestsAfterClickButton + def click_button(locator = nil, **options) + super + + wait_for_all_requests + end + end + + module WaitForAllRequestsAfterClickLink + def click_link(locator = nil, **options) + super + + wait_for_all_requests + end + end + + prepend WaitForAllRequestsAfterClickButton + prepend WaitForAllRequestsAfterClickLink + end + end +end diff --git a/spec/support/database/prevent_cross_joins.rb b/spec/support/database/prevent_cross_joins.rb index 8e08824c464..c44bf96a268 100644 --- a/spec/support/database/prevent_cross_joins.rb +++ b/spec/support/database/prevent_cross_joins.rb @@ -40,7 +40,7 @@ module Database return end - schemas = ::Gitlab::Database::GitlabSchema.table_schemas(tables) + schemas = ::Gitlab::Database::GitlabSchema.table_schemas!(tables) schemas.subtract(IGNORED_SCHEMAS) if schemas.many? diff --git a/spec/support/fast_quarantine.rb b/spec/support/fast_quarantine.rb new file mode 100644 index 00000000000..b5ed1a2aa96 --- /dev/null +++ b/spec/support/fast_quarantine.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +return unless ENV['CI'] +return if ENV['FAST_QUARANTINE'] == "false" +return if ENV['CI_MERGE_REQUEST_LABELS'].to_s.include?('pipeline:run-flaky-tests') + +require_relative '../../tooling/lib/tooling/fast_quarantine' + +RSpec.configure do |config| + fast_quarantine_local_path = ENV.fetch('RSPEC_FAST_QUARANTINE_LOCAL_PATH', 'rspec/fast_quarantine-gitlab.txt') + fast_quarantine_path = ENV.fetch( + 'RSPEC_FAST_QUARANTINE_PATH', + File.expand_path("../../#{fast_quarantine_local_path}", __dir__) + ) + fast_quarantine = Tooling::FastQuarantine.new(fast_quarantine_path: fast_quarantine_path) + skipped_examples = [] + + config.around do |example| + if fast_quarantine.skip_example?(example) + skipped_examples << example.id + skip "Skipping #{example.id} because it's been fast-quarantined." + else + example.run + end + end + + config.after(:suite) do + next if skipped_examples.empty? + + skipped_tests_report_path = ENV.fetch( + 'SKIPPED_TESTS_REPORT_PATH', + File.expand_path("../../rspec/flaky/skipped_tests.txt", __dir__) + ) + + File.write(skipped_tests_report_path, "#{ENV.fetch('CI_JOB_URL', 'local-run')}\n#{skipped_examples.join("\n")}\n\n") + end +end diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml index 750295e16c4..8fcb4ee7b9c 100644 --- a/spec/support/finder_collection_allowlist.yml +++ b/spec/support/finder_collection_allowlist.yml @@ -24,7 +24,8 @@ - Ci::CommitStatusesFinder - Ci::DailyBuildGroupReportResultsFinder - ClusterAncestorsFinder -- Clusters::AgentAuthorizationsFinder +- Clusters::Agents::Authorizations::CiAccess::Finder +- Clusters::Agents::Authorizations::UserAccess::Finder - Clusters::KubernetesNamespaceFinder - ComplianceManagement::MergeRequests::ComplianceViolationsFinder - ContainerRepositoriesFinder @@ -62,6 +63,7 @@ - Security::TrainingUrlsFinder - Security::TrainingProviders::KontraUrlFinder - Security::TrainingProviders::SecureCodeWarriorUrlFinder +- Security::TrainingProviders::SecureFlagUrlFinder - SentryIssueFinder - ServerlessDomainFinder - TagsFinder @@ -69,3 +71,4 @@ - UploaderFinder - UserGroupNotificationSettingsFinder - UserGroupsCounter +- DataTransfer::MockedTransferFinder # Can be removed when https://gitlab.com/gitlab-org/gitlab/-/issues/397693 is closed diff --git a/spec/support/flaky_tests.rb b/spec/support/flaky_tests.rb deleted file mode 100644 index 4df0d23bfc3..00000000000 --- a/spec/support/flaky_tests.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -return unless ENV['CI'] -return if ENV['SKIP_FLAKY_TESTS_AUTOMATICALLY'] == "false" -return if ENV['CI_MERGE_REQUEST_LABELS'].to_s.include?('pipeline:run-flaky-tests') - -require_relative '../../tooling/rspec_flaky/config' -require_relative '../../tooling/rspec_flaky/report' - -RSpec.configure do |config| - $flaky_test_example_ids = begin # rubocop:disable Style/GlobalVars - raise "#{RspecFlaky::Config.suite_flaky_examples_report_path} doesn't exist" unless File.exist?(RspecFlaky::Config.suite_flaky_examples_report_path) - - RspecFlaky::Report.load(RspecFlaky::Config.suite_flaky_examples_report_path).map { |_, flaky_test_data| flaky_test_data.to_h[:example_id] } - rescue => e # rubocop:disable Style/RescueStandardError - puts e - [] - end - $skipped_flaky_tests_report = [] # rubocop:disable Style/GlobalVars - - config.around do |example| - # Skip flaky tests automatically - if $flaky_test_example_ids.include?(example.id) # rubocop:disable Style/GlobalVars - puts "Skipping #{example.id} '#{example.full_description}' because it's flaky." - $skipped_flaky_tests_report << example.id # rubocop:disable Style/GlobalVars - else - example.run - end - end - - config.after(:suite) do - next unless RspecFlaky::Config.skipped_flaky_tests_report_path - next if $skipped_flaky_tests_report.empty? # rubocop:disable Style/GlobalVars - - File.write(RspecFlaky::Config.skipped_flaky_tests_report_path, "#{ENV['CI_JOB_URL']}\n#{$skipped_flaky_tests_report.join("\n")}\n\n") # rubocop:disable Style/GlobalVars - end -end diff --git a/spec/support/helpers/api_internal_base_helpers.rb b/spec/support/helpers/api_internal_base_helpers.rb index e89716571f9..0c334e164a6 100644 --- a/spec/support/helpers/api_internal_base_helpers.rb +++ b/spec/support/helpers/api_internal_base_helpers.rb @@ -13,8 +13,6 @@ module APIInternalBaseHelpers Gitlab::GlRepository::PROJECT.identifier_for_container(container) when Snippet Gitlab::GlRepository::SNIPPET.identifier_for_container(container) - else - nil end end @@ -44,12 +42,14 @@ module APIInternalBaseHelpers end def push(key, container, protocol = 'ssh', env: nil, changes: nil) - push_with_path(key, - full_path: full_path_for(container), - gl_repository: gl_repository_for(container), - protocol: protocol, - env: env, - changes: changes) + push_with_path( + key, + full_path: full_path_for(container), + gl_repository: gl_repository_for(container), + protocol: protocol, + env: env, + changes: changes + ) end def push_with_path(key, full_path:, gl_repository: nil, protocol: 'ssh', env: nil, changes: nil) diff --git a/spec/support/helpers/board_helpers.rb b/spec/support/helpers/board_helpers.rb index d7277ba9a20..c7a7993c52b 100644 --- a/spec/support/helpers/board_helpers.rb +++ b/spec/support/helpers/board_helpers.rb @@ -29,13 +29,15 @@ module BoardHelpers # ensure there is enough horizontal space for four board lists resize_window(2000, 800) - drag_to(selector: selector, - scrollable: '#board-app', - list_from_index: list_from_index, - from_index: from_index, - to_index: to_index, - list_to_index: list_to_index, - perform_drop: perform_drop) + drag_to( + selector: selector, + scrollable: '#board-app', + list_from_index: list_from_index, + from_index: from_index, + to_index: to_index, + list_to_index: list_to_index, + perform_drop: perform_drop + ) end wait_for_requests diff --git a/spec/support/helpers/callouts_test_helper.rb b/spec/support/helpers/callouts_test_helper.rb deleted file mode 100644 index 8c7faa71d9f..00000000000 --- a/spec/support/helpers/callouts_test_helper.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module CalloutsTestHelper - def callouts_trials_link_path - '/-/trial_registrations/new?glm_content=gold-callout&glm_source=gitlab.com' - end -end - -CalloutsTestHelper.prepend_mod diff --git a/spec/support/chunked_io/chunked_io_helpers.rb b/spec/support/helpers/chunked_io_helpers.rb index 278f577f3cb..278f577f3cb 100644 --- a/spec/support/chunked_io/chunked_io_helpers.rb +++ b/spec/support/helpers/chunked_io_helpers.rb diff --git a/spec/support/helpers/ci/source_pipeline_helpers.rb b/spec/support/helpers/ci/source_pipeline_helpers.rb index b99f499cc16..ef3aea7de52 100644 --- a/spec/support/helpers/ci/source_pipeline_helpers.rb +++ b/spec/support/helpers/ci/source_pipeline_helpers.rb @@ -3,11 +3,13 @@ module Ci module SourcePipelineHelpers def create_source_pipeline(upstream, downstream) - create(:ci_sources_pipeline, - source_job: create(:ci_build, pipeline: upstream), - source_project: upstream.project, - pipeline: downstream, - project: downstream.project) + create( + :ci_sources_pipeline, + source_job: create(:ci_build, pipeline: upstream), + source_project: upstream.project, + pipeline: downstream, + project: downstream.project + ) end end end diff --git a/spec/support/helpers/ci/template_helpers.rb b/spec/support/helpers/ci/template_helpers.rb index cd3ab4bd82d..1818dec5fc7 100644 --- a/spec/support/helpers/ci/template_helpers.rb +++ b/spec/support/helpers/ci/template_helpers.rb @@ -6,6 +6,10 @@ module Ci 'registry.gitlab.com' end + def auto_build_image_repository + "gitlab-org/cluster-integration/auto-build-image" + end + def public_image_exist?(registry, repository, image) public_image_manifest(registry, repository, image).present? end diff --git a/spec/support/helpers/content_editor_helpers.rb b/spec/support/helpers/content_editor_helpers.rb new file mode 100644 index 00000000000..83c18f8073f --- /dev/null +++ b/spec/support/helpers/content_editor_helpers.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module ContentEditorHelpers + def switch_to_content_editor + click_button("Switch to rich text") + end + + def type_in_content_editor(keys) + find(content_editor_testid).send_keys keys + end + + def click_attachment_button + page.find('svg[data-testid="paperclip-icon"]').click + end + + def set_source_editor_content(content) + find('.js-gfm-input').set content + end + + def expect_media_bubble_menu_to_be_visible + expect(page).to have_css('[data-testid="media-bubble-menu"]') + end + + def upload_asset(fixture_name) + attach_file('content_editor_image', Rails.root.join('spec', 'fixtures', fixture_name), make_visible: true) + end + + def wait_until_hidden_field_is_updated(value) + expect(page).to have_field(with: value, type: 'hidden') + end + + def display_media_bubble_menu(media_element_selector, fixture_file) + upload_asset fixture_file + + wait_for_requests + + expect(page).to have_css(media_element_selector) + + page.find(media_element_selector).click + end + + def click_edit_diagram_button + page.find('[data-testid="edit-diagram"]').click + end + + def expect_drawio_editor_is_opened + expect(page).to have_css('#drawio-frame', visible: :hidden) + end +end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index eba5771e062..0accb341cb9 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -91,13 +91,13 @@ module CycleAnalyticsHelpers wait_for_requests end - def create_value_stream_group_aggregation(group) - aggregation = Analytics::CycleAnalytics::Aggregation.safe_create_for_namespace(group) + def create_value_stream_aggregation(group_or_project_namespace) + aggregation = Analytics::CycleAnalytics::Aggregation.safe_create_for_namespace(group_or_project_namespace) Analytics::CycleAnalytics::AggregatorService.new(aggregation: aggregation).execute end def select_group_and_custom_value_stream(group, custom_value_stream_name) - create_value_stream_group_aggregation(group) + create_value_stream_aggregation(group) select_group(group) select_value_stream(custom_value_stream_name) @@ -235,4 +235,13 @@ module CycleAnalyticsHelpers pipeline: dummy_pipeline(project), protected: false) end + + def create_deployment(args) + project = args[:project] + environment = project.environments.production.first || create(:environment, :production, project: project) + create(:deployment, :success, args.merge(environment: environment)) + + # this is needed for the DORA API so we have aggregated data + ::Dora::DailyMetrics::RefreshWorker.new.perform(environment.id, Time.current.to_date.to_s) if Gitlab.ee? + end end diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/helpers/cycle_analytics_helpers/test_generation.rb index 816caf5f775..1c7c45c06a1 100644 --- a/spec/support/cycle_analytics_helpers/test_generation.rb +++ b/spec/support/helpers/cycle_analytics_helpers/test_generation.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# rubocop:disable Layout/LineLength +# rubocop:disable Metrics/CyclomaticComplexity +# rubocop:disable Metrics/PerceivedComplexity # rubocop:disable Metrics/AbcSize # Note: The ABC size is large here because we have a method generating test cases with @@ -30,7 +33,7 @@ module CycleAnalyticsHelpers let_it_be(:other_project) { create(:project, :repository) } before do - other_project.add_developer(self.user) + other_project.add_developer(user) end context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do @@ -41,14 +44,14 @@ module CycleAnalyticsHelpers start_time = (index * 10).days.from_now end_time = start_time + rand(1..5).days - start_time_conditions.each do |condition_name, condition_fn| + start_time_conditions.each_value do |condition_fn| travel_to(start_time) { condition_fn[self, data] } end # Run `before_end_fn` at the midpoint between `start_time` and `end_time` - travel_to(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| + end_time_conditions.each_value do |condition_fn| travel_to(end_time) { condition_fn[self, data] } end @@ -73,11 +76,11 @@ module CycleAnalyticsHelpers start_time = Time.now end_time = rand(1..10).days.from_now - start_time_conditions.each do |condition_name, condition_fn| + start_time_conditions.each_value do |condition_fn| travel_to(start_time) { condition_fn[self, data] } end - end_time_conditions.each do |condition_name, condition_fn| + end_time_conditions.each_value do |condition_fn| travel_to(end_time) { condition_fn[self, data] } end @@ -97,13 +100,13 @@ module CycleAnalyticsHelpers end_time = start_time + rand(1..5).days # Run `before_end_fn` at the midpoint between `start_time` and `end_time` - travel_to(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| + end_time_conditions.each_value do |condition_fn| travel_to(start_time) { condition_fn[self, data] } end - start_time_conditions.each do |condition_name, condition_fn| + start_time_conditions.each_value do |condition_fn| travel_to(end_time) { condition_fn[self, data] } end @@ -113,36 +116,34 @@ module CycleAnalyticsHelpers end end end - end - context "start condition NOT PRESENT: #{start_time_conditions.map(&:first).to_sentence}" do - context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do + context "end condition NOT PRESENT: #{end_time_conditions.map(&:first).to_sentence}" do it "returns nil" do data = data_fn[self] - end_time = rand(1..10).days.from_now + start_time = Time.now - end_time_conditions.each_with_index do |(_condition_name, condition_fn), index| - travel_to(end_time + index.days) { condition_fn[self, data] } + start_time_conditions.each_value do |condition_fn| + travel_to(start_time) { condition_fn[self, data] } end - travel_to(end_time + 1.day) { post_fn[self, data] } if post_fn + post_fn[self, data] if post_fn expect(subject[phase].project_median).to be_nil end end end - context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do - context "end condition NOT PRESENT: #{end_time_conditions.map(&:first).to_sentence}" do + context "start condition NOT PRESENT: #{start_time_conditions.map(&:first).to_sentence}" do + context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do it "returns nil" do data = data_fn[self] - start_time = Time.now + end_time = rand(1..10).days.from_now - start_time_conditions.each do |condition_name, condition_fn| - travel_to(start_time) { condition_fn[self, data] } + end_time_conditions.each_with_index do |(_condition_name, condition_fn), index| + travel_to(end_time + index.days) { condition_fn[self, data] } end - 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 @@ -158,3 +159,8 @@ module CycleAnalyticsHelpers end end end + +# rubocop:enable Layout/LineLength +# rubocop:enable Metrics/CyclomaticComplexity +# rubocop:enable Metrics/PerceivedComplexity +# rubocop:enable Metrics/AbcSize diff --git a/spec/support/helpers/database/database_helpers.rb b/spec/support/helpers/database/database_helpers.rb index ecc42041e93..ff694bcd15b 100644 --- a/spec/support/helpers/database/database_helpers.rb +++ b/spec/support/helpers/database/database_helpers.rb @@ -4,11 +4,13 @@ module Database module DatabaseHelpers # In order to directly work with views using factories, # we can swapout the view for a table of identical structure. - def swapout_view_for_table(view, connection:) + def swapout_view_for_table(view, connection:, schema: nil) + table_name = [schema, "_test_#{view}_copy"].compact.join('.') + connection.execute(<<~SQL.squish) - CREATE TABLE #{view}_copy (LIKE #{view}); + CREATE TABLE #{table_name} (LIKE #{view}); DROP VIEW #{view}; - ALTER TABLE #{view}_copy RENAME TO #{view}; + ALTER TABLE #{table_name} RENAME TO #{view}; SQL end diff --git a/spec/support/helpers/database/inject_failure_helpers.rb b/spec/support/helpers/database/inject_failure_helpers.rb new file mode 100644 index 00000000000..df98f45e69f --- /dev/null +++ b/spec/support/helpers/database/inject_failure_helpers.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Database + module InjectFailureHelpers + # These methods are used by specs that inject faults into the migration procedure and then ensure + # that it migrates correctly when rerun + def fail_first_time + # We can't directly use a boolean here, as we need something that will be passed by-reference to the proc + fault_status = { faulted: false } + proc do |m, *args, **kwargs| + next m.call(*args, **kwargs) if fault_status[:faulted] + + fault_status[:faulted] = true + raise 'fault!' + end + end + + def fail_sql_matching(regex) + proc do + allow(migration_context.connection).to receive(:execute).and_call_original + allow(migration_context.connection).to receive(:execute).with(regex).and_wrap_original(&fail_first_time) + end + end + + def fail_adding_fk(from_table, to_table) + proc do + allow(migration_context.connection).to receive(:add_foreign_key).and_call_original + expect(migration_context.connection).to receive(:add_foreign_key).with(from_table, to_table, any_args) + .and_wrap_original(&fail_first_time) + end + end + + def fail_removing_fk(from_table, to_table) + proc do + allow(migration_context.connection).to receive(:remove_foreign_key).and_call_original + expect(migration_context.connection).to receive(:remove_foreign_key).with(from_table, to_table, any_args) + .and_wrap_original(&fail_first_time) + end + end + end +end diff --git a/spec/support/helpers/database/multiple_databases_helpers.rb b/spec/support/helpers/database/multiple_databases_helpers.rb index 5083ea1ff53..3c9a5762c47 100644 --- a/spec/support/helpers/database/multiple_databases_helpers.rb +++ b/spec/support/helpers/database/multiple_databases_helpers.rb @@ -4,6 +4,28 @@ module Database module MultipleDatabasesHelpers EXTRA_DBS = ::Gitlab::Database::DATABASE_NAMES.map(&:to_sym) - [:main] + def database_exists?(database_name) + ::Gitlab::Database.has_database?(database_name) + end + + def skip_if_shared_database(database_name) + skip "Skipping because #{database_name} is shared or doesn't not exist" unless database_exists?(database_name) + end + + def skip_if_database_exists(database_name) + skip "Skipping because database #{database_name} exists" if database_exists?(database_name) + end + + def execute_on_each_database(query, databases: %I[main ci]) + databases = databases.select { |database_name| database_exists?(database_name) } + + Gitlab::Database::EachDatabase.each_database_connection(only: databases, include_shared: false) do |connection, _| + next unless Gitlab::Database.gitlab_schemas_for_connection(connection).include?(:gitlab_shared) + + connection.execute(query) + end + end + def skip_if_multiple_databases_not_setup(*databases) unless (databases - EXTRA_DBS).empty? raise "Unsupported database in #{databases}. It must be one of #{EXTRA_DBS}." diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb index f4bdaa7e425..57386233775 100644 --- a/spec/support/helpers/email_helpers.rb +++ b/spec/support/helpers/email_helpers.rb @@ -76,4 +76,25 @@ module EmailHelpers composed_expectation.and(have_enqueued_mail(mailer_class, mailer_method).with(*arguments)) end end + + def expect_sender(user, sender_email: nil) + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq("#{user.name} (@#{user.username})") + expect(sender.address).to eq(sender_email.presence || gitlab_sender) + end + + def expect_service_desk_custom_email_delivery_options(service_desk_setting) + expect(subject.delivery_method).to be_a Mail::SMTP + expect(service_desk_setting.custom_email_credential).to be_present + + credential = service_desk_setting.custom_email_credential + + expect(subject.delivery_method.settings).to include( + address: credential.smtp_address, + port: credential.smtp_port, + user_name: credential.smtp_username, + password: credential.smtp_password, + domain: service_desk_setting.custom_email.split('@').last + ) + end end diff --git a/spec/support/helpers/every_sidekiq_worker_test_helper.rb b/spec/support/helpers/every_sidekiq_worker_test_helper.rb new file mode 100644 index 00000000000..b053ed04b58 --- /dev/null +++ b/spec/support/helpers/every_sidekiq_worker_test_helper.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module EverySidekiqWorkerTestHelper + def extra_retry_exceptions + {} + end +end + +EverySidekiqWorkerTestHelper.prepend_mod diff --git a/spec/support/helpers/fake_u2f_device.rb b/spec/support/helpers/fake_u2f_device.rb deleted file mode 100644 index 2ed1222ebd3..00000000000 --- a/spec/support/helpers/fake_u2f_device.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -class FakeU2fDevice - attr_reader :name - - def initialize(page, name, device = nil) - @page = page - @name = name - @u2f_device = device - end - - def respond_to_u2f_registration - app_id = @page.evaluate_script('gon.u2f.app_id') - challenges = @page.evaluate_script('gon.u2f.challenges') - - json_response = u2f_device(app_id).register_response(challenges[0]) - - @page.execute_script(" - u2f.register = function(appId, registerRequests, signRequests, callback) { - callback(#{json_response}); - }; - ") - end - - def respond_to_u2f_authentication - app_id = @page.evaluate_script('gon.u2f.app_id') - challenge = @page.evaluate_script('gon.u2f.challenge') - json_response = u2f_device(app_id).sign_response(challenge) - - @page.execute_script(" - u2f.sign = function(appId, challenges, signRequests, callback) { - callback(#{json_response}); - }; - window.gl.u2fAuthenticate.start(); - ") - end - - def fake_u2f_authentication - @page.execute_script("window.gl.u2fAuthenticate.renderAuthenticated('abc');") - end - - private - - def u2f_device(app_id) - @u2f_device ||= U2F::FakeU2F.new(app_id) - end -end diff --git a/spec/support/helpers/fake_webauthn_device.rb b/spec/support/helpers/fake_webauthn_device.rb index d2c2f7d6bf3..5a535735817 100644 --- a/spec/support/helpers/fake_webauthn_device.rb +++ b/spec/support/helpers/fake_webauthn_device.rb @@ -45,7 +45,7 @@ class FakeWebauthnDevice return Promise.resolve(result); }; JS - @page.click_link('Try again?', href: false) + @page.click_button(_('Try again?')) end def fake_webauthn_authentication diff --git a/spec/support/helpers/feature_flag_helpers.rb b/spec/support/helpers/feature_flag_helpers.rb index 4e57002a7c6..3cf611c66e6 100644 --- a/spec/support/helpers/feature_flag_helpers.rb +++ b/spec/support/helpers/feature_flag_helpers.rb @@ -2,22 +2,32 @@ module FeatureFlagHelpers def create_flag(project, name, active = true, description: nil, version: Operations::FeatureFlag.versions['new_version_flag']) - create(:operations_feature_flag, name: name, active: active, version: version, - description: description, project: project) + create( + :operations_feature_flag, + name: name, + active: active, + version: version, + description: description, + project: project + ) end def create_scope(feature_flag, environment_scope, active = true, strategies = [{ name: "default", parameters: {} }]) - create(:operations_feature_flag_scope, + create( + :operations_feature_flag_scope, feature_flag: feature_flag, environment_scope: environment_scope, active: active, - strategies: strategies) + strategies: strategies + ) end def create_strategy(feature_flag, name = 'default', parameters = {}) - create(:operations_strategy, + create( + :operations_strategy, feature_flag: feature_flag, - name: name) + name: name + ) end def within_feature_flag_row(index) @@ -95,6 +105,6 @@ module FeatureFlagHelpers end def expect_user_to_see_feature_flags_index_page - expect(page).to have_text('Feature Flags') + expect(page).to have_text('Feature flags') end end diff --git a/spec/support/helpers/features/access_token_helpers.rb b/spec/support/helpers/features/access_token_helpers.rb index f4bdb70c160..bc839642914 100644 --- a/spec/support/helpers/features/access_token_helpers.rb +++ b/spec/support/helpers/features/access_token_helpers.rb @@ -1,18 +1,15 @@ # frozen_string_literal: true -module Spec - module Support - module Helpers - module AccessTokenHelpers - def active_access_tokens - find("[data-testid='active-tokens']") - end - def created_access_token - within('[data-testid=access-token-section]') do - find('[data-testid=toggle-visibility-button]').click - find_field('new-access-token').value - end - end +module Features + module AccessTokenHelpers + def active_access_tokens + find("[data-testid='active-tokens']") + end + + def created_access_token + within('[data-testid=access-token-section]') do + find('[data-testid=toggle-visibility-button]').click + find_field('new-access-token').value end end end diff --git a/spec/support/helpers/features/admin_users_helpers.rb b/spec/support/helpers/features/admin_users_helpers.rb index 99b19eedcff..9a87ccf113a 100644 --- a/spec/support/helpers/features/admin_users_helpers.rb +++ b/spec/support/helpers/features/admin_users_helpers.rb @@ -1,24 +1,18 @@ # frozen_string_literal: true -module Spec - module Support - module Helpers - module Features - module AdminUsersHelpers - def click_user_dropdown_toggle(user_id) - page.within("[data-testid='user-actions-#{user_id}']") do - find("[data-testid='dropdown-toggle']").click - end - end +module Features + module AdminUsersHelpers + def click_user_dropdown_toggle(user_id) + page.within("[data-testid='user-actions-#{user_id}']") do + find("[data-testid='dropdown-toggle']").click + end + end - def click_action_in_user_dropdown(user_id, action) - click_user_dropdown_toggle(user_id) + def click_action_in_user_dropdown(user_id, action) + click_user_dropdown_toggle(user_id) - within find("[data-testid='user-actions-#{user_id}']") do - find('li button', exact_text: action).click - end - end - end + within find("[data-testid='user-actions-#{user_id}']") do + find('li button', exact_text: action).click end end end diff --git a/spec/support/helpers/features/blob_spec_helpers.rb b/spec/support/helpers/features/blob_spec_helpers.rb index 7ccfc9be7e2..8254e1d76bd 100644 --- a/spec/support/helpers/features/blob_spec_helpers.rb +++ b/spec/support/helpers/features/blob_spec_helpers.rb @@ -1,14 +1,16 @@ # frozen_string_literal: true -# These helpers help you interact within the blobs page and blobs edit page (Single file editor). -module BlobSpecHelpers - include ActionView::Helpers::JavaScriptHelper +module Features + # These helpers help you interact within the blobs page and blobs edit page (Single file editor). + module BlobSpecHelpers + include ActionView::Helpers::JavaScriptHelper - def set_default_button(type) - evaluate_script("localStorage.setItem('gl-web-ide-button-selected', '#{type}')") - end + def set_default_button(type) + evaluate_script("localStorage.setItem('gl-web-ide-button-selected', '#{type}')") + end - def unset_default_button - set_default_button('') + def unset_default_button + set_default_button('') + end end end diff --git a/spec/support/helpers/features/branches_helpers.rb b/spec/support/helpers/features/branches_helpers.rb index dc4fa448167..9fb6236d052 100644 --- a/spec/support/helpers/features/branches_helpers.rb +++ b/spec/support/helpers/features/branches_helpers.rb @@ -4,31 +4,28 @@ # # Usage: # describe "..." do -# include Spec::Support::Helpers::Features::BranchesHelpers +# include Features::BranchesHelpers # ... # # create_branch("feature") # select_branch("master") # -module Spec - module Support - module Helpers - module Features - module BranchesHelpers - def create_branch(branch_name, source_branch_name = "master") - fill_in("branch_name", with: branch_name) - select_branch(source_branch_name) - click_button("Create branch") - end +module Features + module BranchesHelpers + include ListboxHelpers - def select_branch(branch_name) - wait_for_requests + def create_branch(branch_name, source_branch_name = "master") + fill_in("branch_name", with: branch_name) + select_branch(source_branch_name) + click_button("Create branch") + end + + def select_branch(branch_name) + wait_for_requests - click_button branch_name - send_keys branch_name - end - end - end + click_button branch_name + send_keys branch_name + select_listbox_item(branch_name) end end end diff --git a/spec/support/helpers/features/canonical_link_helpers.rb b/spec/support/helpers/features/canonical_link_helpers.rb index da3a28f1cb2..6ef934a924b 100644 --- a/spec/support/helpers/features/canonical_link_helpers.rb +++ b/spec/support/helpers/features/canonical_link_helpers.rb @@ -4,25 +4,19 @@ # # Usage: # describe "..." do -# include Spec::Support::Helpers::Features::CanonicalLinkHelpers +# include Features::CanonicalLinkHelpers # ... # # expect(page).to have_canonical_link(url) # -module Spec - module Support - module Helpers - module Features - module CanonicalLinkHelpers - def have_canonical_link(url) - have_xpath("//link[@rel=\"canonical\" and @href=\"#{url}\"]", visible: false) - end +module Features + module CanonicalLinkHelpers + def have_canonical_link(url) + have_xpath("//link[@rel=\"canonical\" and @href=\"#{url}\"]", visible: false) + end - def have_any_canonical_links - have_xpath('//link[@rel="canonical"]', visible: false) - end - end - end + def have_any_canonical_links + have_xpath('//link[@rel="canonical"]', visible: false) 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 deleted file mode 100644 index 47cbd6b5208..00000000000 --- a/spec/support/helpers/features/invite_members_modal_helper.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true - -module Spec - module Support - module Helpers - module Features - module InviteMembersModalHelper - def invite_member(names, role: 'Guest', expires_at: nil) - click_on 'Invite members' - - page.within invite_modal_selector do - select_members(names) - choose_options(role, expires_at) - submit_invites - end - - 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) - click_on 'Invite members' - - page.within invite_modal_selector do - select_members(names) - end - end - - def select_members(names) - Array.wrap(names).each do |name| - find(member_dropdown_selector).set(name) - - wait_for_requests - click_button name - end - end - - def invite_group(name, role: 'Guest', expires_at: nil) - click_on 'Invite a group' - - click_on 'Select a group' - wait_for_requests - click_button name - choose_options(role, expires_at) - - submit_invites - end - - def submit_invites - click_button 'Invite' - end - - def choose_options(role, expires_at) - select role, from: 'Select a role' - fill_in 'YYYY-MM-DD', with: expires_at.strftime('%Y-%m-%d') if expires_at - end - - def click_groups_tab - expect(page).to have_link 'Groups' - click_link "Groups" - end - - def group_dropdown_selector - '[data-testid="group-select-dropdown"]' - end - - def member_dropdown_selector - '[data-testid="members-token-select-input"]' - end - - def invite_modal_selector - '[data-testid="invite-modal"]' - end - - def member_token_error_selector(id) - "[data-testid='error-icon-#{id}']" - end - - def member_token_avatar_selector - "[data-testid='token-avatar']" - end - - def member_token_selector(id) - "[data-token-id='#{id}']" - end - - def more_invite_errors_button_selector - "[data-testid='accordion-button']" - end - - def limited_invite_error_selector - "[data-testid='errors-limited-item']" - end - - def expanded_invite_error_selector - "[data-testid='errors-expanded-item']" - end - - def remove_token(id) - page.within member_token_selector(id) do - find('[data-testid="close-icon"]').click - end - end - - def expect_to_have_successful_invite_indicator(page, user) - expect(page).to have_selector("#{member_token_selector(user.id)} .gl-bg-green-100") - expect(page).not_to have_text("#{user.name}: ") - end - - def expect_to_have_invalid_invite_indicator(page, user, message: true) - expect(page).to have_selector("#{member_token_selector(user.id)} .gl-bg-red-100") - expect(page).to have_selector(member_token_error_selector(user.id)) - expect(page).to have_text("#{user.name}: Access level should be greater than or equal to") if message - end - - def expect_to_have_normal_invite_indicator(page, user) - expect(page).to have_selector(member_token_selector(user.id)) - expect(page).not_to have_selector("#{member_token_selector(user.id)} .gl-bg-red-100") - expect(page).not_to have_selector("#{member_token_selector(user.id)} .gl-bg-green-100") - expect(page).not_to have_text("#{user.name}: ") - end - - def expect_to_have_invite_removed(page, user) - expect(page).not_to have_selector(member_token_selector(user.id)) - expect(page).not_to have_text("#{user.name}: Access level should be greater than or equal to") - end - - def expect_to_have_group(group) - expect(page).to have_selector("[entity-id='#{group.id}']") - end - - def expect_not_to_have_group(group) - expect(page).not_to have_selector("[entity-id='#{group.id}']") - end - end - end - end - end -end diff --git a/spec/support/helpers/features/invite_members_modal_helpers.rb b/spec/support/helpers/features/invite_members_modal_helpers.rb new file mode 100644 index 00000000000..75573616686 --- /dev/null +++ b/spec/support/helpers/features/invite_members_modal_helpers.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +module Features + module InviteMembersModalHelpers + def invite_member(names, role: 'Guest', expires_at: nil) + click_on 'Invite members' + + page.within invite_modal_selector do + select_members(names) + choose_options(role, expires_at) + submit_invites + end + + 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) + click_on 'Invite members' + + page.within invite_modal_selector do + select_members(names) + end + end + + def select_members(names) + Array.wrap(names).each do |name| + find(member_dropdown_selector).set(name) + + wait_for_requests + click_button name + end + end + + def invite_group(name, role: 'Guest', expires_at: nil) + click_on 'Invite a group' + + click_on 'Select a group' + wait_for_requests + click_button name + choose_options(role, expires_at) + + submit_invites + end + + def submit_invites + click_button 'Invite' + end + + def choose_options(role, expires_at) + select role, from: 'Select a role' + fill_in 'YYYY-MM-DD', with: expires_at.strftime('%Y-%m-%d') if expires_at + end + + def click_groups_tab + expect(page).to have_link 'Groups' + click_link "Groups" + end + + def group_dropdown_selector + '[data-testid="group-select-dropdown"]' + end + + def member_dropdown_selector + '[data-testid="members-token-select-input"]' + end + + def invite_modal_selector + '[data-testid="invite-modal"]' + end + + def member_token_error_selector(id) + "[data-testid='error-icon-#{id}']" + end + + def member_token_avatar_selector + "[data-testid='token-avatar']" + end + + def member_token_selector(id) + "[data-token-id='#{id}']" + end + + def more_invite_errors_button_selector + "[data-testid='accordion-button']" + end + + def limited_invite_error_selector + "[data-testid='errors-limited-item']" + end + + def expanded_invite_error_selector + "[data-testid='errors-expanded-item']" + end + + def remove_token(id) + page.within member_token_selector(id) do + find('[data-testid="close-icon"]').click + end + end + + def expect_to_have_successful_invite_indicator(page, user) + expect(page).to have_selector("#{member_token_selector(user.id)} .gl-bg-green-100") + expect(page).not_to have_text("#{user.name}: ") + end + + def expect_to_have_invalid_invite_indicator(page, user, message: true) + expect(page).to have_selector("#{member_token_selector(user.id)} .gl-bg-red-100") + expect(page).to have_selector(member_token_error_selector(user.id)) + expect(page).to have_text("#{user.name}: Access level should be greater than or equal to") if message + end + + def expect_to_have_normal_invite_indicator(page, user) + expect(page).to have_selector(member_token_selector(user.id)) + expect(page).not_to have_selector("#{member_token_selector(user.id)} .gl-bg-red-100") + expect(page).not_to have_selector("#{member_token_selector(user.id)} .gl-bg-green-100") + expect(page).not_to have_text("#{user.name}: ") + end + + def expect_to_have_invite_removed(page, user) + expect(page).not_to have_selector(member_token_selector(user.id)) + expect(page).not_to have_text("#{user.name}: Access level should be greater than or equal to") + end + + def expect_to_have_group(group) + expect(page).to have_selector("[entity-id='#{group.id}']") + end + + def expect_not_to_have_group(group) + expect(page).not_to have_selector("[entity-id='#{group.id}']") + end + end +end diff --git a/spec/support/helpers/features/iteration_helpers.rb b/spec/support/helpers/features/iteration_helpers.rb index 8e1d252f55f..fab373a547f 100644 --- a/spec/support/helpers/features/iteration_helpers.rb +++ b/spec/support/helpers/features/iteration_helpers.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true -module IterationHelpers - def iteration_period(iteration) - "#{iteration.start_date.to_s(:medium)} - #{iteration.due_date.to_s(:medium)}" + +module Features + module IterationHelpers + def iteration_period(iteration) + "#{iteration.start_date.to_s(:medium)} - #{iteration.due_date.to_s(:medium)}" + end end end diff --git a/spec/support/helpers/features/list_rows_helpers.rb b/spec/support/helpers/features/list_rows_helpers.rb deleted file mode 100644 index 0626415361c..00000000000 --- a/spec/support/helpers/features/list_rows_helpers.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true -# These helpers allow you to access rows in the list -# -# Usage: -# describe "..." do -# include Spec::Support::Helpers::Features::ListRowsHelpers -# ... -# -# expect(first_row.text).to include("John Doe") -# expect(second_row.text).to include("John Smith") -# -module Spec - module Support - module Helpers - module Features - module ListRowsHelpers - def first_row - page.all('ul.content-list > li')[0] - end - - def second_row - page.all('ul.content-list > li')[1] - end - end - end - end - end -end diff --git a/spec/support/helpers/features/members_helpers.rb b/spec/support/helpers/features/members_helpers.rb index 2d3f0902a3c..9882767cecf 100644 --- a/spec/support/helpers/features/members_helpers.rb +++ b/spec/support/helpers/features/members_helpers.rb @@ -1,78 +1,72 @@ # frozen_string_literal: true -module Spec - module Support - module Helpers - module Features - module MembersHelpers - def members_table - page.find('[data-testid="members-table"]') - end +module Features + module MembersHelpers + def members_table + page.find('[data-testid="members-table"]') + end - def all_rows - page.within(members_table) do - page.all('tbody > tr') - end - end + def all_rows + page.within(members_table) do + page.all('tbody > tr') + end + end - def first_row - all_rows[0] - end + def first_row + all_rows[0] + end - def second_row - all_rows[1] - end + def second_row + all_rows[1] + end - def third_row - all_rows[2] - end + def third_row + all_rows[2] + end - def find_row(name) - page.within(members_table) do - page.find('tbody > tr', text: name) - end - end + def find_row(name) + page.within(members_table) do + page.find('tbody > tr', text: name) + end + end - def find_member_row(user) - find_row(user.name) - end + def find_member_row(user) + find_row(user.name) + end - def find_username_row(user) - find_row(user.username) - end + def find_username_row(user) + find_row(user.username) + end - def find_invited_member_row(email) - find_row(email) - end + def find_invited_member_row(email) + find_row(email) + end - def find_group_row(group) - find_row(group.full_name) - end + def find_group_row(group) + find_row(group.full_name) + end - def fill_in_filtered_search(label, with:) - page.within '[data-testid="members-filtered-search-bar"]' do - find_field(label).click - find('input').native.send_keys(with) - click_button 'Search' - end - end + def fill_in_filtered_search(label, with:) + page.within '[data-testid="members-filtered-search-bar"]' do + find_field(label).click + find('input').native.send_keys(with) + click_button 'Search' + end + end - def user_action_dropdown - '[data-testid="user-action-dropdown"]' - end + def user_action_dropdown + '[data-testid="user-action-dropdown"]' + end - def show_actions - within user_action_dropdown do - find('button').click - end - end + def show_actions + within user_action_dropdown do + find('button').click + end + end - def show_actions_for_username(user) - within find_username_row(user) do - show_actions - end - end - end + def show_actions_for_username(user) + within find_username_row(user) do + show_actions end end end diff --git a/spec/support/helpers/features/merge_request_helpers.rb b/spec/support/helpers/features/merge_request_helpers.rb index 53896e1fe12..260a55487ea 100644 --- a/spec/support/helpers/features/merge_request_helpers.rb +++ b/spec/support/helpers/features/merge_request_helpers.rb @@ -1,25 +1,19 @@ # frozen_string_literal: true -module Spec - module Support - module Helpers - module Features - module MergeRequestHelpers - def preload_view_requirements(merge_request, note) - # This will load the status fields of the author of the note and merge request - # to avoid queries when rendering the view being tested. - # - merge_request.author.status - note.author.status - end +module Features + module MergeRequestHelpers + def preload_view_requirements(merge_request, note) + # This will load the status fields of the author of the note and merge request + # to avoid queries when rendering the view being tested. + # + merge_request.author.status + note.author.status + end - def serialize_issuable_sidebar(user, project, merge_request) - MergeRequestSerializer - .new(current_user: user, project: project) - .represent(merge_request, serializer: 'sidebar') - end - end - end + def serialize_issuable_sidebar(user, project, merge_request) + MergeRequestSerializer + .new(current_user: user, project: project) + .represent(merge_request, serializer: 'sidebar') end end end diff --git a/spec/support/helpers/features/mirroring_helpers.rb b/spec/support/helpers/features/mirroring_helpers.rb new file mode 100644 index 00000000000..0c3006cd1d1 --- /dev/null +++ b/spec/support/helpers/features/mirroring_helpers.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# These helpers allow you to set up mirroring. +# +# Usage: +# describe "..." do +# include Features::MirroringHelpers +# ... +# +# fill_and_wait_for_mirror_url_javascript("url", "ssh://user@localhost/project.git") +# wait_for_mirror_field_javascript("protected", "0") +# +module Features + module MirroringHelpers + # input_identifier - identifier of the input field, passed to `fill_in` (can be an ID or a label). + # url - the URL to fill the input field with. + def fill_and_wait_for_mirror_url_javascript(input_identifier, url) + fill_in input_identifier, with: url + wait_for_mirror_field_javascript('url', url) + end + + # attribute - can be `url` or `protected`. It's used in the `.js-mirror-<field>-hidden` selector. + # expected_value - the expected value of the hidden field. + def wait_for_mirror_field_javascript(attribute, expected_value) + expect(page).to have_css(".js-mirror-#{attribute}-hidden[value=\"#{expected_value}\"]", visible: :hidden) + end + end +end diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb index f8252254531..7973d541f9c 100644 --- a/spec/support/helpers/features/notes_helpers.rb +++ b/spec/support/helpers/features/notes_helpers.rb @@ -4,53 +4,47 @@ # # Usage: # describe "..." do -# include Spec::Support::Helpers::Features::NotesHelpers +# include Features::NotesHelpers # ... # # add_note("Hello world!") # -module Spec - module Support - module Helpers - module Features - module NotesHelpers - def add_note(text) - perform_enqueued_jobs do - page.within(".js-main-target-form") do - fill_in("note[note]", with: text) - find(".js-comment-submit-button").click - end - end - - wait_for_requests - end - - def edit_note(note_text_to_edit, new_note_text) - page.within('#notes-list li.note', text: note_text_to_edit) do - find('.js-note-edit').click - fill_in('note[note]', with: new_note_text) - find('.js-comment-button').click - end - - wait_for_requests - end - - def preview_note(text) - page.within('.js-main-target-form') do - filled_text = fill_in('note[note]', with: text) - - # Wait for quick action prompt to load and then dismiss it with ESC - # because it may block the Preview button - wait_for_requests - filled_text.send_keys(:escape) - - click_on('Preview') - - yield if block_given? - end - end +module Features + module NotesHelpers + def add_note(text) + perform_enqueued_jobs do + page.within(".js-main-target-form") do + fill_in("note[note]", with: text) + find(".js-comment-submit-button").click end end + + wait_for_requests + end + + def edit_note(note_text_to_edit, new_note_text) + page.within('#notes-list li.note', text: note_text_to_edit) do + find('.js-note-edit').click + fill_in('note[note]', with: new_note_text) + find('.js-comment-button').click + end + + wait_for_requests + end + + def preview_note(text) + page.within('.js-main-target-form') do + filled_text = fill_in('note[note]', with: text) + + # Wait for quick action prompt to load and then dismiss it with ESC + # because it may block the Preview button + wait_for_requests + filled_text.send_keys(:escape) + + click_button("Preview") + + yield if block_given? + end end end end diff --git a/spec/support/helpers/features/releases_helpers.rb b/spec/support/helpers/features/releases_helpers.rb index 545e12341ef..d5846aad15d 100644 --- a/spec/support/helpers/features/releases_helpers.rb +++ b/spec/support/helpers/features/releases_helpers.rb @@ -4,80 +4,83 @@ # # Usage: # describe "..." do -# include Spec::Support::Helpers::Features::ReleasesHelpers +# include Features::ReleasesHelpers # ... # # fill_tag_name("v1.0") # select_create_from("my-feature-branch") # -module Spec - module Support - module Helpers - module Features - module ReleasesHelpers - include ListboxHelpers +module Features + module ReleasesHelpers + include ListboxHelpers - def select_new_tag_name(tag_name) - page.within '[data-testid="tag-name-field"]' do - find('button').click - wait_for_all_requests + def select_new_tag_name(tag_name) + open_tag_popover - find('input[aria-label="Search or create tag"]').set(tag_name) - wait_for_all_requests + page.within '[data-testid="tag-name-search"]' do + find('input[type="search"]').set(tag_name) + wait_for_all_requests - click_button("Create tag #{tag_name}") - click_button tag_name - end - end - - def select_create_from(branch_name) - page.within '[data-testid="create-from-field"]' do - find('button').click + click_button("Create tag #{tag_name}") + end + end - wait_for_all_requests + def select_create_from(branch_name) + open_tag_popover - find('input[aria-label="Search branches, tags, and commits"]').set(branch_name) + page.within '[data-testid="create-from-field"]' do + find('.ref-selector button').click - wait_for_all_requests + wait_for_all_requests - select_listbox_item(branch_name.to_s, exact_text: true) - end - end + find('input[aria-label="Search branches, tags, and commits"]').set(branch_name) - def fill_release_title(release_title) - fill_in('Release title', with: release_title) - end + wait_for_all_requests - def select_milestone(milestone_title) - page.within '[data-testid="milestones-field"]' do - find('button').click + select_listbox_item(branch_name.to_s, exact_text: true) - wait_for_all_requests + click_button _('Save') + end + end - find('input[aria-label="Search Milestones"]').set(milestone_title) + def fill_release_title(release_title) + fill_in('Release title', with: release_title) + end - wait_for_all_requests + def select_milestone(milestone_title) + page.within '[data-testid="milestones-field"]' do + find('button').click - find('button', text: milestone_title, match: :first).click - end - end + wait_for_all_requests - def fill_release_notes(release_notes) - fill_in('Release notes', with: release_notes) - end + find('input[aria-label="Search Milestones"]').set(milestone_title) - def fill_asset_link(link) - all('input[name="asset-url"]').last.set(link[:url]) - all('input[name="asset-link-name"]').last.set(link[:title]) - all('select[name="asset-type"]').last.find("option[value=\"#{link[:type]}\"").select_option - end + wait_for_all_requests - # Click "Add another link" and tab back to the beginning of the new row - def add_another_asset_link - click_button('Add another link') - end - end + find('button', text: milestone_title, match: :first).click end end + + def fill_release_notes(release_notes) + fill_in('Release notes', with: release_notes) + end + + def fill_asset_link(link) + all('input[name="asset-url"]').last.set(link[:url]) + all('input[name="asset-link-name"]').last.set(link[:title]) + all('select[name="asset-type"]').last.find("option[value=\"#{link[:type]}\"").select_option + end + + # Click "Add another link" and tab back to the beginning of the new row + def add_another_asset_link + click_button('Add another link') + end + + def open_tag_popover(name = s_('Release|Search or create tag name')) + return if page.has_css? '.release-tag-selector' + + click_button name + wait_for_all_requests + end end end diff --git a/spec/support/helpers/features/responsive_table_helpers.rb b/spec/support/helpers/features/responsive_table_helpers.rb index 7a175219fe9..980f09b7eea 100644 --- a/spec/support/helpers/features/responsive_table_helpers.rb +++ b/spec/support/helpers/features/responsive_table_helpers.rb @@ -3,7 +3,7 @@ # # Usage: # describe "..." do -# include Spec::Support::Helpers::Features::ResponsiveTableHelpers +# include Features::ResponsiveTableHelpers # ... # # expect(first_row.text).to include("John Doe") @@ -13,20 +13,14 @@ # index starts at 1 as index 0 is expected to be the table header # # -module Spec - module Support - module Helpers - module Features - module ResponsiveTableHelpers - def first_row - page.all('.gl-responsive-table-row')[1] - end +module Features + module ResponsiveTableHelpers + def first_row + page.all('.gl-responsive-table-row')[1] + end - def second_row - page.all('.gl-responsive-table-row')[2] - end - end - end + def second_row + page.all('.gl-responsive-table-row')[2] end end end diff --git a/spec/support/helpers/features/runners_helpers.rb b/spec/support/helpers/features/runners_helpers.rb index c5d26108953..0504e883b82 100644 --- a/spec/support/helpers/features/runners_helpers.rb +++ b/spec/support/helpers/features/runners_helpers.rb @@ -1,68 +1,62 @@ # frozen_string_literal: true -module Spec - module Support - module Helpers - module Features - module RunnersHelpers - def within_runner_row(runner_id) - within "[data-testid='runner-row-#{runner_id}']" do - yield - end - end - - def search_bar_selector - '[data-testid="runners-filtered-search"]' - end +module Features + module RunnersHelpers + def within_runner_row(runner_id) + within "[data-testid='runner-row-#{runner_id}']" do + yield + end + end - # The filters must be clicked first to be able to receive events - # See: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1493 - def focus_filtered_search - page.within(search_bar_selector) do - page.find('.gl-filtered-search-term-token').click - end - end + def search_bar_selector + '[data-testid="runners-filtered-search"]' + end - def input_filtered_search_keys(search_term) - focus_filtered_search + # The filters must be clicked first to be able to receive events + # See: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1493 + def focus_filtered_search + page.within(search_bar_selector) do + page.find('.gl-filtered-search-term-token').click + end + end - page.within(search_bar_selector) do - page.find('input').send_keys(search_term) - click_on 'Search' - end + def input_filtered_search_keys(search_term) + focus_filtered_search - wait_for_requests - end + page.within(search_bar_selector) do + page.find('input').send_keys(search_term) + click_on 'Search' + end - def open_filtered_search_suggestions(filter) - focus_filtered_search + wait_for_requests + end - page.within(search_bar_selector) do - click_on filter - end + def open_filtered_search_suggestions(filter) + focus_filtered_search - wait_for_requests - end + page.within(search_bar_selector) do + click_on filter + end - def input_filtered_search_filter_is_only(filter, value) - focus_filtered_search + wait_for_requests + end - page.within(search_bar_selector) do - click_on filter + def input_filtered_search_filter_is_only(filter, value) + focus_filtered_search - # For OPERATORS_IS, clicking the filter - # immediately preselects "=" operator + page.within(search_bar_selector) do + click_on filter - page.find('input').send_keys(value) - page.find('input').send_keys(:enter) + # For OPERATORS_IS, clicking the filter + # immediately preselects "=" operator - click_on 'Search' - end + page.find('input').send_keys(value) + page.find('input').send_keys(:enter) - wait_for_requests - end - end + click_on 'Search' end + + wait_for_requests end end end diff --git a/spec/support/helpers/features/snippet_helpers.rb b/spec/support/helpers/features/snippet_helpers.rb deleted file mode 100644 index 3e32b0e4c67..00000000000 --- a/spec/support/helpers/features/snippet_helpers.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -# These helpers help you interact within the Source Editor (single-file editor, snippets, etc.). -# - -require Rails.root.join("spec/support/helpers/features/source_editor_spec_helpers.rb") - -module Spec - module Support - module Helpers - module Features - module SnippetSpecHelpers - include ActionView::Helpers::JavaScriptHelper - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers - - def snippet_description_locator - 'snippet-description' - end - - def snippet_blob_path_locator - 'snippet_file_name' - end - - def snippet_description_view_selector - '.snippet-header .snippet-description' - end - - def snippet_description_field_collapsed - find('.js-description-input').find('input,textarea') - end - - def snippet_get_first_blob_path - page.find_field('snippet_file_name', match: :first).value - end - - def snippet_get_first_blob_value - page.find('.gl-source-editor', match: :first) - end - - def snippet_description_value - page.find_field(snippet_description_locator).value - end - - def snippet_fill_in_visibility(text) - page.find('#visibility-level-setting').choose(text) - end - - def snippet_fill_in_title(value) - fill_in 'snippet-title', with: value - end - - def snippet_fill_in_description(value) - # Click placeholder first to expand full description field - snippet_description_field_collapsed.click - fill_in snippet_description_locator, with: value - end - - def snippet_fill_in_content(value) - page.within('.gl-source-editor') do - el = find('.inputarea') - el.send_keys value - end - end - - def snippet_fill_in_file_name(value) - fill_in(snippet_blob_path_locator, match: :first, with: value) - end - - def snippet_fill_in_form(title: nil, content: nil, file_name: nil, description: nil, visibility: nil) - if content - snippet_fill_in_content(content) - # It takes some time after sending keys for the vue component to - # update so let Capybara wait for the content before proceeding - expect(page).to have_content(content) - end - - snippet_fill_in_title(title) if title - - snippet_fill_in_description(description) if description - - snippet_fill_in_file_name(file_name) if file_name - - snippet_fill_in_visibility(visibility) if visibility - end - end - end - end - end -end diff --git a/spec/support/helpers/features/snippet_spec_helpers.rb b/spec/support/helpers/features/snippet_spec_helpers.rb new file mode 100644 index 00000000000..19393f6e438 --- /dev/null +++ b/spec/support/helpers/features/snippet_spec_helpers.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +# These helpers help you interact within the Source Editor (single-file editor, snippets, etc.). +# + +require Rails.root.join("spec/support/helpers/features/source_editor_spec_helpers.rb") + +module Features + module SnippetSpecHelpers + include ActionView::Helpers::JavaScriptHelper + include Features::SourceEditorSpecHelpers + + def snippet_description_locator + 'snippet-description' + end + + def snippet_blob_path_locator + 'snippet_file_name' + end + + def snippet_description_view_selector + '.snippet-header .snippet-description' + end + + def snippet_description_field_collapsed + find('.js-description-input').find('input,textarea') + end + + def snippet_get_first_blob_path + page.find_field('snippet_file_name', match: :first).value + end + + def snippet_get_first_blob_value + page.find('.gl-source-editor', match: :first) + end + + def snippet_description_value + page.find_field(snippet_description_locator).value + end + + def snippet_fill_in_visibility(text) + page.find('#visibility-level-setting').choose(text) + end + + def snippet_fill_in_title(value) + fill_in 'snippet-title', with: value + end + + def snippet_fill_in_description(value) + # Click placeholder first to expand full description field + snippet_description_field_collapsed.click + fill_in snippet_description_locator, with: value + end + + def snippet_fill_in_content(value) + page.within('.gl-source-editor') do + el = find('.inputarea') + el.send_keys value + end + end + + def snippet_fill_in_file_name(value) + fill_in(snippet_blob_path_locator, match: :first, with: value) + end + + def snippet_fill_in_form(title: nil, content: nil, file_name: nil, description: nil, visibility: nil) + if content + snippet_fill_in_content(content) + # It takes some time after sending keys for the vue component to + # update so let Capybara wait for the content before proceeding + expect(page).to have_content(content) + end + + snippet_fill_in_title(title) if title + + snippet_fill_in_description(description) if description + + snippet_fill_in_file_name(file_name) if file_name + + snippet_fill_in_visibility(visibility) if visibility + end + end +end diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb index 504a9b764cf..8dda16af625 100644 --- a/spec/support/helpers/features/sorting_helpers.rb +++ b/spec/support/helpers/features/sorting_helpers.rb @@ -4,33 +4,27 @@ # # Usage: # describe "..." do -# include Spec::Support::Helpers::Features::SortingHelpers +# include Features::SortingHelpers # ... # # sort_by("Last updated") # -module Spec - module Support - module Helpers - module Features - module SortingHelpers - def sort_by(value) - find('.filter-dropdown-container .dropdown').click +module Features + module SortingHelpers + def sort_by(value) + find('.filter-dropdown-container .dropdown').click - page.within('ul.dropdown-menu.dropdown-menu-right li') do - click_link(value) - end - end - - # pajamas_sort_by is used to sort new pajamas dropdowns. When - # all of the dropdowns are converted, pajamas_sort_by can be renamed to sort_by - # https://gitlab.com/groups/gitlab-org/-/epics/7551 - def pajamas_sort_by(value) - find('.filter-dropdown-container .gl-new-dropdown').click - find('.gl-new-dropdown-item', text: value).click - end - end + page.within('ul.dropdown-menu.dropdown-menu-right li') do + click_link(value) end end + + # pajamas_sort_by is used to sort new pajamas dropdowns. When + # all of the dropdowns are converted, pajamas_sort_by can be renamed to sort_by + # https://gitlab.com/groups/gitlab-org/-/epics/7551 + def pajamas_sort_by(value) + find('.filter-dropdown-container .gl-new-dropdown').click + find('.gl-new-dropdown-item', text: value).click + end end end diff --git a/spec/support/helpers/features/source_editor_spec_helpers.rb b/spec/support/helpers/features/source_editor_spec_helpers.rb index f7eb2a52507..e20ded60b01 100644 --- a/spec/support/helpers/features/source_editor_spec_helpers.rb +++ b/spec/support/helpers/features/source_editor_spec_helpers.rb @@ -2,24 +2,18 @@ # These helpers help you interact within the Source Editor (single-file editor, snippets, etc.). # -module Spec - module Support - module Helpers - module Features - module SourceEditorSpecHelpers - include ActionView::Helpers::JavaScriptHelper +module Features + module SourceEditorSpecHelpers + include ActionView::Helpers::JavaScriptHelper - def editor_set_value(value) - editor = find('.monaco-editor') - uri = editor['data-uri'] - execute_script("localMonaco.getModel('#{uri}').setValue('#{escape_javascript(value)}')") + def editor_set_value(value) + editor = find('.monaco-editor') + uri = editor['data-uri'] + execute_script("localMonaco.getModel('#{uri}').setValue('#{escape_javascript(value)}')") - # We only check that the first line is present because when the content is long, - # only a part of the text will be rendered in the DOM due to scrolling - page.has_selector?('.gl-source-editor .view-lines', text: value.lines.first) - end - end - end + # We only check that the first line is present because when the content is long, + # only a part of the text will be rendered in the DOM due to scrolling + page.has_selector?('.gl-source-editor .view-lines', text: value.lines.first) end end end diff --git a/spec/support/helpers/features/top_nav_spec_helpers.rb b/spec/support/helpers/features/top_nav_spec_helpers.rb index de495eceabc..ecc05189fb4 100644 --- a/spec/support/helpers/features/top_nav_spec_helpers.rb +++ b/spec/support/helpers/features/top_nav_spec_helpers.rb @@ -2,37 +2,31 @@ # These helpers help you interact within the Source Editor (single-file editor, snippets, etc.). # -module Spec - module Support - module Helpers - module Features - module TopNavSpecHelpers - def open_top_nav - find('.js-top-nav-dropdown-toggle').click - end +module Features + module TopNavSpecHelpers + def open_top_nav + find('.js-top-nav-dropdown-toggle').click + end - def within_top_nav - within('.js-top-nav-dropdown-menu') do - yield - end - end + def within_top_nav + within('.js-top-nav-dropdown-menu') do + yield + end + end - def open_top_nav_projects - open_top_nav + def open_top_nav_projects + open_top_nav - within_top_nav do - click_button('Projects') - end - end + within_top_nav do + click_button('Projects') + end + end - def open_top_nav_groups - open_top_nav + def open_top_nav_groups + open_top_nav - within_top_nav do - click_button('Groups') - end - end - end + within_top_nav do + click_button('Groups') end end end diff --git a/spec/support/helpers/features/two_factor_helpers.rb b/spec/support/helpers/features/two_factor_helpers.rb index 08a7665201f..e0469091d96 100644 --- a/spec/support/helpers/features/two_factor_helpers.rb +++ b/spec/support/helpers/features/two_factor_helpers.rb @@ -4,71 +4,86 @@ # # Usage: # describe "..." do -# include Spec::Support::Helpers::Features::TwoFactorHelpers +# include Features::TwoFactorHelpers # ... # # manage_two_factor_authentication # -module Spec - module Support - module Helpers - module Features - module TwoFactorHelpers - def manage_two_factor_authentication - click_on 'Manage two-factor authentication' - expect(page).to have_content("Set up new device") - wait_for_requests - end - - def register_u2f_device(u2f_device = nil, name: 'My device') - u2f_device ||= FakeU2fDevice.new(page, name) - u2f_device.respond_to_u2f_registration - click_on 'Set up new device' - expect(page).to have_content('Your device was successfully set up') - fill_in "Pick a name", with: name - click_on 'Register device' - u2f_device - end +module Features + module TwoFactorHelpers + def copy_recovery_codes + click_on _('Copy codes') + click_on _('Proceed') + end - # Registers webauthn device via UI - def register_webauthn_device(webauthn_device = nil, name: 'My device') - webauthn_device ||= FakeWebauthnDevice.new(page, name) - webauthn_device.respond_to_webauthn_registration - click_on 'Set up new device' - expect(page).to have_content('Your device was successfully set up') - fill_in 'Pick a name', with: name - click_on 'Register device' - webauthn_device - end + def enable_two_factor_authentication + click_on _('Enable two-factor authentication') + expect(page).to have_content(_('Set up new device')) + wait_for_requests + end - # Adds webauthn device directly via database - def add_webauthn_device(app_id, user, fake_device = nil, name: 'My device') - fake_device ||= WebAuthn::FakeClient.new(app_id) + def manage_two_factor_authentication + click_on 'Manage two-factor authentication' + expect(page).to have_content("Set up new device") + wait_for_requests + end - options_for_create = WebAuthn::Credential.options_for_create( - user: { id: user.webauthn_xid, name: user.username }, - authenticator_selection: { user_verification: 'discouraged' }, - rp: { name: 'GitLab' } - ) - challenge = options_for_create.challenge + # Registers webauthn device via UI + # Remove after `webauthn_without_totp` feature flag is deleted. + def register_webauthn_device(webauthn_device = nil, name: 'My device') + webauthn_device ||= FakeWebauthnDevice.new(page, name) + webauthn_device.respond_to_webauthn_registration + click_on 'Set up new device' + expect(page).to have_content('Your device was successfully set up') + fill_in 'Pick a name', with: name + click_on 'Register device' + webauthn_device + end - device_response = fake_device.create(challenge: challenge).to_json # rubocop:disable Rails/SaveBang - device_registration_params = { device_response: device_response, - name: name } + def webauthn_device_registration(webauthn_device: nil, name: 'My device', password: 'fake') + webauthn_device ||= FakeWebauthnDevice.new(page, name) + webauthn_device.respond_to_webauthn_registration + click_on _('Set up new device') + webauthn_fill_form_and_submit(name: name, password: password) + webauthn_device + end - Webauthn::RegisterService.new( - user, device_registration_params, challenge).execute - FakeWebauthnDevice.new(page, name, fake_device) - end + def webauthn_fill_form_and_submit(name: 'My device', password: 'fake') + content = _('Your device was successfully set up! Give it a name and register it with the GitLab server.') + expect(page).to have_content(content) - def assert_fallback_ui(page) - expect(page).to have_button('Verify code') - expect(page).to have_css('#user_otp_attempt') - expect(page).not_to have_link('Sign in via 2FA code') - expect(page).not_to have_css("#js-authenticate-token-2fa") - end - end + within '[data-testid="create-webauthn"]' do + fill_in _('Device name'), with: name + fill_in _('Current password'), with: password + click_on _('Register device') end end + + # Adds webauthn device directly via database + def add_webauthn_device(app_id, user, fake_device = nil, name: 'My device') + fake_device ||= WebAuthn::FakeClient.new(app_id) + + options_for_create = WebAuthn::Credential.options_for_create( + user: { id: user.webauthn_xid, name: user.username }, + authenticator_selection: { user_verification: 'discouraged' }, + rp: { name: 'GitLab' } + ) + challenge = options_for_create.challenge + + device_response = fake_device.create(challenge: challenge).to_json # rubocop:disable Rails/SaveBang + device_registration_params = { device_response: device_response, + name: name } + + Webauthn::RegisterService.new( + user, device_registration_params, challenge).execute + FakeWebauthnDevice.new(page, name, fake_device) + end + + def assert_fallback_ui(page) + expect(page).to have_button('Verify code') + expect(page).to have_css('#user_otp_attempt') + expect(page).not_to have_link('Sign in via 2FA code') + expect(page).not_to have_css("#js-authenticate-token-2fa") + end end end diff --git a/spec/support/helpers/features/web_ide_spec_helpers.rb b/spec/support/helpers/features/web_ide_spec_helpers.rb index 4793c9479fe..c51116b55b2 100644 --- a/spec/support/helpers/features/web_ide_spec_helpers.rb +++ b/spec/support/helpers/features/web_ide_spec_helpers.rb @@ -4,119 +4,120 @@ # # Usage: # describe "..." do -# include WebIdeSpecHelpers +# include Features::WebIdeSpecHelpers # ... # # ide_visit(project) # ide_commit -# -module WebIdeSpecHelpers - include Spec::Support::Helpers::Features::SourceEditorSpecHelpers - - # Open the IDE from anywhere by first visiting the given project's page - def ide_visit(project) - visit project_path(project) - - ide_visit_from_link - end +module Features + module WebIdeSpecHelpers + include Features::SourceEditorSpecHelpers - # Open the IDE from the current page by clicking the Web IDE link - def ide_visit_from_link(link_sel = 'Web IDE') - new_tab = window_opened_by { click_link(link_sel) } + # Open the IDE from anywhere by first visiting the given project's page + def ide_visit(project) + visit project_path(project) - switch_to_window new_tab - end + ide_visit_from_link + end - def ide_tree_body - page.find('.ide-tree-body') - end + # Open the IDE from the current page by clicking the Web IDE link + def ide_visit_from_link(link_sel = 'Web IDE') + new_tab = window_opened_by { click_link(link_sel) } - def ide_tree_actions - page.find('.ide-tree-actions') - end + switch_to_window new_tab + end - def ide_tab_selector(mode) - ".js-ide-#{mode}-mode" - end + def ide_tree_body + page.find('.ide-tree-body') + end - def ide_folder_row_open?(row) - row.matches_css?('.folder.is-open') - end + def ide_tree_actions + page.find('.ide-tree-actions') + end - # Deletes a file by traversing to `path` - # then clicking the 'Delete' action. - # - # - Throws an error if the file is not found - def ide_delete_file(path) - container = ide_traverse_to_file(path) + def ide_tab_selector(mode) + ".js-ide-#{mode}-mode" + end - click_file_action(container, 'Delete') - end + def ide_folder_row_open?(row) + row.matches_css?('.folder.is-open') + end - # Opens parent directories until the file at `path` - # is exposed. - # - # - Returns a reference to the file row at `path` - # - Throws an error if the file is not found - def ide_traverse_to_file(path) - paths = path.split('/') - container = nil + # Deletes a file by traversing to `path` + # then clicking the 'Delete' action. + # + # - Throws an error if the file is not found + def ide_delete_file(path) + container = ide_traverse_to_file(path) - paths.each_with_index do |path, index| - ide_open_file_row(container) if container - container = find_file_child(container, path, level: index) + click_file_action(container, 'Delete') end - container - end + # Opens parent directories until the file at `path` + # is exposed. + # + # - Returns a reference to the file row at `path` + # - Throws an error if the file is not found + def ide_traverse_to_file(path) + paths = path.split('/') + container = nil + + paths.each_with_index do |path, index| + ide_open_file_row(container) if container + container = find_file_child(container, path, level: index) + end + + container + end - def ide_open_file_row(row) - return if ide_folder_row_open?(row) + def ide_open_file_row(row) + return if ide_folder_row_open?(row) - row.click - end + row.click + end - def ide_set_editor_value(value) - editor_set_value(value) - end + def ide_set_editor_value(value) + editor_set_value(value) + end - def ide_commit_tab_selector - ide_tab_selector('commit') - end + def ide_commit_tab_selector + ide_tab_selector('commit') + end - def ide_commit - find(ide_commit_tab_selector).click + def ide_commit + find(ide_commit_tab_selector).click - commit_to_current_branch - end + commit_to_current_branch + end - private + private - def file_row_container(row) - row ? row.find(:xpath, '..') : ide_tree_body - end + def file_row_container(row) + row ? row.find(:xpath, '..') : ide_tree_body + end - def find_file_child(row, name, level: nil) - container = file_row_container(row) - container.find(".file-row[data-level=\"#{level}\"]", text: name) - end + def find_file_child(row, name, level: nil) + container = file_row_container(row) + container.find(".file-row[data-level=\"#{level}\"]", text: name) + end - def click_file_action(row, text) - row.hover - dropdown = row.find('.ide-new-btn') - dropdown.find('button').click - dropdown.find('button', text: text).click - end + def click_file_action(row, text) + row.hover + dropdown = row.find('.ide-new-btn') + dropdown.find('button').click + dropdown.find('button', text: text).click + end - def commit_to_current_branch(option: 'Commit to master branch', message: '') - within '.multi-file-commit-form' do - fill_in('commit-message', with: message) if message + def commit_to_current_branch(option: 'Commit to master branch', message: '') + within '.multi-file-commit-form' do + fill_in('commit-message', with: message) if message - choose(option) + choose(option) - click_button('Commit') + click_button('Commit') - wait_for_requests + wait_for_requests + end end end end diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index 677cea7b804..60638eb06cd 100644 --- a/spec/support/helpers/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -69,12 +69,6 @@ module FilteredSearchHelpers filtered_search.send_keys(:enter) end - def init_label_search - filtered_search.set('label:=') - # This ensures the dropdown is shown - expect(find('#js-dropdown-label')).not_to have_css('.filter-dropdown-loading') - end - def expect_filtered_search_input_empty expect(find('.filtered-search').value).to eq('') end @@ -190,9 +184,9 @@ module FilteredSearchHelpers ## # For use with gl-filtered-search - def select_tokens(*args, submit: false) + def select_tokens(*args, submit: false, input_text: 'Search') within '[data-testid="filtered-search-input"]' do - find_field('Search').click + find_field(input_text).click args.each do |token| # Move mouse away to prevent invoking tooltips on usernames, which blocks the search input @@ -219,7 +213,7 @@ module FilteredSearchHelpers def submit_search_term(value) click_filtered_search_bar - send_keys(value, :enter) + send_keys(value, :enter, :enter) end def click_filtered_search_bar @@ -230,6 +224,13 @@ module FilteredSearchHelpers find('.gl-filtered-search-token-segment', text: value).click end + def toggle_sort_direction + page.within('.vue-filtered-search-bar-container .sort-dropdown-container') do + page.find("button[title^='Sort direction']").click + wait_for_requests + end + end + def expect_visible_suggestions_list expect(page).to have_css('.gl-filtered-search-suggestion-list') end diff --git a/spec/support/helpers/fixture_helpers.rb b/spec/support/helpers/fixture_helpers.rb index 7b3b8ae5f7a..eafdecb2e3d 100644 --- a/spec/support/helpers/fixture_helpers.rb +++ b/spec/support/helpers/fixture_helpers.rb @@ -8,6 +8,6 @@ module FixtureHelpers end def expand_fixture_path(filename, dir: '') - File.expand_path(Rails.root.join(dir, 'spec', 'fixtures', filename)) + File.expand_path(rails_root_join(dir, 'spec', 'fixtures', filename)) end end diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb index 398a2a20f2f..7db9e0aaf09 100644 --- a/spec/support/helpers/gitaly_setup.rb +++ b/spec/support/helpers/gitaly_setup.rb @@ -10,7 +10,6 @@ require 'securerandom' require 'socket' require 'logger' require 'fileutils' -require 'bundler' require_relative '../../../lib/gitlab/utils' @@ -50,51 +49,18 @@ module GitalySetup expand_path('.gitlab_shell_secret') end - def gemfile - File.join(tmp_tests_gitaly_dir, 'ruby', 'Gemfile') - end - - def gemfile_dir - File.dirname(gemfile) - end - def gitlab_shell_secret_file File.join(tmp_tests_gitlab_shell_dir, '.gitlab_shell_secret') end def env { - 'GEM_PATH' => Gem.path.join(':'), - 'BUNDLER_SETUP' => nil, - 'BUNDLE_INSTALL_FLAGS' => nil, - 'BUNDLE_IGNORE_CONFIG' => '1', - 'BUNDLE_PATH' => bundle_path, - 'BUNDLE_GEMFILE' => gemfile, - 'BUNDLE_JOBS' => '4', - 'BUNDLE_RETRY' => '3', - 'RUBYOPT' => nil, - # Git hooks can't run during tests as the internal API is not running. 'GITALY_TESTING_NO_GIT_HOOKS' => "1", 'GITALY_TESTING_ENABLE_ALL_FEATURE_FLAGS' => "true" } end - def bundle_path - # Allow the user to override BUNDLE_PATH if they need to - return ENV['GITALY_TEST_BUNDLE_PATH'] if ENV['GITALY_TEST_BUNDLE_PATH'] - - if ENV['CI'] - expand_path('vendor/gitaly-ruby') - else - explicit_path = Bundler.configured_bundle_path.explicit_path - - return unless explicit_path - - expand_path(explicit_path) - end - end - def config_path(service) case service when :gitaly @@ -125,10 +91,6 @@ module GitalySetup system(env, *cmd, exception: true, chdir: tmp_tests_gitaly_dir) end - def install_gitaly_gems - run_command(%W[make #{tmp_tests_gitaly_dir}/.ruby-bundle], env: env) - end - def build_gitaly run_command(%w[make all WITH_BUNDLED_GIT=YesPlease], env: env.merge('GIT_VERSION' => nil)) end @@ -188,20 +150,6 @@ module GitalySetup end end - def check_gitaly_config! - LOGGER.debug "Checking gitaly-ruby Gemfile...\n" - - unless File.exist?(gemfile) - message = "#{gemfile} does not exist." - message += "\n\nThis might have happened if the CI artifacts for this build were destroyed." if ENV['CI'] - abort message - end - - LOGGER.debug "Checking gitaly-ruby bundle...\n" - out = ENV['CI'] ? $stdout : '/dev/null' - abort 'bundle check failed' unless system(env, 'bundle', 'check', out: out, chdir: gemfile_dir) - end - def connect_proc(toml) # 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 @@ -343,8 +291,6 @@ module GitalySetup end def spawn_gitaly(toml = nil) - check_gitaly_config! - pids = [] if toml diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/helpers/google_api/cloud_platform_helpers.rb index b9752577c76..3d4ffe88da9 100644 --- a/spec/support/google_api/cloud_platform_helpers.rb +++ b/spec/support/helpers/google_api/cloud_platform_helpers.rb @@ -88,66 +88,68 @@ module GoogleApi # rubocop:disable Metrics/PerceivedComplexity def cloud_platform_cluster_body(options) { - "name": options[:name] || 'string', - "description": options[:description] || 'string', - "initialNodeCount": options[:initialNodeCount] || 'number', - "masterAuth": { - "username": options[:username] || 'string', - "password": options[:password] || 'string', - "clusterCaCertificate": options[:clusterCaCertificate] || load_sample_cert, - "clientCertificate": options[:clientCertificate] || 'string', - "clientKey": options[:clientKey] || 'string' + name: options[:name] || 'string', + description: options[:description] || 'string', + initialNodeCount: options[:initialNodeCount] || 'number', + masterAuth: { + username: options[:username] || 'string', + password: options[:password] || 'string', + clusterCaCertificate: options[:clusterCaCertificate] || load_sample_cert, + clientCertificate: options[:clientCertificate] || 'string', + clientKey: options[:clientKey] || 'string' }, - "loggingService": options[:loggingService] || 'string', - "monitoringService": options[:monitoringService] || 'string', - "network": options[:network] || 'string', - "clusterIpv4Cidr": options[:clusterIpv4Cidr] || 'string', - "subnetwork": options[:subnetwork] || 'string', - "enableKubernetesAlpha": options[:enableKubernetesAlpha] || 'boolean', - "labelFingerprint": options[:labelFingerprint] || 'string', - "selfLink": options[:selfLink] || 'string', - "zone": options[:zone] || 'string', - "endpoint": options[:endpoint] || 'string', - "initialClusterVersion": options[:initialClusterVersion] || 'string', - "currentMasterVersion": options[:currentMasterVersion] || 'string', - "currentNodeVersion": options[:currentNodeVersion] || 'string', - "createTime": options[:createTime] || 'string', - "status": options[:status] || 'RUNNING', - "statusMessage": options[:statusMessage] || 'string', - "nodeIpv4CidrSize": options[:nodeIpv4CidrSize] || 'number', - "servicesIpv4Cidr": options[:servicesIpv4Cidr] || 'string', - "currentNodeCount": options[:currentNodeCount] || 'number', - "expireTime": options[:expireTime] || 'string' + loggingService: options[:loggingService] || 'string', + monitoringService: options[:monitoringService] || 'string', + network: options[:network] || 'string', + clusterIpv4Cidr: options[:clusterIpv4Cidr] || 'string', + subnetwork: options[:subnetwork] || 'string', + enableKubernetesAlpha: options[:enableKubernetesAlpha] || 'boolean', + labelFingerprint: options[:labelFingerprint] || 'string', + selfLink: options[:selfLink] || 'string', + zone: options[:zone] || 'string', + endpoint: options[:endpoint] || 'string', + initialClusterVersion: options[:initialClusterVersion] || 'string', + currentMasterVersion: options[:currentMasterVersion] || 'string', + currentNodeVersion: options[:currentNodeVersion] || 'string', + createTime: options[:createTime] || 'string', + status: options[:status] || 'RUNNING', + statusMessage: options[:statusMessage] || 'string', + nodeIpv4CidrSize: options[:nodeIpv4CidrSize] || 'number', + servicesIpv4Cidr: options[:servicesIpv4Cidr] || 'string', + currentNodeCount: options[:currentNodeCount] || 'number', + expireTime: options[:expireTime] || 'string' } end + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity def cloud_platform_operation_body(options) { - "name": options[:name] || 'operation-1234567891234-1234567', - "zone": options[:zone] || 'us-central1-a', - "operationType": options[:operationType] || 'CREATE_CLUSTER', - "status": options[:status] || 'PENDING', - "detail": options[:detail] || 'detail', - "statusMessage": options[:statusMessage] || '', - "selfLink": options[:selfLink] || 'https://container.googleapis.com/v1/projects/123456789101/zones/us-central1-a/operations/operation-1234567891234-1234567', - "targetLink": options[:targetLink] || 'https://container.googleapis.com/v1/projects/123456789101/zones/us-central1-a/clusters/test-cluster', - "startTime": options[:startTime] || '2017-09-13T16:49:13.055601589Z', - "endTime": options[:endTime] || '' + name: options[:name] || 'operation-1234567891234-1234567', + zone: options[:zone] || 'us-central1-a', + operationType: options[:operationType] || 'CREATE_CLUSTER', + status: options[:status] || 'PENDING', + detail: options[:detail] || 'detail', + statusMessage: options[:statusMessage] || '', + selfLink: options[:selfLink] || 'https://container.googleapis.com/v1/projects/123456789101/zones/us-central1-a/operations/operation-1234567891234-1234567', + targetLink: options[:targetLink] || 'https://container.googleapis.com/v1/projects/123456789101/zones/us-central1-a/clusters/test-cluster', + startTime: options[:startTime] || '2017-09-13T16:49:13.055601589Z', + endTime: options[:endTime] || '' } end def cloud_platform_projects_body(options) { - "projects": [ + projects: [ { - "projectNumber": options[:project_number] || "1234", - "projectId": options[:project_id] || "test-project-1234", - "lifecycleState": "ACTIVE", - "name": options[:name] || "test-project", - "createTime": "2017-12-16T01:48:29.129Z", - "parent": { - "type": "organization", - "id": "12345" + projectNumber: options[:project_number] || "1234", + projectId: options[:project_id] || "test-project-1234", + lifecycleState: "ACTIVE", + name: options[:name] || "test-project", + createTime: "2017-12-16T01:48:29.129Z", + parent: { + type: "organization", + id: "12345" } } ] @@ -156,10 +158,10 @@ module GoogleApi def cloud_platform_projects_billing_info_body(project_id, billing_enabled) { - "name": "projects/#{project_id}/billingInfo", - "projectId": project_id.to_s, - "billingAccountName": "account-name", - "billingEnabled": billing_enabled + name: "projects/#{project_id}/billingInfo", + projectId: project_id.to_s, + billingAccountName: "account-name", + billingEnabled: billing_enabled } end end diff --git a/spec/support/graphql/arguments.rb b/spec/support/helpers/graphql/arguments.rb index 478a460a0f6..478a460a0f6 100644 --- a/spec/support/graphql/arguments.rb +++ b/spec/support/helpers/graphql/arguments.rb diff --git a/spec/support/graphql/fake_query_type.rb b/spec/support/helpers/graphql/fake_query_type.rb index 18cf2cf3e82..bdf30908532 100644 --- a/spec/support/graphql/fake_query_type.rb +++ b/spec/support/helpers/graphql/fake_query_type.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'graphql' module Graphql diff --git a/spec/support/graphql/fake_tracer.rb b/spec/support/helpers/graphql/fake_tracer.rb index 58688c9abd0..58688c9abd0 100644 --- a/spec/support/graphql/fake_tracer.rb +++ b/spec/support/helpers/graphql/fake_tracer.rb diff --git a/spec/support/graphql/field_inspection.rb b/spec/support/helpers/graphql/field_inspection.rb index 8730f82b893..8730f82b893 100644 --- a/spec/support/graphql/field_inspection.rb +++ b/spec/support/helpers/graphql/field_inspection.rb diff --git a/spec/support/graphql/field_selection.rb b/spec/support/helpers/graphql/field_selection.rb index 432340cfdb5..432340cfdb5 100644 --- a/spec/support/graphql/field_selection.rb +++ b/spec/support/helpers/graphql/field_selection.rb diff --git a/spec/support/graphql/resolver_factories.rb b/spec/support/helpers/graphql/resolver_factories.rb index 76df4b58943..76df4b58943 100644 --- a/spec/support/graphql/resolver_factories.rb +++ b/spec/support/helpers/graphql/resolver_factories.rb diff --git a/spec/support/graphql/subscriptions/action_cable/mock_action_cable.rb b/spec/support/helpers/graphql/subscriptions/action_cable/mock_action_cable.rb index 5467564a79e..2ccc62a8729 100644 --- a/spec/support/graphql/subscriptions/action_cable/mock_action_cable.rb +++ b/spec/support/helpers/graphql/subscriptions/action_cable/mock_action_cable.rb @@ -13,7 +13,7 @@ module Graphql attr_reader :mock_broadcasted_messages - def stream_from(stream_name, coder: nil, &block) + def stream_from(stream_name, coder: nil, &block) # rubocop:disable Lint/UnusedMethodArgument # Rails uses `coder`, we don't block ||= ->(msg) { @mock_broadcasted_messages << msg } MockActionCable.mock_stream_for(stream_name).add_mock_channel(self, block) @@ -30,7 +30,7 @@ module Graphql end def mock_broadcast(message) - @mock_channels.each do |channel, handler| + @mock_channels.each_value do |handler| handler && handler.call(message) end end diff --git a/spec/support/graphql/subscriptions/action_cable/mock_gitlab_schema.rb b/spec/support/helpers/graphql/subscriptions/action_cable/mock_gitlab_schema.rb index cd5d78cc78b..cd5d78cc78b 100644 --- a/spec/support/graphql/subscriptions/action_cable/mock_gitlab_schema.rb +++ b/spec/support/helpers/graphql/subscriptions/action_cable/mock_gitlab_schema.rb diff --git a/spec/support/graphql/subscriptions/notes/helper.rb b/spec/support/helpers/graphql/subscriptions/notes/helper.rb index 9a552f9879e..9a552f9879e 100644 --- a/spec/support/graphql/subscriptions/notes/helper.rb +++ b/spec/support/helpers/graphql/subscriptions/notes/helper.rb diff --git a/spec/support/graphql/var.rb b/spec/support/helpers/graphql/var.rb index 4f2c774e898..4f2c774e898 100644 --- a/spec/support/graphql/var.rb +++ b/spec/support/helpers/graphql/var.rb diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 2176a477371..a9ad853b028 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -89,13 +89,16 @@ module GraphqlHelpers # All mutations accept a single `:input` argument. Wrap arguments here. args = { input: args } if resolver_class <= ::Mutations::BaseMutation && !args.key?(:input) - resolve_field(field, obj, - args: args, - ctx: ctx, - schema: schema, - object_type: resolver_parent, - extras: { parent: parent, lookahead: lookahead }, - arg_style: arg_style) + resolve_field( + field, + obj, + args: args, + ctx: ctx, + schema: schema, + object_type: resolver_parent, + extras: { parent: parent, lookahead: lookahead }, + arg_style: arg_style + ) end # Resolve the value of a field on an object. @@ -310,7 +313,7 @@ module GraphqlHelpers "{ #{q} }" end - def graphql_mutation(name, input, fields = nil, &block) + def graphql_mutation(name, input, fields = nil, excluded = [], &block) raise ArgumentError, 'Please pass either `fields` parameter or a block to `#graphql_mutation`, but not both.' if fields.present? && block name = name.graphql_name if name.respond_to?(:graphql_name) @@ -319,7 +322,7 @@ module GraphqlHelpers mutation_field = GitlabSchema.mutation.fields[mutation_name] fields = yield if block - fields ||= all_graphql_fields_for(mutation_field.type.to_type_signature) + fields ||= all_graphql_fields_for(mutation_field.type.to_type_signature, excluded: excluded) query = <<~MUTATION mutation(#{input_variable_name}: #{mutation_field.arguments['input'].type.to_type_signature}) { @@ -513,20 +516,23 @@ module GraphqlHelpers end def post_graphql_mutation(mutation, current_user: nil, token: {}) - post_graphql(mutation.query, - current_user: current_user, - variables: mutation.variables, - token: token) + post_graphql( + mutation.query, + current_user: current_user, + variables: mutation.variables, + token: token + ) end def post_graphql_mutation_with_uploads(mutation, current_user: nil) file_paths = file_paths_in_mutation(mutation) params = mutation_to_apollo_uploads_param(mutation, files: file_paths) - workhorse_post_with_file(api('/', current_user, version: 'graphql'), - params: params, - file_key: '1' - ) + workhorse_post_with_file( + api('/', current_user, version: 'graphql'), + params: params, + file_key: '1' + ) end def file_paths_in_mutation(mutation) @@ -633,7 +639,11 @@ module GraphqlHelpers end def expect_graphql_errors_to_be_empty - expect(flattened_errors).to be_empty + # TODO: using eq([]) instead of be_empty makes it print out the full error message including the + # raisedAt key which contains the full stacktrace. This is necessary to know where the + # unexpected error occurred during tests. + # This or an equivalent fix should be added in a separate MR on master. + expect(flattened_errors).to eq([]) end # Helps migrate to the new GraphQL interpreter, diff --git a/spec/support/http_io/http_io_helpers.rb b/spec/support/helpers/http_io_helpers.rb index 0193db81fa9..638d780cdc2 100644 --- a/spec/support/http_io/http_io_helpers.rb +++ b/spec/support/helpers/http_io_helpers.rb @@ -31,9 +31,7 @@ module HttpIOHelpers def remote_url_response_headers(response_status, from, to, size) { 'Content-Type' => 'text/plain' }.tap do |headers| - if response_status == 206 - headers.merge('Content-Range' => "bytes #{from}-#{to}/#{size}") - end + headers.merge('Content-Range' => "bytes #{from}-#{to}/#{size}") if response_status == 206 end end diff --git a/spec/support/helpers/jira_integration_helpers.rb b/spec/support/helpers/jira_integration_helpers.rb index 66940314589..098f2968b0b 100644 --- a/spec/support/helpers/jira_integration_helpers.rb +++ b/spec/support/helpers/jira_integration_helpers.rb @@ -11,7 +11,7 @@ module JiraIntegrationHelpers jira_issue_transition_id = '1' jira_tracker.update!( - url: url, username: username, password: password, + url: url, username: username, password: password, jira_auth_type: 0, jira_issue_transition_id: jira_issue_transition_id, active: true ) end diff --git a/spec/support/helpers/keyset_pagination_helpers.rb b/spec/support/helpers/keyset_pagination_helpers.rb new file mode 100644 index 00000000000..4a476c47fda --- /dev/null +++ b/spec/support/helpers/keyset_pagination_helpers.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module KeysetPaginationHelpers + def pagination_links(response) + link = response.headers['LINK'] + return unless link + + link.split(',').filter_map do |link| + match = link.match(/<(?<url>.*)>; rel="(?<rel>\w+)"/) + next unless match + + { url: match[:url], rel: match[:rel] } + end + end + + def pagination_params_from_next_url(response) + next_link = pagination_links(response).find { |link| link[:rel] == 'next' } + next_url = next_link&.fetch(:url) + return unless next_url + + Rack::Utils.parse_query(URI.parse(next_url).query) + end +end diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index 5fde80e6dc9..67315b9d81e 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -51,7 +51,7 @@ module LoginHelpers def gitlab_enable_admin_mode_sign_in(user) visit new_admin_session_path fill_in 'user_password', with: user.password - click_button 'Enter Admin Mode' + click_button 'Enter admin mode' wait_for_requests end @@ -82,7 +82,7 @@ module LoginHelpers open_top_nav within_top_nav do - click_on 'Leave Admin Mode' + click_on 'Leave admin mode' end end @@ -94,8 +94,8 @@ module LoginHelpers # remember - Whether or not to check "Remember me" (default: false) # two_factor_auth - If two-factor authentication is enabled (default: false) # password - password to attempt to login with (default: user.password) - def gitlab_sign_in_with(user, remember: false, two_factor_auth: false, password: nil) - visit new_user_session_path + def gitlab_sign_in_with(user, remember: false, two_factor_auth: false, password: nil, visit: true) + visit new_user_session_path if visit fill_in "user_login", with: user.email fill_in "user_password", with: (password || user.password) @@ -114,9 +114,9 @@ module LoginHelpers def login_via(provider, user, uid, remember_me: false, additional_info: {}) mock_auth_hash(provider, uid, user.email, additional_info: additional_info) visit new_user_session_path - expect(page).to have_content('Sign in with') + expect(page).to have_css('.omniauth-container') - check 'remember_me' if remember_me + check 'remember_me_omniauth' if remember_me click_button "oauth-login-#{provider}" end @@ -139,11 +139,6 @@ module LoginHelpers click_link_or_button "oauth-login-#{provider}" end - def fake_successful_u2f_authentication - allow(U2fRegistration).to receive(:authenticate).and_return(true) - FakeU2fDevice.new(page, nil).fake_u2f_authentication - end - def fake_successful_webauthn_authentication allow_any_instance_of(Webauthn::AuthenticateService).to receive(:execute).and_return(true) FakeWebauthnDevice.new(page, nil).fake_webauthn_authentication @@ -218,6 +213,15 @@ module LoginHelpers config end + def prepare_provider_route(provider_name) + routes = Rails.application.routes + routes.disable_clear_and_finalize = true + routes.formatter.clear + routes.draw do + post "/users/auth/#{provider_name}" => "omniauth_callbacks##{provider_name}" + end + end + def stub_omniauth_provider(provider, context: Rails.application) env = env_from_context(context) diff --git a/spec/support/helpers/markdown_feature.rb b/spec/support/helpers/markdown_feature.rb index 0cb2863dc2c..5d9ef557ae6 100644 --- a/spec/support/helpers/markdown_feature.rb +++ b/spec/support/helpers/markdown_feature.rb @@ -48,6 +48,10 @@ class MarkdownFeature @issue ||= create(:issue, project: project) end + def work_item + @issue ||= create(:work_item, project: project) + end + def merge_request @merge_request ||= create(:merge_request, :simple, source_project: project) end @@ -106,6 +110,10 @@ class MarkdownFeature @xissue ||= create(:issue, project: xproject) end + def xwork_item + @xwork_item ||= create(:work_item, project: xproject) + end + def xmerge_request @xmerge_request ||= create(:merge_request, :simple, source_project: xproject) end diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb index a384e44f428..417baeda33a 100644 --- a/spec/support/helpers/metrics_dashboard_helpers.rb +++ b/spec/support/helpers/metrics_dashboard_helpers.rb @@ -49,8 +49,4 @@ module MetricsDashboardHelpers def business_metric_title Enums::PrometheusMetric.group_details[:business][:group_title] end - - def self_monitoring_dashboard_path - Metrics::Dashboard::SelfMonitoringDashboardService::DASHBOARD_PATH - end end diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb index 6fc5904fc83..1b8c3388051 100644 --- a/spec/support/helpers/migrations_helpers.rb +++ b/spec/support/helpers/migrations_helpers.rb @@ -92,7 +92,7 @@ module MigrationsHelpers end def reset_column_information(klass) - klass.reset_column_information + klass.reset_column_information if klass.instance_variable_get(:@table_name) end # In some migration tests, we're using factories to create records, diff --git a/spec/support/migrations_helpers/cluster_helpers.rb b/spec/support/helpers/migrations_helpers/cluster_helpers.rb index 03104e22bcf..03104e22bcf 100644 --- a/spec/support/migrations_helpers/cluster_helpers.rb +++ b/spec/support/helpers/migrations_helpers/cluster_helpers.rb diff --git a/spec/support/helpers/migrations_helpers/namespaces_helper.rb b/spec/support/helpers/migrations_helpers/namespaces_helper.rb new file mode 100644 index 00000000000..d9a4e0d1731 --- /dev/null +++ b/spec/support/helpers/migrations_helpers/namespaces_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module MigrationHelpers + module NamespacesHelpers + def create_namespace(name, visibility, options = {}) + table(:namespaces).create!( + { + name: name, + path: name, + type: 'Group', + visibility_level: visibility + }.merge(options)) + end + end +end diff --git a/spec/support/migrations_helpers/schema_version_finder.rb b/spec/support/helpers/migrations_helpers/schema_version_finder.rb index b677db7ea26..69469959ce5 100644 --- a/spec/support/migrations_helpers/schema_version_finder.rb +++ b/spec/support/helpers/migrations_helpers/schema_version_finder.rb @@ -7,7 +7,8 @@ # to find and use schema prior to specified one. # # @example -# RSpec.describe CleanupThings, :migration, schema: MigrationHelpers::SchemaVersionFinder.migration_prior(AddNotNullConstraint) do ... +# RSpec.describe CleanupThings, :migration, +# schema: MigrationHelpers::SchemaVersionFinder.migration_prior(AddNotNullConstraint) do ... # # SchemaVersionFinder returns schema version prior to the one specified, which allows to then add # invalid records to the database, which in return allows to properly test data migration. diff --git a/spec/support/migrations_helpers/vulnerabilities_findings_helper.rb b/spec/support/helpers/migrations_helpers/vulnerabilities_findings_helper.rb index 9a5313c3fa4..1f8505978f5 100644 --- a/spec/support/migrations_helpers/vulnerabilities_findings_helper.rb +++ b/spec/support/helpers/migrations_helpers/vulnerabilities_findings_helper.rb @@ -7,7 +7,7 @@ module MigrationHelpers { project_fingerprint: SecureRandom.hex(20), - location_fingerprint: Digest::SHA1.hexdigest(SecureRandom.hex(10)), + location_fingerprint: Digest::SHA1.hexdigest(SecureRandom.hex(10)), # rubocop:disable Fips/SHA1 uuid: uuid, name: "Vulnerability Finding #{uuid}", metadata_version: '1.3', @@ -20,7 +20,7 @@ module MigrationHelpers "description" => "The cipher does not provide data integrity update 1", "message" => "The cipher does not provide data integrity", "cve" => "818bf5dacb291e15d9e6dc3c5ac32178:CIPHER", - "solution" => "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.", + "solution" => "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.", # rubocop:disable Layout/LineLength "location" => { "file" => "maven/src/main/java/com/gitlab/security_products/tests/App.java", "start_line" => 29, @@ -49,8 +49,8 @@ module MigrationHelpers "body" => nil, "headers" => [ { - "name" => "Accept", - "value" => "*/*" + "name" => "Accept", + "value" => "*/*" } ] }, @@ -60,8 +60,8 @@ module MigrationHelpers "body" => nil, "headers" => [ { - "name" => "Content-Length", - "value" => "0" + "name" => "Content-Length", + "value" => "0" } ] }, diff --git a/spec/support/models/ci/partitioning_testing/cascade_check.rb b/spec/support/helpers/models/ci/partitioning_testing/cascade_check.rb index bcfc9675476..81c2d2cb225 100644 --- a/spec/support/models/ci/partitioning_testing/cascade_check.rb +++ b/spec/support/helpers/models/ci/partitioning_testing/cascade_check.rb @@ -19,7 +19,7 @@ module PartitioningTesting 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 + super || (args.first == :after && args.second == :check_partition_cascade_value) end end end diff --git a/spec/support/models/ci/partitioning_testing/partition_identifiers.rb b/spec/support/helpers/models/ci/partitioning_testing/partition_identifiers.rb index aa091095fb6..aa091095fb6 100644 --- a/spec/support/models/ci/partitioning_testing/partition_identifiers.rb +++ b/spec/support/helpers/models/ci/partitioning_testing/partition_identifiers.rb diff --git a/spec/support/models/ci/partitioning_testing/rspec_hooks.rb b/spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb index 39b15ba8721..3f0a2bb7f3b 100644 --- a/spec/support/models/ci/partitioning_testing/rspec_hooks.rb +++ b/spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb @@ -4,6 +4,10 @@ RSpec.configure do |config| config.include Ci::PartitioningTesting::PartitionIdentifiers config.around(:each, :ci_partitionable) do |example| + unless Ci::Build.table_name.to_s.starts_with?('p_') + skip 'Skipping partitioning tests until `ci_builds` is partitioned' + end + Ci::PartitioningTesting::SchemaHelpers.with_routing_tables do example.run end diff --git a/spec/support/models/ci/partitioning_testing/schema_helpers.rb b/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb index 4107bbcb976..4107bbcb976 100644 --- a/spec/support/models/ci/partitioning_testing/schema_helpers.rb +++ b/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb diff --git a/spec/support/models/merge_request_without_merge_request_diff.rb b/spec/support/helpers/models/merge_request_without_merge_request_diff.rb index 5cdf1feb7a5..e9f97a2c95a 100644 --- a/spec/support/models/merge_request_without_merge_request_diff.rb +++ b/spec/support/helpers/models/merge_request_without_merge_request_diff.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MergeRequestWithoutMergeRequestDiff < ::MergeRequest +class MergeRequestWithoutMergeRequestDiff < ::MergeRequest # rubocop:disable Gitlab/NamespacedClass self.inheritance_column = :_type_disabled def ensure_merge_request_diff; end diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb index 48c6e590e1b..9a6af5fb8ae 100644 --- a/spec/support/helpers/navbar_structure_helper.rb +++ b/spec/support/helpers/navbar_structure_helper.rb @@ -73,7 +73,7 @@ module NavbarStructureHelper insert_after_sub_nav_item( _('Package Registry'), within: _('Packages and registries'), - new_sub_nav_item_name: _('Infrastructure Registry') + new_sub_nav_item_name: _('Terraform modules') ) end @@ -100,12 +100,28 @@ module NavbarStructureHelper def insert_infrastructure_google_cloud_nav insert_after_sub_nav_item( - _('Terraform'), + s_('Terraform|Terraform states'), within: _('Infrastructure'), new_sub_nav_item_name: _('Google Cloud') ) end + def insert_infrastructure_aws_nav + insert_after_sub_nav_item( + _('Google Cloud'), + within: _('Infrastructure'), + new_sub_nav_item_name: _('AWS') + ) + end + + def insert_model_experiments_nav(within) + insert_after_sub_nav_item( + within, + within: _('Packages and registries'), + new_sub_nav_item_name: _('Model experiments') + ) + end + def project_analytics_sub_nav_item [ _('Value stream'), diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb index fa2705a64fa..40f1f6fe6f3 100644 --- a/spec/support/helpers/note_interaction_helpers.rb +++ b/spec/support/helpers/note_interaction_helpers.rb @@ -7,6 +7,6 @@ module NoteInteractionHelpers note_element = find_by_scrolling("#note_#{note.id}") note_element.find('.more-actions-toggle').click - note_element.find('.more-actions .dropdown-menu li', match: :first) + note_element.find('.more-actions li', match: :first) end end diff --git a/spec/support/helpers/project_template_test_helper.rb b/spec/support/helpers/project_template_test_helper.rb index bedbb8601e8..35e40faeea7 100644 --- a/spec/support/helpers/project_template_test_helper.rb +++ b/spec/support/helpers/project_template_test_helper.rb @@ -4,12 +4,12 @@ module ProjectTemplateTestHelper def all_templates %w[ rails spring express iosswift dotnetcore android - gomicro gatsby hugo jekyll plainhtml gitbook + gomicro gatsby hugo jekyll plainhtml 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 + pelican bridgetown typo3_distribution laravel ] end end diff --git a/spec/support/prometheus/metric_builders.rb b/spec/support/helpers/prometheus/metric_builders.rb index 512e32a44d0..53329ee8dce 100644 --- a/spec/support/prometheus/metric_builders.rb +++ b/spec/support/helpers/prometheus/metric_builders.rb @@ -16,9 +16,9 @@ module Prometheus def simple_metrics(added_metric_name: 'metric_a') [ - simple_metric(required_metrics: %W(#{added_metric_name} metric_b), queries: simple_queries), + simple_metric(required_metrics: %W[#{added_metric_name} metric_b], queries: simple_queries), simple_metric(required_metrics: [added_metric_name], queries: [simple_query('empty')]), - simple_metric(required_metrics: %w{metric_c}) + simple_metric(required_metrics: %w[metric_c]) ] end diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb index 5be9ba9ae1e..e8fa73a1b95 100644 --- a/spec/support/helpers/query_recorder.rb +++ b/spec/support/helpers/query_recorder.rb @@ -102,6 +102,10 @@ module ActiveRecord @occurrences ||= @log.group_by(&:to_s).transform_values(&:count) end + def occurrences_starting_with(str) + occurrences.select { |query, _count| query.starts_with?(str) } + end + def ignorable?(values) return true if skip_schema_queries && values[:name]&.include?("SCHEMA") return true if values[:name]&.match(/License Load/) diff --git a/spec/support/redis/redis_helpers.rb b/spec/support/helpers/redis_helpers.rb index 2c5ceb2f09e..2c5ceb2f09e 100644 --- a/spec/support/redis/redis_helpers.rb +++ b/spec/support/helpers/redis_helpers.rb diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb index 9f37cf61cc9..45467fb7099 100644 --- a/spec/support/helpers/repo_helpers.rb +++ b/spec/support/helpers/repo_helpers.rb @@ -41,6 +41,7 @@ eos line_code: '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14', line_code_path: 'files/ruby/popen.rb', del_line_code: '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13', + referenced_by: [], message: <<eos Change some files Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> @@ -56,6 +57,7 @@ eos author_full_name: "Sytse Sijbrandij", author_email: "sytse@gitlab.com", files_changed_count: 1, + referenced_by: [], message: <<eos Add directory structure for tree_helper spec @@ -74,6 +76,7 @@ eos sha: "913c66a37b4a45b9769037c55c2d238bd0942d2e", author_full_name: "Dmitriy Zaporozhets", author_email: "dmitriy.zaporozhets@gmail.com", + referenced_by: [], message: <<eos Files, encoding and much more Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> @@ -89,6 +92,7 @@ eos author_email: "dmitriy.zaporozhets@gmail.com", old_blob_id: '33f3729a45c02fc67d00adb1b8bca394b0e761d9', new_blob_id: '2f63565e7aac07bcdadb654e253078b727143ec4', + referenced_by: [], message: <<eos Modified image Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb index eab30be9243..75853371c0f 100644 --- a/spec/support/helpers/search_helpers.rb +++ b/spec/support/helpers/search_helpers.rb @@ -2,9 +2,6 @@ module SearchHelpers def fill_in_search(text) - # Once the `new_header_search` feature flag has been removed - # We can remove the `.search-input-wrap` selector - # https://gitlab.com/gitlab-org/gitlab/-/issues/339348 page.within('.header-search-new') do find('#search').click fill_in 'search', with: text @@ -14,10 +11,7 @@ module SearchHelpers end def submit_search(query) - # Once the `new_header_search` feature flag has been removed - # We can remove the `.search-form` selector - # https://gitlab.com/gitlab-org/gitlab/-/issues/339348 - page.within('.header-search, .search-form, .search-page-form') do + page.within('.header-search, .search-page-form') do field = find_field('search') field.click field.fill_in(with: query) diff --git a/spec/support/helpers/session_helpers.rb b/spec/support/helpers/session_helpers.rb index 394a401afca..5554695a27d 100644 --- a/spec/support/helpers/session_helpers.rb +++ b/spec/support/helpers/session_helpers.rb @@ -39,4 +39,10 @@ module SessionHelpers def get_ttl(key) Gitlab::Redis::Sessions.with { |redis| redis.ttl(key) } end + + def expire_session + get_session_keys.each do |key| + ::Gitlab::Redis::Sessions.with { |redis| redis.expire(key, -1) } + end + end end diff --git a/spec/support/helpers/snowplow_helpers.rb b/spec/support/helpers/snowplow_helpers.rb index 265e1c38b09..a04e5d46df9 100644 --- a/spec/support/helpers/snowplow_helpers.rb +++ b/spec/support/helpers/snowplow_helpers.rb @@ -46,7 +46,7 @@ module SnowplowHelpers # } # ] # ) - def expect_snowplow_event(category:, action:, context: nil, **kwargs) + def expect_snowplow_event(category:, action:, context: nil, tracking_method: :event, **kwargs) if context if context.is_a?(Array) kwargs[:context] = [] @@ -60,7 +60,7 @@ module SnowplowHelpers end end - expect(Gitlab::Tracking).to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking + expect(Gitlab::Tracking).to have_received(tracking_method) # rubocop:disable RSpec/ExpectGitlabTracking .with(category, action, **kwargs).at_least(:once) end @@ -79,11 +79,11 @@ module SnowplowHelpers # expect_no_snowplow_event # end # end - def expect_no_snowplow_event(category: nil, action: nil, **kwargs) + def expect_no_snowplow_event(category: nil, action: nil, tracking_method: :event, **kwargs) if category && action - expect(Gitlab::Tracking).not_to have_received(:event).with(category, action, **kwargs) # rubocop:disable RSpec/ExpectGitlabTracking + expect(Gitlab::Tracking).not_to have_received(tracking_method).with(category, action, **kwargs) # rubocop:disable RSpec/ExpectGitlabTracking else - expect(Gitlab::Tracking).not_to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking + expect(Gitlab::Tracking).not_to have_received(tracking_method) # rubocop:disable RSpec/ExpectGitlabTracking end end end diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index 4ca8f26be9e..4c997aceeee 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -102,7 +102,7 @@ module StubConfiguration messages[storage_name] = Gitlab::GitalyClient::StorageSettings.new(storage_hash.to_h) end - allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages)) + allow(Gitlab.config.repositories).to receive(:storages).and_return(::GitlabSettings::Options.build(messages)) end def stub_sentry_settings(enabled: true) @@ -175,11 +175,11 @@ module StubConfiguration end end - # Support nested hashes by converting all values into Settingslogic objects + # Support nested hashes by converting all values into GitlabSettings::Objects objects def to_settings(hash) hash.transform_values do |value| if value.is_a? Hash - Settingslogic.new(value.deep_stringify_keys) + ::GitlabSettings::Options.build(value) else value end diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb index e5c30769531..748ea525e40 100644 --- a/spec/support/helpers/stub_gitlab_calls.rb +++ b/spec/support/helpers/stub_gitlab_calls.rb @@ -94,10 +94,10 @@ module StubGitlabCalls end def stub_commonmark_sourcepos_disabled - render_options = Banzai::Filter::MarkdownEngines::CommonMark::RENDER_OPTIONS + engine = Banzai::Filter::MarkdownFilter.render_engine(nil) - allow_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance| - allow(instance).to receive(:render_options).and_return(render_options) + allow_next_instance_of(engine) do |instance| + allow(instance).to receive(:sourcepos_disabled?).and_return(true) end end diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb index 6b633856228..e7587e59a91 100644 --- a/spec/support/helpers/stub_object_storage.rb +++ b/spec/support/helpers/stub_object_storage.rb @@ -2,9 +2,11 @@ module StubObjectStorage def stub_dependency_proxy_object_storage(**params) - stub_object_storage_uploader(config: ::Gitlab.config.dependency_proxy.object_store, - uploader: ::DependencyProxy::FileUploader, - **params) + stub_object_storage_uploader( + config: ::Gitlab.config.dependency_proxy.object_store, + uploader: ::DependencyProxy::FileUploader, + **params + ) end def stub_object_storage_uploader( @@ -15,7 +17,7 @@ module StubObjectStorage direct_upload: false, cdn: {} ) - old_config = Settingslogic.new(config.deep_stringify_keys) + old_config = ::GitlabSettings::Options.build(config.to_h.deep_stringify_keys) new_config = config.to_h.deep_symbolize_keys.merge({ enabled: enabled, proxy_download: proxy_download, @@ -30,14 +32,16 @@ module StubObjectStorage allow(config).to receive(:proxy_download) { proxy_download } allow(config).to receive(:direct_upload) { direct_upload } - uploader_config = Settingslogic.new(new_config.deep_stringify_keys) + uploader_config = ::GitlabSettings::Options.build(new_config.to_h.deep_stringify_keys) allow(uploader).to receive(:object_store_options).and_return(uploader_config) allow(uploader.options).to receive(:object_store).and_return(uploader_config) return unless enabled - stub_object_storage(connection_params: uploader.object_store_credentials, - remote_directory: old_config.remote_directory) + stub_object_storage( + connection_params: uploader.object_store_credentials, + remote_directory: old_config.remote_directory + ) end def stub_object_storage(connection_params:, remote_directory:) @@ -55,63 +59,99 @@ module StubObjectStorage end def stub_artifacts_object_storage(uploader = JobArtifactUploader, **params) - stub_object_storage_uploader(config: Gitlab.config.artifacts.object_store, - uploader: uploader, - **params) + stub_object_storage_uploader( + config: Gitlab.config.artifacts.object_store, + uploader: uploader, + **params + ) end def stub_external_diffs_object_storage(uploader = described_class, **params) - stub_object_storage_uploader(config: Gitlab.config.external_diffs.object_store, - uploader: uploader, - **params) + stub_object_storage_uploader( + config: Gitlab.config.external_diffs.object_store, + uploader: uploader, + **params + ) end def stub_lfs_object_storage(**params) - stub_object_storage_uploader(config: Gitlab.config.lfs.object_store, - uploader: LfsObjectUploader, - **params) + stub_object_storage_uploader( + config: Gitlab.config.lfs.object_store, + uploader: LfsObjectUploader, + **params + ) end def stub_package_file_object_storage(**params) - stub_object_storage_uploader(config: Gitlab.config.packages.object_store, - uploader: ::Packages::PackageFileUploader, - **params) + stub_object_storage_uploader( + config: Gitlab.config.packages.object_store, + uploader: ::Packages::PackageFileUploader, + **params + ) end def stub_rpm_repository_file_object_storage(**params) - stub_object_storage_uploader(config: Gitlab.config.packages.object_store, - uploader: ::Packages::Rpm::RepositoryFileUploader, - **params) + stub_object_storage_uploader( + config: Gitlab.config.packages.object_store, + uploader: ::Packages::Rpm::RepositoryFileUploader, + **params + ) end def stub_composer_cache_object_storage(**params) - stub_object_storage_uploader(config: Gitlab.config.packages.object_store, - uploader: ::Packages::Composer::CacheUploader, - **params) + stub_object_storage_uploader( + config: Gitlab.config.packages.object_store, + uploader: ::Packages::Composer::CacheUploader, + **params + ) + end + + def debian_component_file_object_storage(**params) + stub_object_storage_uploader( + config: Gitlab.config.packages.object_store, + uploader: ::Packages::Debian::ComponentFileUploader, + **params + ) + end + + def debian_distribution_release_file_object_storage(**params) + stub_object_storage_uploader( + config: Gitlab.config.packages.object_store, + uploader: ::Packages::Debian::DistributionReleaseFileUploader, + **params + ) end def stub_uploads_object_storage(uploader = described_class, **params) - stub_object_storage_uploader(config: Gitlab.config.uploads.object_store, - uploader: uploader, - **params) + stub_object_storage_uploader( + config: Gitlab.config.uploads.object_store, + uploader: uploader, + **params + ) end def stub_ci_secure_file_object_storage(**params) - stub_object_storage_uploader(config: Gitlab.config.ci_secure_files.object_store, - uploader: Ci::SecureFileUploader, - **params) + stub_object_storage_uploader( + config: Gitlab.config.ci_secure_files.object_store, + uploader: Ci::SecureFileUploader, + **params + ) end def stub_terraform_state_object_storage(**params) - stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store, - uploader: Terraform::StateUploader, - **params) + stub_object_storage_uploader( + config: Gitlab.config.terraform_state.object_store, + uploader: Terraform::StateUploader, + **params + ) end def stub_pages_object_storage(uploader = described_class, **params) - stub_object_storage_uploader(config: Gitlab.config.pages.object_store, - uploader: uploader, - **params) + stub_object_storage_uploader( + config: Gitlab.config.pages.object_store, + uploader: uploader, + **params + ) end def stub_object_storage_multipart_init(endpoint, upload_id = "upload_id") @@ -125,4 +165,16 @@ module StubObjectStorage </InitiateMultipartUploadResult> EOS end + + def stub_object_storage_multipart_init_with_final_store_path(full_path, upload_id = "upload_id") + stub_request(:post, %r{\A#{full_path}\?uploads\z}) + .to_return status: 200, body: <<-EOS.strip_heredoc + <?xml version="1.0" encoding="UTF-8"?> + <InitiateMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> + <Bucket>example-bucket</Bucket> + <Key>example-object</Key> + <UploadId>#{upload_id}</UploadId> + </InitiateMultipartUploadResult> + EOS + end end diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 3403064bf0b..ceb567e54c4 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -2,6 +2,7 @@ require 'parallel' require_relative 'gitaly_setup' +require_relative '../../../lib/gitlab/setup_helper' module TestEnv extend self @@ -233,7 +234,7 @@ module TestEnv end def workhorse_dir - @workhorse_path ||= File.join('tmp', 'tests', 'gitlab-workhorse') + @workhorse_path ||= Rails.root.join('tmp', 'tests', 'gitlab-workhorse') end def with_workhorse(host, port, upstream, &blk) @@ -371,6 +372,7 @@ module TestEnv def seed_db Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions + FactoryBot.create(:organization, :default) end private diff --git a/spec/support/test_reports/test_reports_helper.rb b/spec/support/helpers/test_reports_helper.rb index 85483062958..4c5a1cf3c74 100644 --- a/spec/support/test_reports/test_reports_helper.rb +++ b/spec/support/helpers/test_reports_helper.rb @@ -43,7 +43,7 @@ module TestReportsHelper end def sample_rspec_failed_message - <<-EOF.strip_heredoc + <<-TEST_REPORT_MESSAGE.strip_heredoc Failure/Error: is_expected.to eq(3) expected: 3 @@ -51,7 +51,7 @@ module TestReportsHelper (compared using ==) ./spec/test_spec.rb:12:in `block (4 levels) in <top (required)>' - EOF + TEST_REPORT_MESSAGE end def create_test_case_java_success(name = 'addTest') @@ -92,12 +92,12 @@ module TestReportsHelper end def sample_java_failed_message - <<-EOF.strip_heredoc + <<-TEST_REPORT_MESSAGE.strip_heredoc junit.framework.AssertionFailedError: expected:<1> but was:<3> at CalculatorTest.subtractExpression(Unknown Source) at java.base/jdk.internal.database.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.database.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.database.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) - EOF + TEST_REPORT_MESSAGE end end diff --git a/spec/support/trace/trace_helpers.rb b/spec/support/helpers/trace_helpers.rb index 9255715ff71..9255715ff71 100644 --- a/spec/support/trace/trace_helpers.rb +++ b/spec/support/helpers/trace_helpers.rb diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index 2bec945fbc8..a1c25338312 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -7,7 +7,6 @@ module UsageDataHelpers ci_external_pipelines ci_pipeline_config_auto_devops ci_pipeline_config_repository - ci_runners ci_triggers ci_pipeline_schedules auto_devops_enabled @@ -54,8 +53,6 @@ module UsageDataHelpers 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 @@ -86,15 +83,9 @@ module UsageDataHelpers ).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 @@ -122,14 +113,14 @@ module UsageDataHelpers end def stub_prometheus_queries - stub_request(:get, %r{^https?://::1:9090/-/ready}) + stub_request(:get, %r{^https?://.*:9090/-/ready}) .to_return( status: 200, body: [{}].to_json, headers: { 'Content-Type' => 'application/json' } ) - stub_request(:get, %r{^https?://::1:9090/api/v1/query\?query=.*}) + stub_request(:get, %r{^https?://.*:9090/api/v1/query\?query=.*}) .to_return( status: 200, body: [{}].to_json, diff --git a/spec/support/helpers/user_login_helper.rb b/spec/support/helpers/user_login_helper.rb index 47e858cb68c..d8368a94ad7 100644 --- a/spec/support/helpers/user_login_helper.rb +++ b/spec/support/helpers/user_login_helper.rb @@ -30,4 +30,20 @@ module UserLoginHelper def ensure_one_active_pane expect(page).to have_selector('.tab-pane.active', count: 1) end + + def ensure_remember_me_in_tab(tab_name) + find_link(tab_name).click + + within '.tab-pane.active' do + expect(page).to have_content _('Remember me') + end + end + + def ensure_remember_me_not_in_tab(tab_name) + find_link(tab_name).click + + within '.tab-pane.active' do + expect(page).not_to have_content _('Remember me') + end + end end diff --git a/spec/support/helpers/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb index 8fd9bb47053..5e2e8ad53e0 100644 --- a/spec/support/helpers/wait_for_requests.rb +++ b/spec/support/helpers/wait_for_requests.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true +require_relative 'wait_helpers' + module WaitForRequests + include WaitHelpers extend self # This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb index f894aff373c..f3b1d3af501 100644 --- a/spec/support/helpers/workhorse_helpers.rb +++ b/spec/support/helpers/workhorse_helpers.rb @@ -29,31 +29,48 @@ module WorkhorseHelpers # workhorse_form_with_file will transform file_key inside params as if it was disk accelerated by workhorse def workhorse_form_with_file(url, file_key:, params:, method: :post) - workhorse_request_with_file(method, url, - file_key: file_key, - params: params, - env: { 'CONTENT_TYPE' => 'multipart/form-data' }, - send_rewritten_field: true + workhorse_request_with_file( + method, url, + file_key: file_key, + params: params, + env: { 'CONTENT_TYPE' => 'multipart/form-data' }, + send_rewritten_field: true ) end # 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, 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) + 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, file_keys:, params:, method: :post, headers: {}, send_rewritten_field: false) - workhorse_request_with_multiple_files(method, url, - file_keys: file_keys, - params: params, - extra_headers: headers, - send_rewritten_field: send_rewritten_field + workhorse_request_with_multiple_files( + method, url, + file_keys: file_keys, + params: params, + extra_headers: headers, + send_rewritten_field: send_rewritten_field ) end 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) + 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:, send_rewritten_field:, env: {}, extra_headers: {}) @@ -118,14 +135,15 @@ module WorkhorseHelpers end end - def fog_to_uploaded_file(file, sha256: nil) - filename = File.basename(file.key) + def fog_to_uploaded_file(file, filename: nil, sha256: nil, remote_id: nil) + filename ||= File.basename(file.key) - UploadedFile.new(nil, - filename: filename, - remote_id: filename, - size: file.content_length, - sha256: sha256 - ) + UploadedFile.new( + nil, + filename: filename, + remote_id: remote_id || filename, + size: file.content_length, + sha256: sha256 + ) end end diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb index f8f32fa59d1..53e943dc3bc 100644 --- a/spec/support/import_export/common_util.rb +++ b/spec/support/import_export/common_util.rb @@ -18,14 +18,8 @@ module ImportExport allow(Gitlab::ImportExport).to receive(:export_path) { export_path } end - def setup_reader(reader) - if reader == :ndjson_reader && Feature.enabled?(:project_import_ndjson) - allow_any_instance_of(Gitlab::ImportExport::Json::LegacyReader::File).to receive(:exist?).and_return(false) - allow_any_instance_of(Gitlab::ImportExport::Json::NdjsonReader).to receive(:exist?).and_return(true) - else - allow_any_instance_of(Gitlab::ImportExport::Json::LegacyReader::File).to receive(:exist?).and_return(true) - allow_any_instance_of(Gitlab::ImportExport::Json::NdjsonReader).to receive(:exist?).and_return(false) - end + def setup_reader + allow_any_instance_of(Gitlab::ImportExport::Json::NdjsonReader).to receive(:exist?).and_return(true) end def fixtures_path @@ -36,19 +30,12 @@ module ImportExport "tmp/tests/gitlab-test/import_export" end - def get_json(path, exportable_path, key, ndjson_enabled) - if ndjson_enabled - json = if key == :projects - consume_attributes(path, exportable_path) - else - consume_relations(path, exportable_path, key) - end + def get_json(path, exportable_path, key) + if key == :projects + consume_attributes(path, exportable_path) else - json = project_json(path) - json = json[key.to_s] unless key == :projects + consume_relations(path, exportable_path, key) end - - json end def restore_then_save_project(project, user, import_path:, export_path:) diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb index 9a26f50903f..ee1b4a3c33a 100644 --- a/spec/support/import_export/export_file_helper.rb +++ b/spec/support/import_export/export_file_helper.rb @@ -21,21 +21,25 @@ module ExportFileHelper create(:label_link, label: label, target: issue) - ci_pipeline = create(:ci_pipeline, - project: project, - sha: merge_request.diff_head_sha, - ref: merge_request.source_branch, - statuses: [commit_status]) + ci_pipeline = create( + :ci_pipeline, + project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch, + statuses: [commit_status] + ) create(:ci_build, pipeline: ci_pipeline, project: project) create(:milestone, project: project) create(:note, noteable: issue, project: project) create(:note, noteable: merge_request, project: project) create(:note, noteable: snippet, project: project) - create(:note_on_commit, - author: user, - project: project, - commit_id: ci_pipeline.sha) + create( + :note_on_commit, + author: user, + project: project, + commit_id: ci_pipeline.sha + ) event = create(:event, :created, target: milestone, project: project, author: user, action: 5) create(:push_event_payload, event: event) diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb index 9f39f576b95..97993b158c8 100644 --- a/spec/support/matchers/background_migrations_matchers.rb +++ b/spec/support/matchers/background_migrations_matchers.rb @@ -100,3 +100,17 @@ RSpec::Matchers.define :be_finalize_background_migration_of do |migration| end end end + +RSpec::Matchers.define :ensure_batched_background_migration_is_finished_for do |migration_arguments| + define_method :matches? do |klass| + expect_next_instance_of(klass) do |instance| + expect(instance).to receive(:ensure_batched_background_migration_is_finished).with(migration_arguments) + end + end + + define_method :does_not_match? do |klass| + expect_next_instance_of(klass) do |instance| + expect(instance).not_to receive(:ensure_batched_background_migration_is_finished).with(migration_arguments) + end + end +end diff --git a/spec/support/matchers/be_a_foreign_key_column_of.rb b/spec/support/matchers/be_a_foreign_key_column_of.rb new file mode 100644 index 00000000000..af190991216 --- /dev/null +++ b/spec/support/matchers/be_a_foreign_key_column_of.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Assert all the given id columns are one of the foreign key columns: +# +# ``` +# id_columns = ['partition_id'] +# composite_keys = [['partition_id', 'build_id', 'name']] +# expect(id_columns).to be_a_foreign_key_column_of(composite_keys) +# ``` +# +RSpec::Matchers.define :be_a_foreign_key_column_of do |composite_keys| + match do |id_columns| + id_columns.all? do |id_column| + composite_keys.any? do |composite_key| + composite_key.include?(id_column) + end + end + end +end diff --git a/spec/support/matchers/be_indexed_by.rb b/spec/support/matchers/be_indexed_by.rb new file mode 100644 index 00000000000..ae955624ae9 --- /dev/null +++ b/spec/support/matchers/be_indexed_by.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Assert all the given foreign keys are indexed: +# +# ``` +# composite_foreign_keys = [['build_id', 'partition_id']] +# indexed_columns = [['build_id', 'name', 'partition_id'], ['partition_id', 'build_id', 'name']] +# expect(composite_foreign_keys).to be_indexed_by(indexed_columns) +# ``` +# +RSpec::Matchers.define :be_indexed_by do |indexed_columns| + match do |composite_foreign_keys| + composite_foreign_keys.all? do |composite_foreign_key| + indexed_columns.any? do |columns| + # for example, [build_id, partition_id] should be covered by indexes e.g. + # - [build_id, partition_id, name] + # - [partition_id, build_id, name] + # but not by [build_id, name, partition_id] + # therefore, we just need to take the first few columns (same length as composite key) + # e.g. [partition_id, build_id] of [partition_id, build_id, name] + # and compare with [build_id, partition_id] + (composite_foreign_key - columns.first(composite_foreign_key.length)).blank? + end + end + end +end diff --git a/spec/support/matchers/exceed_redis_call_limit.rb b/spec/support/matchers/exceed_redis_call_limit.rb new file mode 100644 index 00000000000..2b1e1ebad23 --- /dev/null +++ b/spec/support/matchers/exceed_redis_call_limit.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ExceedRedisCallLimitHelpers + def build_recorder(block) + return block if block.is_a?(RedisCommands::Recorder) + + RedisCommands::Recorder.new(&block) + end + + def verify_count(expected, block) + @actual = build_recorder(block).count + + @actual > expected + end + + def verify_commands_count(command, expected, block) + @actual = build_recorder(block).by_command(command).count + + @actual > expected + end +end + +RSpec::Matchers.define :exceed_redis_calls_limit do |expected| + supports_block_expectations + + include ExceedRedisCallLimitHelpers + + match do |block| + verify_count(expected, block) + end + + failure_message do + "Expected at least #{expected} calls, but got #{actual}" + end + + failure_message_when_negated do + "Expected a maximum of #{expected} calls, but got #{actual}" + end +end + +RSpec::Matchers.define :exceed_redis_command_calls_limit do |command, expected| + supports_block_expectations + + include ExceedRedisCallLimitHelpers + + match do |block| + verify_commands_count(command, expected, block) + end + + failure_message do + "Expected at least #{expected} calls to '#{command}', but got #{actual}" + end + + failure_message_when_negated do + "Expected a maximum of #{expected} calls to '#{command}', but got #{actual}" + end +end diff --git a/spec/support/matchers/have_plain_text_content.rb b/spec/support/matchers/have_plain_text_content.rb new file mode 100644 index 00000000000..94f65ce3771 --- /dev/null +++ b/spec/support/matchers/have_plain_text_content.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# can be replaced with https://github.com/email-spec/email-spec/pull/196 in the future +RSpec::Matchers.define :have_plain_text_content do |expected_text| + match do |actual_email| + plain_text_body(actual_email).include? expected_text + end + + failure_message do |actual_email| + "Expected email\n#{plain_text_body(actual_email).indent(2)}\nto contain\n#{expected_text.indent(2)}" + end + + def plain_text_body(email) + email.text_part.body.to_s + end +end diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index a80c269f915..8fdece7b26d 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -115,7 +115,16 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-issue', count: 6) + expect(actual).to have_selector('a.gfm.gfm-issue', count: 9) + end + end + + # WorkItemReferenceFilter + matcher :reference_work_items do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('a.gfm.gfm-work_item', count: 2) end end @@ -202,7 +211,7 @@ module MarkdownMatchers match do |actual| expect(actual).to have_selector('[data-math-style="inline"]', count: 4) - expect(actual).to have_selector('[data-math-style="display"]', count: 4) + expect(actual).to have_selector('[data-math-style="display"]', count: 6) end end diff --git a/spec/support/matchers/request_urgency_matcher.rb b/spec/support/matchers/request_urgency_matcher.rb new file mode 100644 index 00000000000..d3c5093719e --- /dev/null +++ b/spec/support/matchers/request_urgency_matcher.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module RequestUrgencyMatcher + RSpec::Matchers.define :have_request_urgency do |request_urgency| + match do |_actual| + if controller_instance = request.env["action_controller.instance"] + controller_instance.urgency.name == request_urgency + elsif endpoint = request.env['api.endpoint'] + urgency = endpoint.options[:for].try(:urgency_for_app, endpoint) + urgency.name == request_urgency + else + raise 'neither a controller nor a request spec' + end + end + + failure_message do |_actual| + if controller_instance = request.env["action_controller.instance"] + "request urgency #{controller_instance.urgency.name} is set, \ + but expected to be #{request_urgency}".squish + elsif endpoint = request.env['api.endpoint'] + urgency = endpoint.options[:for].try(:urgency_for_app, endpoint) + "request urgency #{urgency.name} is set, \ + but expected to be #{request_urgency}".squish + end + end + end +end diff --git a/spec/support/matchers/snapshot_matcher.rb b/spec/support/matchers/snapshot_matcher.rb new file mode 100644 index 00000000000..ec1e9cb0815 --- /dev/null +++ b/spec/support/matchers/snapshot_matcher.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +RSpec::Matchers.define :have_snapshot do |date, expected_states| + match do |actual_snapshots| + snapshot = actual_snapshots.find { |snapshot| snapshot[:date] == date } + + @snapshot_not_found = snapshot.nil? + @item_states_not_found = [] + @not_eq_error = nil + + break false if @snapshot_not_found + + expected_states.each do |expected_state| + actual_state = snapshot[:item_states].find { |state| state[:item_id] == expected_state[:item_id] } + + if actual_state.nil? + @item_states_not_found << expected_state[:issue_id] + else + default_state = { + weight: 0, + start_state: ResourceStateEvent.states[:opened], + end_state: ResourceStateEvent.states[:opened], + parent_id: nil, + children_ids: Set.new + } + begin + expect(actual_state).to eq(default_state.merge(expected_state)) + rescue RSpec::Expectations::ExpectationNotMetError => e + @error_item_title = WorkItem.find(expected_state[:item_id]).title + @not_eq_error = e + + raise + end + end + end + end + + failure_message do |_| + break "No snapshot found for the given date #{date}" if @snapshot_not_found + + messages = [] + + messages << <<~MESSAGE + Expected the snapshot on #{date} to match the expected snapshot. + + Errors: + MESSAGE + + messages << "Item states not found for: #{@item_states_not_found.join(', ')}" unless @item_states_not_found.empty? + + messages << "`#{@error_item_title}` does not have the expected states.\n#{@not_eq_error}" if @not_eq_error + + messages.join("\n") + end +end diff --git a/spec/support/migrations_helpers/namespaces_helper.rb b/spec/support/migrations_helpers/namespaces_helper.rb deleted file mode 100644 index c62ef6a4620..00000000000 --- a/spec/support/migrations_helpers/namespaces_helper.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module MigrationHelpers - module NamespacesHelpers - def create_namespace(name, visibility, options = {}) - table(:namespaces).create!({ - name: name, - path: name, - type: 'Group', - visibility_level: visibility - }.merge(options)) - end - end -end diff --git a/spec/support/permissions_check.rb b/spec/support/permissions_check.rb new file mode 100644 index 00000000000..efe0ecb530b --- /dev/null +++ b/spec/support/permissions_check.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Support + module PermissionsCheck + def self.inject(mod) + mod.prepend PermissionsExtension if Gitlab::Utils.to_boolean(ENV['GITLAB_DEBUG_POLICIES']) + end + + module PermissionsExtension + def before_check(policy, ability, _user, _subject, _opts) + puts( + "POLICY CHECK DEBUG -> " \ + "policy: #{policy.class.name}, ability: #{ability}, called_from: #{caller_locations(2, 5)}" + ) + end + end + end +end diff --git a/spec/support/protected_branch_helpers.rb b/spec/support/protected_branch_helpers.rb index b34b9ec4641..d983d03fd2e 100644 --- a/spec/support/protected_branch_helpers.rb +++ b/spec/support/protected_branch_helpers.rb @@ -2,18 +2,10 @@ module ProtectedBranchHelpers def set_allowed_to(operation, option = 'Maintainers', form: '.js-new-protected-branch') - within form do - select_elem = find(".js-allowed-to-#{operation}") - select_elem.click - - wait_for_requests - - within('.dropdown-content') do + within(form) do + within_select(".js-allowed-to-#{operation}") do Array(option).each { |opt| click_on(opt) } end - - # Enhanced select is used in EE, therefore an extra click is needed. - select_elem.click if select_elem['aria-expanded'] == 'true' end end @@ -32,4 +24,15 @@ module ProtectedBranchHelpers click_on "Protect" wait_for_requests end + + def within_select(selector, &block) + select_input = find(selector) + select_input.click + wait_for_requests + + within('.dropdown.show .dropdown-menu', &block) + + # Enhanced select is used in EE, therefore an extra click is needed. + select_input.click if select_input['aria-expanded'] == 'true' + end end diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb deleted file mode 100644 index 8666c19481c..00000000000 --- a/spec/support/protected_tags/access_control_ce_shared_examples.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples "protected tags > access control > CE" do - ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)| - it "allows creating protected tags that #{access_type_name} can create" do - visit project_protected_tags_path(project) - - set_protected_tag_name('master') - - within('.js-new-protected-tag') do - allowed_to_create_button = find(".js-allowed-to-create") - - unless allowed_to_create_button.text == access_type_name - allowed_to_create_button.click - find('.create_access_levels-container .dropdown-menu li', match: :first) - within('.create_access_levels-container .dropdown-menu') { click_on access_type_name } - end - end - - click_on "Protect" - - expect(ProtectedTag.count).to eq(1) - expect(ProtectedTag.last.create_access_levels.map(&:access_level)).to eq([access_type_id]) - end - - it "allows updating protected tags so that #{access_type_name} can create them" do - visit project_protected_tags_path(project) - - set_protected_tag_name('master') - - click_on "Protect" - - expect(ProtectedTag.count).to eq(1) - - within(".protected-tags-list") do - find(".js-allowed-to-create").click - - within('.js-allowed-to-create-container') do - expect(first("li")).to have_content("Roles") - click_on access_type_name - end - end - - wait_for_requests - - expect(ProtectedTag.last.create_access_levels.map(&:access_level)).to include(access_type_id) - end - end -end diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb index ff0b5bebe33..94c43669173 100644 --- a/spec/support/rspec.rb +++ b/spec/support/rspec.rb @@ -19,6 +19,13 @@ RSpec.configure do |config| # Re-run failures locally with `--only-failures` config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', './spec/examples.txt') + # Makes diffs show entire non-truncated values. + config.before(:each, :unlimited_max_formatted_output_length) do + config.expect_with :rspec do |c| + c.max_formatted_output_length = nil + end + end + unless ENV['CI'] # Allow running `:focus` examples locally, # falling back to all tests when there is no `:focus` example. @@ -43,7 +50,10 @@ RSpec.configure do |config| # 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" + location = + example.metadata[:shared_group_inclusion_backtrace].last&.formatted_inclusion_location || + example.location + warn "Missing metadata feature_category: #{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.rb b/spec/support/rspec_order.rb index c128e18b38e..0305ae7241d 100644 --- a/spec/support/rspec_order.rb +++ b/spec/support/rspec_order.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'yaml' + module Support module RspecOrder TODO_YAML = File.join(__dir__, 'rspec_order_todo.yml') diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 7aa7d8e8abd..82dc6659dbf 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -34,7 +34,6 @@ - './ee/spec/controllers/concerns/geo_instrumentation_spec.rb' - './ee/spec/controllers/concerns/gitlab_subscriptions/seat_count_alert_spec.rb' - './ee/spec/controllers/concerns/internal_redirect_spec.rb' -- './ee/spec/controllers/concerns/registrations/verification_spec.rb' - './ee/spec/controllers/concerns/routable_actions_spec.rb' - './ee/spec/controllers/countries_controller_spec.rb' - './ee/spec/controllers/country_states_controller_spec.rb' @@ -62,7 +61,6 @@ - './ee/spec/controllers/groups/analytics/cycle_analytics_controller_spec.rb' - './ee/spec/controllers/groups/analytics/cycle_analytics/stages_controller_spec.rb' - './ee/spec/controllers/groups/analytics/cycle_analytics/summary_controller_spec.rb' -- './ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb' - './ee/spec/controllers/groups/analytics/productivity_analytics_controller_spec.rb' - './ee/spec/controllers/groups/analytics/repository_analytics_controller_spec.rb' - './ee/spec/controllers/groups/analytics/tasks_by_type_controller_spec.rb' @@ -123,7 +121,6 @@ - './ee/spec/controllers/projects/branches_controller_spec.rb' - './ee/spec/controllers/projects/clusters_controller_spec.rb' - './ee/spec/controllers/projects_controller_spec.rb' -- './ee/spec/controllers/projects/dependencies_controller_spec.rb' - './ee/spec/controllers/projects/deploy_keys_controller_spec.rb' - './ee/spec/controllers/projects/environments_controller_spec.rb' - './ee/spec/controllers/projects/feature_flag_issues_controller_spec.rb' @@ -226,7 +223,6 @@ - './ee/spec/features/analytics/code_analytics_spec.rb' - './ee/spec/features/analytics/group_analytics_spec.rb' - './ee/spec/features/billings/billing_plans_spec.rb' -- './ee/spec/features/billings/extend_reactivate_trial_spec.rb' - './ee/spec/features/billings/qrtly_reconciliation_alert_spec.rb' - './ee/spec/features/boards/board_filters_spec.rb' - './ee/spec/features/boards/boards_licensed_features_spec.rb' @@ -286,7 +282,6 @@ - './ee/spec/features/groups/audit_events_spec.rb' - './ee/spec/features/groups/billing_spec.rb' - './ee/spec/features/groups/contribution_analytics_spec.rb' -- './ee/spec/features/groups/feature_discovery_moments_spec.rb' - './ee/spec/features/groups/group_overview_spec.rb' - './ee/spec/features/groups/group_page_with_external_authorization_service_spec.rb' - './ee/spec/features/groups/group_projects_spec.rb' @@ -330,7 +325,6 @@ - './ee/spec/features/groups/wiki/user_views_wiki_empty_spec.rb' - './ee/spec/features/ide/user_opens_ide_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' - './ee/spec/features/issues/epic_in_issue_sidebar_spec.rb' - './ee/spec/features/issues/filtered_search/filter_issues_by_iteration_spec.rb' @@ -497,9 +491,7 @@ - './ee/spec/features/trial_registrations/company_information_spec.rb' - './ee/spec/features/trial_registrations/signin_spec.rb' - './ee/spec/features/trial_registrations/signup_spec.rb' -- './ee/spec/features/trials/select_namespace_spec.rb' - './ee/spec/features/trials/show_trial_banner_spec.rb' -- './ee/spec/features/users/arkose_labs_csp_spec.rb' - './ee/spec/features/users/login_spec.rb' - './ee/spec/features/users/signup_spec.rb' - './ee/spec/features/user_unsubscribes_from_admin_notifications_spec.rb' @@ -529,7 +521,6 @@ - './ee/spec/finders/ee/alert_management/http_integrations_finder_spec.rb' - './ee/spec/finders/ee/autocomplete/users_finder_spec.rb' - './ee/spec/finders/ee/ci/daily_build_group_report_results_finder_spec.rb' -- './ee/spec/finders/ee/clusters/agent_authorizations_finder_spec.rb' - './ee/spec/finders/ee/clusters/agents_finder_spec.rb' - './ee/spec/finders/ee/fork_targets_finder_spec.rb' - './ee/spec/finders/ee/group_members_finder_spec.rb' @@ -564,7 +555,6 @@ - './ee/spec/finders/license_template_finder_spec.rb' - './ee/spec/finders/merge_requests/by_approvers_finder_spec.rb' - './ee/spec/finders/merge_requests_finder_spec.rb' -- './ee/spec/finders/merge_trains_finder_spec.rb' - './ee/spec/finders/notes_finder_spec.rb' - './ee/spec/finders/productivity_analytics_finder_spec.rb' - './ee/spec/finders/projects/integrations/jira/by_ids_finder_spec.rb' @@ -602,7 +592,6 @@ - './ee/spec/frontend/fixtures/merge_requests.rb' - './ee/spec/frontend/fixtures/on_demand_dast_scans.rb' - './ee/spec/frontend/fixtures/project_quality_summary.rb' -- './ee/spec/frontend/fixtures/projects.rb' - './ee/spec/frontend/fixtures/runner.rb' - './ee/spec/frontend/fixtures/saml_providers.rb' - './ee/spec/frontend/fixtures/search.rb' @@ -957,7 +946,6 @@ - './ee/spec/helpers/ee/geo_helper_spec.rb' - './ee/spec/helpers/ee/gitlab_routing_helper_spec.rb' - './ee/spec/helpers/ee/graph_helper_spec.rb' -- './ee/spec/helpers/ee/groups/analytics/cycle_analytics_helper_spec.rb' - './ee/spec/helpers/ee/groups/group_members_helper_spec.rb' - './ee/spec/helpers/ee/groups_helper_spec.rb' - './ee/spec/helpers/ee/groups/settings_helper_spec.rb' @@ -984,14 +972,11 @@ - './ee/spec/helpers/ee/subscribable_banner_helper_spec.rb' - './ee/spec/helpers/ee/system_note_helper_spec.rb' - './ee/spec/helpers/ee/todos_helper_spec.rb' -- './ee/spec/helpers/ee/trial_helper_spec.rb' -- './ee/spec/helpers/ee/trial_registration_helper_spec.rb' - './ee/spec/helpers/ee/users/callouts_helper_spec.rb' - './ee/spec/helpers/ee/version_check_helper_spec.rb' - './ee/spec/helpers/ee/wiki_helper_spec.rb' - './ee/spec/helpers/epics_helper_spec.rb' - './ee/spec/helpers/gitlab_subscriptions/upcoming_reconciliation_helper_spec.rb' -- './ee/spec/helpers/groups/feature_discovery_moments_helper_spec.rb' - './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' @@ -1122,19 +1107,13 @@ - './ee/spec/lib/ee/gitlab/background_migration/backfill_project_statistics_container_repository_size_spec.rb' - './ee/spec/lib/ee/gitlab/background_migration/create_security_setting_spec.rb' - './ee/spec/lib/ee/gitlab/background_migration/delete_invalid_epic_issues_spec.rb' -- './ee/spec/lib/ee/gitlab/background_migration/drop_invalid_remediations_spec.rb' -- './ee/spec/lib/ee/gitlab/background_migration/fix_incorrect_max_seats_used_spec.rb' - './ee/spec/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules_check_progress_spec.rb' - './ee/spec/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules_in_batch_spec.rb' - './ee/spec/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules_spec.rb' - './ee/spec/lib/ee/gitlab/background_migration/migrate_shared_vulnerability_scanners_spec.rb' - './ee/spec/lib/ee/gitlab/background_migration/populate_latest_pipeline_ids_spec.rb' -- './ee/spec/lib/ee/gitlab/background_migration/populate_namespace_statistics_spec.rb' - './ee/spec/lib/ee/gitlab/background_migration/populate_resolved_on_default_branch_column_spec.rb' -- './ee/spec/lib/ee/gitlab/background_migration/populate_uuids_for_security_findings_spec.rb' - './ee/spec/lib/ee/gitlab/background_migration/purge_stale_security_scans_spec.rb' -- './ee/spec/lib/ee/gitlab/background_migration/recalculate_vulnerability_finding_signatures_for_findings_spec.rb' -- './ee/spec/lib/ee/gitlab/background_migration/update_vulnerability_occurrences_location_spec.rb' - './ee/spec/lib/ee/gitlab/checks/push_rule_check_spec.rb' - './ee/spec/lib/ee/gitlab/checks/push_rules/branch_check_spec.rb' - './ee/spec/lib/ee/gitlab/checks/push_rules/commit_check_spec.rb' @@ -1309,8 +1288,6 @@ - './ee/spec/lib/gitlab/auth/smartcard/session_enforcer_spec.rb' - './ee/spec/lib/gitlab/auth/smartcard/session_spec.rb' - './ee/spec/lib/gitlab/background_migration/migrate_requirements_to_work_items_spec.rb' -- './ee/spec/lib/gitlab/background_migration/populate_test_reports_issue_id_spec.rb' -- './ee/spec/lib/gitlab/background_migration/remove_all_trace_expiration_dates_spec.rb' - './ee/spec/lib/gitlab/bullet/exclusions_spec.rb' - './ee/spec/lib/gitlab/cache_spec.rb' - './ee/spec/lib/gitlab/checks/changes_access_spec.rb' @@ -1323,7 +1300,6 @@ - './ee/spec/lib/gitlab/ci/config/required/processor_spec.rb' - './ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb' - './ee/spec/lib/gitlab/cidr_spec.rb' -- './ee/spec/lib/gitlab/ci/minutes/build_consumption_spec.rb' - './ee/spec/lib/gitlab/ci/minutes/cached_quota_spec.rb' - './ee/spec/lib/gitlab/ci/minutes/cost_factor_spec.rb' - './ee/spec/lib/gitlab/ci/minutes/gitlab_contribution_cost_factor_spec.rb' @@ -1371,7 +1347,6 @@ - './ee/spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb' - './ee/spec/lib/gitlab/ci/templates/Jobs/dast_default_branch_gitlab_ci_yaml_spec.rb' - './ee/spec/lib/gitlab/ci/templates/Jobs/load_performance_testing_gitlab_ci_yaml_spec.rb' -- './ee/spec/lib/gitlab/ci/templates/license_scanning_gitlab_ci_yaml_spec.rb' - './ee/spec/lib/gitlab/ci/templates/sast_gitlab_ci_yaml_spec.rb' - './ee/spec/lib/gitlab/ci/templates/sast_iac_gitlab_ci_yaml_spec.rb' - './ee/spec/lib/gitlab/ci/templates/sast_latest_gitlab_ci_yaml_spec.rb' @@ -1577,13 +1552,10 @@ - './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' -- './ee/spec/lib/sidebars/groups/menus/administration_menu_spec.rb' - './ee/spec/lib/sidebars/groups/menus/analytics_menu_spec.rb' - './ee/spec/lib/sidebars/groups/menus/epics_menu_spec.rb' - './ee/spec/lib/sidebars/groups/menus/security_compliance_menu_spec.rb' -- './ee/spec/lib/sidebars/groups/menus/trial_experiment_menu_spec.rb' - './ee/spec/lib/sidebars/groups/menus/wiki_menu_spec.rb' -- './ee/spec/lib/sidebars/projects/menus/trial_experiment_menu_spec.rb' - './ee/spec/lib/system_check/app/search_check_spec.rb' - './ee/spec/lib/system_check/geo/authorized_keys_check_spec.rb' - './ee/spec/lib/system_check/geo/authorized_keys_flag_check_spec.rb' @@ -1613,21 +1585,12 @@ - './ee/spec/mailers/notify_spec.rb' - './ee/spec/migrations/20220411173544_cleanup_orphans_approval_project_rules_spec.rb' - './ee/spec/migrations/20220517144749_remove_vulnerability_approval_rules_spec.rb' -- './ee/spec/migrations/add_non_null_constraint_for_escalation_rule_on_pending_alert_escalations_spec.rb' -- './ee/spec/migrations/async_build_trace_expire_at_index_spec.rb' - './ee/spec/migrations/backfill_delayed_group_deletion_spec.rb' -- './ee/spec/migrations/drop_invalid_remediations_spec.rb' - './ee/spec/migrations/geo/fix_state_column_in_file_registry_spec.rb' - './ee/spec/migrations/geo/fix_state_column_in_lfs_object_registry_spec.rb' - './ee/spec/migrations/geo/migrate_ci_job_artifacts_to_separate_registry_spec.rb' - './ee/spec/migrations/geo/migrate_lfs_objects_to_separate_registry_spec.rb' - './ee/spec/migrations/geo/set_resync_flag_for_retried_projects_spec.rb' -- './ee/spec/migrations/remove_schedule_and_status_null_constraints_from_pending_escalations_alert_spec.rb' -- './ee/spec/migrations/schedule_delete_invalid_epic_issues_revised_spec.rb' -- './ee/spec/migrations/schedule_populate_test_reports_issue_id_spec.rb' -- './ee/spec/migrations/schedule_requirements_migration_spec.rb' -- './ee/spec/migrations/schedule_trace_expiry_removal_spec.rb' -- './ee/spec/migrations/update_vulnerability_occurrences_location_spec.rb' - './ee/spec/models/alert_management/alert_payload_field_spec.rb' - './ee/spec/models/allowed_email_domain_spec.rb' - './ee/spec/models/analytics/cycle_analytics/aggregation_context_spec.rb' @@ -1662,7 +1625,6 @@ - './ee/spec/models/boards/epic_user_preference_spec.rb' - './ee/spec/models/board_spec.rb' - './ee/spec/models/board_user_preference_spec.rb' -- './ee/spec/models/broadcast_message_spec.rb' - './ee/spec/models/burndown_spec.rb' - './ee/spec/models/ci/bridge_spec.rb' - './ee/spec/models/ci/daily_build_group_report_result_spec.rb' @@ -1683,7 +1645,6 @@ - './ee/spec/models/concerns/approver_migrate_hook_spec.rb' - './ee/spec/models/concerns/auditable_spec.rb' - './ee/spec/models/concerns/deprecated_approvals_before_merge_spec.rb' -- './ee/spec/models/concerns/ee/clusters/agents/authorization_config_scopes_spec.rb' - './ee/spec/models/concerns/ee/issuable_spec.rb' - './ee/spec/models/concerns/ee/mentionable_spec.rb' - './ee/spec/models/concerns/ee/milestoneable_spec.rb' @@ -1733,7 +1694,6 @@ - './ee/spec/models/dora/lead_time_for_changes_metric_spec.rb' - './ee/spec/models/dora/time_to_restore_service_metric_spec.rb' - './ee/spec/models/ee/alert_management/alert_spec.rb' -- './ee/spec/models/ee/analytics/cycle_analytics/stage_event_hash_spec.rb' - './ee/spec/models/ee/analytics/usage_trends/measurement_spec.rb' - './ee/spec/models/ee/appearance_spec.rb' - './ee/spec/models/ee/audit_event_spec.rb' @@ -1754,7 +1714,6 @@ - './ee/spec/models/ee/integrations/jira_spec.rb' - './ee/spec/models/ee/integration_spec.rb' - './ee/spec/models/ee/iterations/cadence_spec.rb' -- './ee/spec/models/ee/iteration_spec.rb' - './ee/spec/models/ee/key_spec.rb' - './ee/spec/models/ee/label_spec.rb' - './ee/spec/models/ee/lfs_object_spec.rb' @@ -1791,7 +1750,6 @@ - './ee/spec/models/ee/user_spec.rb' - './ee/spec/models/ee/users_statistics_spec.rb' - './ee/spec/models/ee/vulnerability_spec.rb' -- './ee/spec/models/ee/work_items/type_spec.rb' - './ee/spec/models/elastic/index_setting_spec.rb' - './ee/spec/models/elastic/migration_record_spec.rb' - './ee/spec/models/elastic/reindexing_slice_spec.rb' @@ -1874,7 +1832,6 @@ - './ee/spec/models/merge_requests/external_status_check_spec.rb' - './ee/spec/models/merge_request_spec.rb' - './ee/spec/models/merge_requests/status_check_response_spec.rb' -- './ee/spec/models/merge_train_spec.rb' - './ee/spec/models/milestone_release_spec.rb' - './ee/spec/models/milestone_spec.rb' - './ee/spec/models/namespace_limit_spec.rb' @@ -1934,7 +1891,6 @@ - './ee/spec/models/storage_shard_spec.rb' - './ee/spec/models/uploads/local_spec.rb' - './ee/spec/models/upload_spec.rb' -- './ee/spec/models/user_detail_spec.rb' - './ee/spec/models/user_permission_export_upload_spec.rb' - './ee/spec/models/user_preference_spec.rb' - './ee/spec/models/users_security_dashboard_project_spec.rb' @@ -2330,7 +2286,6 @@ - './ee/spec/requests/groups_controller_spec.rb' - './ee/spec/requests/groups/epics/epic_links_controller_spec.rb' - './ee/spec/requests/groups/epics/related_epic_links_controller_spec.rb' -- './ee/spec/requests/groups/feature_discovery_moments_spec.rb' - './ee/spec/requests/groups/group_members_controller_spec.rb' - './ee/spec/requests/groups/hook_logs_controller_spec.rb' - './ee/spec/requests/groups/labels_spec.rb' @@ -2471,10 +2426,7 @@ - './ee/spec/services/analytics/cycle_analytics/aggregator_service_spec.rb' - './ee/spec/services/analytics/cycle_analytics/consistency_check_service_spec.rb' - './ee/spec/services/analytics/cycle_analytics/data_loader_service_spec.rb' -- './ee/spec/services/analytics/cycle_analytics/stages/create_service_spec.rb' -- './ee/spec/services/analytics/cycle_analytics/stages/delete_service_spec.rb' - './ee/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb' -- './ee/spec/services/analytics/cycle_analytics/stages/update_service_spec.rb' - './ee/spec/services/analytics/cycle_analytics/value_streams/create_service_spec.rb' - './ee/spec/services/analytics/cycle_analytics/value_streams/update_service_spec.rb' - './ee/spec/services/analytics/devops_adoption/enabled_namespaces/bulk_delete_service_spec.rb' @@ -2534,7 +2486,6 @@ - './ee/spec/services/audit_events/release_associate_milestone_audit_event_service_spec.rb' - './ee/spec/services/audit_events/release_created_audit_event_service_spec.rb' - './ee/spec/services/audit_events/release_updated_audit_event_service_spec.rb' -- './ee/spec/services/audit_events/repository_download_started_audit_event_service_spec.rb' - './ee/spec/services/audit_events/runner_custom_audit_event_service_spec.rb' - './ee/spec/services/audit_events/runners_token_audit_event_service_spec.rb' - './ee/spec/services/audit_events/streaming/headers/base_spec.rb' @@ -2587,7 +2538,6 @@ - './ee/spec/services/ci/external_pull_requests/process_github_event_service_spec.rb' - './ee/spec/services/ci/minutes/additional_packs/change_namespace_service_spec.rb' - './ee/spec/services/ci/minutes/additional_packs/create_service_spec.rb' -- './ee/spec/services/ci/minutes/batch_reset_service_spec.rb' - './ee/spec/services/ci/minutes/email_notification_service_spec.rb' - './ee/spec/services/ci/minutes/refresh_cached_data_service_spec.rb' - './ee/spec/services/ci/minutes/reset_usage_service_spec.rb' @@ -2668,9 +2618,7 @@ - './ee/spec/services/ee/issuable/destroy_service_spec.rb' - './ee/spec/services/ee/issue_links/create_service_spec.rb' - './ee/spec/services/ee/issues/after_create_service_spec.rb' -- './ee/spec/services/ee/issues/build_from_vulnerability_service_spec.rb' - './ee/spec/services/ee/issues/clone_service_spec.rb' -- './ee/spec/services/ee/issues/create_from_vulnerability_data_service_spec.rb' - './ee/spec/services/ee/issues/create_service_spec.rb' - './ee/spec/services/ee/issues/move_service_spec.rb' - './ee/spec/services/ee/issues/update_service_spec.rb' @@ -2761,7 +2709,6 @@ - './ee/spec/services/epics/related_epic_links/list_service_spec.rb' - './ee/spec/services/epics/reopen_service_spec.rb' - './ee/spec/services/epics/transfer_service_spec.rb' -- './ee/spec/services/epics/tree_reorder_service_spec.rb' - './ee/spec/services/epics/update_dates_service_spec.rb' - './ee/spec/services/epics/update_service_spec.rb' - './ee/spec/services/external_status_checks/create_service_spec.rb' @@ -2815,10 +2762,8 @@ - './ee/spec/services/gitlab_subscriptions/create_hand_raise_lead_service_spec.rb' - './ee/spec/services/gitlab_subscriptions/create_service_spec.rb' - './ee/spec/services/gitlab_subscriptions/create_trial_or_lead_service_spec.rb' -- './ee/spec/services/gitlab_subscriptions/extend_reactivate_trial_service_spec.rb' - './ee/spec/services/gitlab_subscriptions/fetch_purchase_eligible_namespaces_service_spec.rb' - './ee/spec/services/gitlab_subscriptions/fetch_subscription_plans_service_spec.rb' -- './ee/spec/services/gitlab_subscriptions/notify_seats_exceeded_service_spec.rb' - './ee/spec/services/gitlab_subscriptions/plan_upgrade_service_spec.rb' - './ee/spec/services/gitlab_subscriptions/preview_billable_user_change_service_spec.rb' - './ee/spec/services/gitlab_subscriptions/reconciliations/calculate_seat_count_data_service_spec.rb' @@ -2869,7 +2814,6 @@ - './ee/spec/services/licenses/destroy_service_spec.rb' - './ee/spec/services/members/activate_service_spec.rb' - './ee/spec/services/members/await_service_spec.rb' -- './ee/spec/services/merge_commits/export_csv_service_spec.rb' - './ee/spec/services/merge_request_approval_settings/update_service_spec.rb' - './ee/spec/services/merge_requests/approval_service_spec.rb' - './ee/spec/services/merge_requests/build_service_spec.rb' @@ -2946,7 +2890,6 @@ - './ee/spec/services/repositories/housekeeping_service_spec.rb' - './ee/spec/services/requirements_management/export_csv_service_spec.rb' - './ee/spec/services/requirements_management/import_csv_service_spec.rb' -- './ee/spec/services/requirements_management/map_export_fields_service_spec.rb' - './ee/spec/services/requirements_management/prepare_import_csv_service_spec.rb' - './ee/spec/services/requirements_management/process_test_reports_service_spec.rb' - './ee/spec/services/resource_access_tokens/create_service_spec.rb' @@ -3017,7 +2960,6 @@ - './ee/spec/services/security/update_training_service_spec.rb' - './ee/spec/services/security/vulnerability_counting_service_spec.rb' - './ee/spec/services/sitemap/create_service_spec.rb' -- './ee/spec/services/slash_commands/global_slack_handler_spec.rb' - './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' @@ -3106,7 +3048,6 @@ - './ee/spec/views/groups/_compliance_frameworks.html.haml_spec.rb' - './ee/spec/views/groups/compliance_frameworks/new.html.haml_spec.rb' - './ee/spec/views/groups/edit.html.haml_spec.rb' -- './ee/spec/views/groups/feature_discovery_moments/advanced_features_dashboard.html.haml_spec.rb' - './ee/spec/views/groups/hook_logs/show.html.haml_spec.rb' - './ee/spec/views/groups/hooks/edit.html.haml_spec.rb' - './ee/spec/views/groups/security/discover/show.html.haml_spec.rb' @@ -3116,13 +3057,11 @@ - './ee/spec/views/layouts/checkout.html.haml_spec.rb' - './ee/spec/views/layouts/header/_current_user_dropdown.html.haml_spec.rb' - './ee/spec/views/layouts/header/_ee_subscribable_banner.html.haml_spec.rb' -- './ee/spec/views/layouts/header/help_dropdown/_cross_stage_fdm.html.haml_spec.rb' - './ee/spec/views/layouts/header/_read_only_banner.html.haml_spec.rb' - './ee/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb' - './ee/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb' - './ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb' - './ee/spec/views/layouts/nav/sidebar/_push_rules_link.html.haml_spec.rb' -- './ee/spec/views/layouts/_search.html.haml_spec.rb' - './ee/spec/views/operations/environments.html.haml_spec.rb' - './ee/spec/views/operations/index.html.haml_spec.rb' - './ee/spec/views/profiles/preferences/show.html.haml_spec.rb' @@ -3141,7 +3080,6 @@ - './ee/spec/views/projects/security/sast_configuration/show.html.haml_spec.rb' - './ee/spec/views/projects/settings/subscriptions/_index.html.haml_spec.rb' - './ee/spec/views/registrations/groups_projects/new.html.haml_spec.rb' -- './ee/spec/views/search/_category.html.haml_spec.rb' - './ee/spec/views/shared/billings/_billing_plan_actions.html.haml_spec.rb' - './ee/spec/views/shared/billings/_billing_plan.html.haml_spec.rb' - './ee/spec/views/shared/billings/_billing_plans.html.haml_spec.rb' @@ -3201,7 +3139,6 @@ - './ee/spec/workers/concerns/elastic/indexing_control_spec.rb' - './ee/spec/workers/concerns/elastic/migration_obsolete_spec.rb' - './ee/spec/workers/concerns/elastic/migration_options_spec.rb' -- './ee/spec/workers/concerns/geo_queue_spec.rb' - './ee/spec/workers/concerns/update_orchestration_policy_configuration_spec.rb' - './ee/spec/workers/create_github_webhook_worker_spec.rb' - './ee/spec/workers/deployments/auto_rollback_worker_spec.rb' @@ -3265,7 +3202,6 @@ - './ee/spec/workers/geo/verification_state_backfill_worker_spec.rb' - './ee/spec/workers/geo/verification_timeout_worker_spec.rb' - './ee/spec/workers/geo/verification_worker_spec.rb' -- './ee/spec/workers/gitlab_subscriptions/notify_seats_exceeded_worker_spec.rb' - './ee/spec/workers/group_saml_group_sync_worker_spec.rb' - './ee/spec/workers/groups/create_event_worker_spec.rb' - './ee/spec/workers/groups/export_memberships_worker_spec.rb' @@ -3314,7 +3250,6 @@ - './ee/spec/workers/sync_seat_link_worker_spec.rb' - './ee/spec/workers/todos_destroyer/confidential_epic_worker_spec.rb' - './ee/spec/workers/update_all_mirrors_worker_spec.rb' -- './ee/spec/workers/update_max_seats_used_for_gitlab_com_subscriptions_worker_spec.rb' - './ee/spec/workers/vulnerabilities/historical_statistics/deletion_worker_spec.rb' - './ee/spec/workers/vulnerabilities/statistics/adjustment_worker_spec.rb' - './ee/spec/workers/vulnerabilities/statistics/schedule_worker_spec.rb' @@ -3323,7 +3258,6 @@ - './spec/bin/feature_flag_spec.rb' - './spec/bin/sidekiq_cluster_spec.rb' - './spec/channels/application_cable/connection_spec.rb' -- './spec/channels/awareness_channel_spec.rb' - './spec/commands/metrics_server/metrics_server_spec.rb' - './spec/commands/sidekiq_cluster/cli_spec.rb' - './spec/components/diffs/overflow_warning_component_spec.rb' @@ -3368,7 +3302,6 @@ - './spec/controllers/admin/jobs_controller_spec.rb' - './spec/controllers/admin/plan_limits_controller_spec.rb' - './spec/controllers/admin/projects_controller_spec.rb' -- './spec/controllers/admin/runner_projects_controller_spec.rb' - './spec/controllers/admin/runners_controller_spec.rb' - './spec/controllers/admin/sessions_controller_spec.rb' - './spec/controllers/admin/spam_logs_controller_spec.rb' @@ -3458,7 +3391,6 @@ - './spec/controllers/import/github_controller_spec.rb' - './spec/controllers/import/gitlab_controller_spec.rb' - './spec/controllers/import/manifest_controller_spec.rb' -- './spec/controllers/import/phabricator_controller_spec.rb' - './spec/controllers/invites_controller_spec.rb' - './spec/controllers/jira_connect/app_descriptor_controller_spec.rb' - './spec/controllers/jira_connect/branches_controller_spec.rb' @@ -3618,13 +3550,10 @@ - './spec/experiments/force_company_trial_experiment_spec.rb' - './spec/experiments/in_product_guidance_environments_webide_experiment_spec.rb' - './spec/experiments/ios_specific_templates_experiment_spec.rb' -- './spec/experiments/require_verification_for_namespace_creation_experiment_spec.rb' -- './spec/experiments/security_reports_mr_widget_prompt_experiment_spec.rb' - './spec/features/abuse_report_spec.rb' - './spec/features/action_cable_logging_spec.rb' - './spec/features/admin/admin_abuse_reports_spec.rb' - './spec/features/admin/admin_appearance_spec.rb' -- './spec/features/admin/admin_broadcast_messages_spec.rb' - './spec/features/admin/admin_browse_spam_logs_spec.rb' - './spec/features/admin/admin_deploy_keys_spec.rb' - './spec/features/admin/admin_dev_ops_reports_spec.rb' @@ -3814,12 +3743,10 @@ - './spec/features/ics/dashboard_issues_spec.rb' - './spec/features/ics/group_issues_spec.rb' - './spec/features/ics/project_issues_spec.rb' -- './spec/features/ide/clientside_preview_csp_spec.rb' - './spec/features/ide_spec.rb' - './spec/features/ide/static_object_external_storage_csp_spec.rb' - './spec/features/ide/user_opens_merge_request_spec.rb' - './spec/features/import/manifest_import_spec.rb' -- './spec/features/invites_spec.rb' - './spec/features/issuables/issuable_list_spec.rb' - './spec/features/issuables/markdown_references/internal_references_spec.rb' - './spec/features/issuables/markdown_references/jira_spec.rb' @@ -3980,7 +3907,6 @@ - './spec/features/merge_request/user_sees_suggest_pipeline_spec.rb' - './spec/features/merge_request/user_sees_system_notes_spec.rb' - './spec/features/merge_request/user_sees_versions_spec.rb' -- './spec/features/merge_request/user_sees_wip_help_message_spec.rb' - './spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb' - './spec/features/merge_request/user_squashes_merge_request_spec.rb' - './spec/features/merge_request/user_suggests_changes_on_diff_spec.rb' @@ -4108,7 +4034,6 @@ - './spec/features/projects/files/project_owner_creates_license_file_spec.rb' - './spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb' - './spec/features/projects/files/template_selector_menu_spec.rb' -- './spec/features/projects/files/template_type_dropdown_spec.rb' - './spec/features/projects/files/undo_template_spec.rb' - './spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb' - './spec/features/projects/files/user_browses_files_spec.rb' @@ -4349,7 +4274,6 @@ - './spec/features/task_lists_spec.rb' - './spec/features/topic_show_spec.rb' - './spec/features/triggers_spec.rb' -- './spec/features/u2f_spec.rb' - './spec/features/unsubscribe_links_spec.rb' - './spec/features/uploads/user_uploads_avatar_to_group_spec.rb' - './spec/features/uploads/user_uploads_avatar_to_profile_spec.rb' @@ -4377,10 +4301,8 @@ - './spec/features/users/snippets_spec.rb' - './spec/features/users/terms_spec.rb' - './spec/features/users/user_browses_projects_on_user_page_spec.rb' -- './spec/features/users/zuora_csp_spec.rb' - './spec/features/webauthn_spec.rb' - './spec/features/whats_new_spec.rb' -- './spec/features/work_items/work_item_children_spec.rb' - './spec/finders/abuse_reports_finder_spec.rb' - './spec/finders/access_requests_finder_spec.rb' - './spec/finders/admin/plans_finder_spec.rb' @@ -4414,7 +4336,6 @@ - './spec/finders/ci/runners_finder_spec.rb' - './spec/finders/ci/variables_finder_spec.rb' - './spec/finders/cluster_ancestors_finder_spec.rb' -- './spec/finders/clusters/agent_authorizations_finder_spec.rb' - './spec/finders/clusters/agents_finder_spec.rb' - './spec/finders/clusters_finder_spec.rb' - './spec/finders/clusters/knative_services_finder_spec.rb' @@ -4515,7 +4436,6 @@ - './spec/finders/security/license_compliance_jobs_finder_spec.rb' - './spec/finders/security/security_jobs_finder_spec.rb' - './spec/finders/sentry_issue_finder_spec.rb' -- './spec/finders/serverless_domain_finder_spec.rb' - './spec/finders/snippets_finder_spec.rb' - './spec/finders/starred_projects_finder_spec.rb' - './spec/finders/tags_finder_spec.rb' @@ -4571,7 +4491,6 @@ - './spec/frontend/fixtures/tags.rb' - './spec/frontend/fixtures/timezones.rb' - './spec/frontend/fixtures/todos.rb' -- './spec/frontend/fixtures/u2f.rb' - './spec/frontend/fixtures/webauthn.rb' - './spec/graphql/features/authorization_spec.rb' - './spec/graphql/gitlab_schema_spec.rb' @@ -4603,7 +4522,6 @@ - './spec/graphql/mutations/clusters/agent_tokens/create_spec.rb' - './spec/graphql/mutations/clusters/agent_tokens/revoke_spec.rb' - './spec/graphql/mutations/commits/create_spec.rb' -- './spec/graphql/mutations/concerns/mutations/finds_by_gid_spec.rb' - './spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb' - './spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb' - './spec/graphql/mutations/container_expiration_policies/update_spec.rb' @@ -5084,7 +5002,6 @@ - './spec/helpers/admin/deploy_key_helper_spec.rb' - './spec/helpers/admin/identities_helper_spec.rb' - './spec/helpers/admin/user_actions_helper_spec.rb' -- './spec/helpers/analytics/cycle_analytics_helper_spec.rb' - './spec/helpers/appearances_helper_spec.rb' - './spec/helpers/application_helper_spec.rb' - './spec/helpers/application_settings_helper_spec.rb' @@ -5099,14 +5016,12 @@ - './spec/helpers/boards_helper_spec.rb' - './spec/helpers/branches_helper_spec.rb' - './spec/helpers/breadcrumbs_helper_spec.rb' -- './spec/helpers/broadcast_messages_helper_spec.rb' - './spec/helpers/button_helper_spec.rb' - './spec/helpers/calendar_helper_spec.rb' - './spec/helpers/ci/builds_helper_spec.rb' - './spec/helpers/ci/jobs_helper_spec.rb' - './spec/helpers/ci/pipeline_editor_helper_spec.rb' - './spec/helpers/ci/pipelines_helper_spec.rb' -- './spec/helpers/ci/runners_helper_spec.rb' - './spec/helpers/ci/secure_files_helper_spec.rb' - './spec/helpers/ci/status_helper_spec.rb' - './spec/helpers/ci/triggers_helper_spec.rb' @@ -5142,7 +5057,6 @@ - './spec/helpers/groups/settings_helper_spec.rb' - './spec/helpers/hooks_helper_spec.rb' - './spec/helpers/icons_helper_spec.rb' -- './spec/helpers/ide_helper_spec.rb' - './spec/helpers/import_helper_spec.rb' - './spec/helpers/instance_configuration_helper_spec.rb' - './spec/helpers/integrations_helper_spec.rb' @@ -5158,7 +5072,6 @@ - './spec/helpers/members_helper_spec.rb' - './spec/helpers/merge_requests_helper_spec.rb' - './spec/helpers/namespaces_helper_spec.rb' -- './spec/helpers/nav_helper_spec.rb' - './spec/helpers/nav/new_dropdown_helper_spec.rb' - './spec/helpers/nav/top_nav_helper_spec.rb' - './spec/helpers/notes_helper_spec.rb' @@ -5201,7 +5114,6 @@ - './spec/helpers/tab_helper_spec.rb' - './spec/helpers/terms_helper_spec.rb' - './spec/helpers/timeboxes_helper_spec.rb' -- './spec/helpers/timeboxes_routing_helper_spec.rb' - './spec/helpers/time_helper_spec.rb' - './spec/helpers/time_zone_helper_spec.rb' - './spec/helpers/todos_helper_spec.rb' @@ -5280,7 +5192,6 @@ - './spec/lib/api/entities/ci/job_request/port_spec.rb' - './spec/lib/api/entities/ci/job_request/service_spec.rb' - './spec/lib/api/entities/ci/pipeline_spec.rb' -- './spec/lib/api/entities/clusters/agent_authorization_spec.rb' - './spec/lib/api/entities/clusters/agent_spec.rb' - './spec/lib/api/entities/deploy_key_spec.rb' - './spec/lib/api/entities/deploy_keys_project_spec.rb' @@ -5674,25 +5585,19 @@ - './spec/lib/gitlab/auth/saml/user_spec.rb' - './spec/lib/gitlab/auth_spec.rb' - './spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb' -- './spec/lib/gitlab/auth/u2f_webauthn_converter_spec.rb' - './spec/lib/gitlab/auth/unique_ips_limiter_spec.rb' - './spec/lib/gitlab/auth/user_access_denied_reason_spec.rb' - './spec/lib/gitlab/avatar_cache_spec.rb' -- './spec/lib/gitlab/background_migration/backfill_ci_queuing_tables_spec.rb' - './spec/lib/gitlab/background_migration/backfill_draft_status_on_merge_requests_spec.rb' - './spec/lib/gitlab/background_migration/backfill_draft_status_on_merge_requests_with_corrected_regex_spec.rb' - './spec/lib/gitlab/background_migration/backfill_group_features_spec.rb' - './spec/lib/gitlab/background_migration/backfill_imported_issue_search_data_spec.rb' - './spec/lib/gitlab/background_migration/backfill_integrations_enable_ssl_verification_spec.rb' -- './spec/lib/gitlab/background_migration/backfill_integrations_type_new_spec.rb' - './spec/lib/gitlab/background_migration/backfill_issue_search_data_spec.rb' -- './spec/lib/gitlab/background_migration/backfill_jira_tracker_deployment_type2_spec.rb' - './spec/lib/gitlab/background_migration/backfill_member_namespace_for_group_members_spec.rb' - './spec/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route_spec.rb' - './spec/lib/gitlab/background_migration/backfill_namespace_id_for_project_route_spec.rb' - './spec/lib/gitlab/background_migration/backfill_namespace_id_of_vulnerability_reads_spec.rb' -- './spec/lib/gitlab/background_migration/backfill_namespace_traversal_ids_children_spec.rb' -- './spec/lib/gitlab/background_migration/backfill_namespace_traversal_ids_roots_spec.rb' - './spec/lib/gitlab/background_migration/backfill_note_discussion_id_spec.rb' - './spec/lib/gitlab/background_migration/backfill_project_feature_package_registry_access_level_spec.rb' - './spec/lib/gitlab/background_migration/backfill_project_import_level_spec.rb' @@ -5701,8 +5606,6 @@ - './spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb' - './spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb' - './spec/lib/gitlab/background_migration/backfill_topics_title_spec.rb' -- './spec/lib/gitlab/background_migration/backfill_upvotes_count_on_issues_spec.rb' -- './spec/lib/gitlab/background_migration/backfill_user_namespace_spec.rb' - './spec/lib/gitlab/background_migration/backfill_vulnerability_reads_cluster_agent_spec.rb' - './spec/lib/gitlab/background_migration/backfill_work_item_type_id_for_issues_spec.rb' - './spec/lib/gitlab/background_migration/base_job_spec.rb' @@ -5714,48 +5617,26 @@ - './spec/lib/gitlab/background_migration/batching_strategies/loose_index_scan_batching_strategy_spec.rb' - './spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb' - './spec/lib/gitlab/background_migration/cleanup_draft_data_from_faulty_regex_spec.rb' -- './spec/lib/gitlab/background_migration/cleanup_orphaned_lfs_objects_projects_spec.rb' - './spec/lib/gitlab/background_migration/cleanup_orphaned_routes_spec.rb' - './spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb' -- './spec/lib/gitlab/background_migration/delete_orphaned_deployments_spec.rb' - './spec/lib/gitlab/background_migration/destroy_invalid_group_members_spec.rb' -- './spec/lib/gitlab/background_migration/disable_expiration_policies_linked_to_no_container_images_spec.rb' - './spec/lib/gitlab/background_migration/disable_legacy_open_source_licence_for_recent_public_projects_spec.rb' - './spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_inactive_public_projects_spec.rb' - './spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb' - './spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_one_member_no_repo_projects_spec.rb' -- './spec/lib/gitlab/background_migration/drop_invalid_security_findings_spec.rb' -- './spec/lib/gitlab/background_migration/drop_invalid_vulnerabilities_spec.rb' -- './spec/lib/gitlab/background_migration/encrypt_integration_properties_spec.rb' -- './spec/lib/gitlab/background_migration/encrypt_static_object_token_spec.rb' - './spec/lib/gitlab/background_migration/expire_o_auth_tokens_spec.rb' -- './spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb' - './spec/lib/gitlab/background_migration/fix_duplicate_project_name_and_path_spec.rb' -- './spec/lib/gitlab/background_migration/fix_first_mentioned_in_commit_at_spec.rb' -- './spec/lib/gitlab/background_migration/fix_merge_request_diff_commit_users_spec.rb' - './spec/lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata_spec.rb' - './spec/lib/gitlab/background_migration/job_coordinator_spec.rb' - './spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb' - './spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb' - './spec/lib/gitlab/background_migration/mailers/unconfirm_mailer_spec.rb' -- './spec/lib/gitlab/background_migration/merge_topics_with_same_name_spec.rb' -- './spec/lib/gitlab/background_migration/migrate_merge_request_diff_commit_users_spec.rb' - './spec/lib/gitlab/background_migration/migrate_personal_namespace_project_maintainer_to_owner_spec.rb' -- './spec/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics_spec.rb' - './spec/lib/gitlab/background_migration/migrate_shimo_confluence_integration_category_spec.rb' -- './spec/lib/gitlab/background_migration/migrate_u2f_webauthn_spec.rb' -- './spec/lib/gitlab/background_migration/move_container_registry_enabled_to_project_feature_spec.rb' - './spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb' - './spec/lib/gitlab/background_migration/populate_container_repository_migration_plan_spec.rb' -- './spec/lib/gitlab/background_migration/populate_namespace_statistics_spec.rb' - './spec/lib/gitlab/background_migration/populate_operation_visibility_permissions_from_operations_spec.rb' -- './spec/lib/gitlab/background_migration/populate_topics_non_private_projects_count_spec.rb' -- './spec/lib/gitlab/background_migration/populate_topics_total_projects_count_cache_spec.rb' -- './spec/lib/gitlab/background_migration/populate_vulnerability_reads_spec.rb' - './spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb' -- './spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb' -- './spec/lib/gitlab/background_migration/remove_all_trace_expiration_dates_spec.rb' -- './spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb' - './spec/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb' - './spec/lib/gitlab/background_migration/remove_self_managed_wiki_notes_spec.rb' - './spec/lib/gitlab/background_migration/remove_vulnerability_finding_links_spec.rb' @@ -5765,16 +5646,10 @@ - './spec/lib/gitlab/background_migration/set_correct_vulnerability_state_spec.rb' - './spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb' - './spec/lib/gitlab/background_migration_spec.rb' -- './spec/lib/gitlab/background_migration/steal_migrate_merge_request_diff_commit_users_spec.rb' - './spec/lib/gitlab/background_migration/update_delayed_project_removal_to_null_for_user_namespaces_spec.rb' - './spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb' -- './spec/lib/gitlab/background_migration/update_timelogs_null_spent_at_spec.rb' -- './spec/lib/gitlab/background_migration/update_timelogs_project_id_spec.rb' -- './spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb' - './spec/lib/gitlab/background_task_spec.rb' - './spec/lib/gitlab/backtrace_cleaner_spec.rb' -- './spec/lib/gitlab/bare_repository_import/importer_spec.rb' -- './spec/lib/gitlab/bare_repository_import/repository_spec.rb' - './spec/lib/gitlab/batch_worker_context_spec.rb' - './spec/lib/gitlab/bitbucket_import/importer_spec.rb' - './spec/lib/gitlab/bitbucket_import/project_creator_spec.rb' @@ -5973,7 +5848,6 @@ - './spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb' - './spec/lib/gitlab/ci/pipeline/chain/command_spec.rb' - './spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb' -- './spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb' - './spec/lib/gitlab/ci/pipeline/chain/create_spec.rb' - './spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb' - './spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb' @@ -6042,7 +5916,6 @@ - './spec/lib/gitlab/ci/reports/security/scanned_resource_spec.rb' - './spec/lib/gitlab/ci/reports/security/scanner_spec.rb' - './spec/lib/gitlab/ci/reports/security/scan_spec.rb' -- './spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb' - './spec/lib/gitlab/ci/reports/terraform_reports_spec.rb' - './spec/lib/gitlab/ci/reports/test_case_spec.rb' - './spec/lib/gitlab/ci/reports/test_failure_history_spec.rb' @@ -6235,8 +6108,6 @@ - './spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb' - './spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb' - './spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb' -- './spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb' -- './spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb' - './spec/lib/gitlab/database_importers/work_items/base_type_importer_spec.rb' - './spec/lib/gitlab/database/load_balancing/action_cable_callbacks_spec.rb' - './spec/lib/gitlab/database/load_balancing/configuration_spec.rb' @@ -6280,7 +6151,6 @@ - './spec/lib/gitlab/database/migrations/test_background_runner_spec.rb' - './spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb' - './spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb' -- './spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb' - './spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb' - './spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb' - './spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb' @@ -6418,7 +6288,6 @@ - './spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb' - './spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb' - './spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb' -- './spec/lib/gitlab/email/hook/validate_addresses_interceptor_spec.rb' - './spec/lib/gitlab/email/message/build_ios_app_guide_spec.rb' - './spec/lib/gitlab/email/message/in_product_marketing/admin_verify_spec.rb' - './spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb' @@ -6449,7 +6318,6 @@ - './spec/lib/gitlab/error_tracking/processor/sanitize_error_message_processor_spec.rb' - './spec/lib/gitlab/error_tracking/processor/sanitizer_processor_spec.rb' - './spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb' -- './spec/lib/gitlab/error_tracking_spec.rb' - './spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb' - './spec/lib/gitlab/etag_caching/middleware_spec.rb' - './spec/lib/gitlab/etag_caching/router/graphql_spec.rb' @@ -6507,7 +6375,6 @@ - './spec/lib/gitlab/gitaly_client/ref_service_spec.rb' - './spec/lib/gitlab/gitaly_client/remote_service_spec.rb' - './spec/lib/gitlab/gitaly_client/repository_service_spec.rb' -- './spec/lib/gitlab/gitaly_client/server_service_spec.rb' - './spec/lib/gitlab/gitaly_client_spec.rb' - './spec/lib/gitlab/gitaly_client/storage_settings_spec.rb' - './spec/lib/gitlab/gitaly_client/util_spec.rb' @@ -6593,9 +6460,6 @@ - './spec/lib/gitlab/github_import_spec.rb' - './spec/lib/gitlab/github_import/user_finder_spec.rb' - './spec/lib/gitlab/git/keep_around_spec.rb' -- './spec/lib/gitlab/gitlab_import/client_spec.rb' -- './spec/lib/gitlab/gitlab_import/importer_spec.rb' -- './spec/lib/gitlab/gitlab_import/project_creator_spec.rb' - './spec/lib/gitlab/git/lfs_changes_spec.rb' - './spec/lib/gitlab/git/lfs_pointer_file_spec.rb' - './spec/lib/gitlab/git/merge_base_spec.rb' @@ -6641,7 +6505,6 @@ - './spec/lib/gitlab/graphql/batch_key_spec.rb' - './spec/lib/gitlab/graphql/calls_gitaly/field_extension_spec.rb' - './spec/lib/gitlab/graphql/copy_field_description_spec.rb' -- './spec/lib/gitlab/graphql/deprecation_spec.rb' - './spec/lib/gitlab/graphql/generic_tracing_spec.rb' - './spec/lib/gitlab/graphql/known_operations_spec.rb' - './spec/lib/gitlab/graphql/lazy_spec.rb' @@ -6729,7 +6592,6 @@ - './spec/lib/gitlab/import_export/error_spec.rb' - './spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb' - './spec/lib/gitlab/import_export/file_importer_spec.rb' -- './spec/lib/gitlab/import_export/fork_spec.rb' - './spec/lib/gitlab/import_export/group/object_builder_spec.rb' - './spec/lib/gitlab/import_export/group/relation_factory_spec.rb' - './spec/lib/gitlab/import_export/group/relation_tree_restorer_spec.rb' @@ -6737,13 +6599,9 @@ - './spec/lib/gitlab/import_export/group/tree_saver_spec.rb' - './spec/lib/gitlab/import_export/hash_util_spec.rb' - './spec/lib/gitlab/import_export/importer_spec.rb' -- './spec/lib/gitlab/import_export/import_export_equivalence_spec.rb' - './spec/lib/gitlab/import_export/import_export_spec.rb' - './spec/lib/gitlab/import_export/import_failure_service_spec.rb' - './spec/lib/gitlab/import_export/import_test_coverage_spec.rb' -- './spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb' -- './spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb' -- './spec/lib/gitlab/import_export/json/legacy_writer_spec.rb' - './spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb' - './spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb' - './spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb' @@ -6790,7 +6648,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/incoming_email_spec.rb' - './spec/lib/gitlab/insecure_key_fingerprint_spec.rb' - './spec/lib/gitlab/instrumentation_helper_spec.rb' - './spec/lib/gitlab/instrumentation/rate_limiting_gates_spec.rb' @@ -6829,19 +6686,6 @@ - './spec/lib/gitlab/kubernetes/default_namespace_spec.rb' - './spec/lib/gitlab/kubernetes/deployment_spec.rb' - './spec/lib/gitlab/kubernetes/generic_secret_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/api_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/pod_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v2/base_command_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v2/certificate_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v2/delete_command_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v2/init_command_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v2/install_command_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v2/patch_command_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v2/reset_command_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v3/base_command_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v3/delete_command_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v3/install_command_spec.rb' -- './spec/lib/gitlab/kubernetes/helm/v3/patch_command_spec.rb' - './spec/lib/gitlab/kubernetes/ingress_spec.rb' - './spec/lib/gitlab/kubernetes/kube_client_spec.rb' - './spec/lib/gitlab/kubernetes/kubeconfig/entry/cluster_spec.rb' @@ -7031,21 +6875,6 @@ - './spec/lib/gitlab/performance_bar_spec.rb' - './spec/lib/gitlab/performance_bar/stats_spec.rb' - './spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb' -- './spec/lib/gitlab/phabricator_import/cache/map_spec.rb' -- './spec/lib/gitlab/phabricator_import/conduit/client_spec.rb' -- './spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb' -- './spec/lib/gitlab/phabricator_import/conduit/response_spec.rb' -- './spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb' -- './spec/lib/gitlab/phabricator_import/conduit/user_spec.rb' -- './spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb' -- './spec/lib/gitlab/phabricator_import/importer_spec.rb' -- './spec/lib/gitlab/phabricator_import/issues/importer_spec.rb' -- './spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb' -- './spec/lib/gitlab/phabricator_import/project_creator_spec.rb' -- './spec/lib/gitlab/phabricator_import/representation/task_spec.rb' -- './spec/lib/gitlab/phabricator_import/representation/user_spec.rb' -- './spec/lib/gitlab/phabricator_import/user_finder_spec.rb' -- './spec/lib/gitlab/phabricator_import/worker_state_spec.rb' - './spec/lib/gitlab/pipeline_scope_counts_spec.rb' - './spec/lib/gitlab/polling_interval_spec.rb' - './spec/lib/gitlab/popen/runner_spec.rb' @@ -7068,7 +6897,6 @@ - './spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb' - './spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb' - './spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb' -- './spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb' - './spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb' - './spec/lib/gitlab/prometheus/queries/validate_query_spec.rb' - './spec/lib/gitlab/prometheus/query_variables_spec.rb' @@ -7085,14 +6913,12 @@ - './spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb' - './spec/lib/gitlab/quick_actions/substitution_definition_spec.rb' - './spec/lib/gitlab/quick_actions/users_extractor_spec.rb' -- './spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb' - './spec/lib/gitlab/rack_attack/request_spec.rb' - './spec/lib/gitlab/rack_attack_spec.rb' - './spec/lib/gitlab/rack_attack/user_allowlist_spec.rb' - './spec/lib/gitlab/reactive_cache_set_cache_spec.rb' - './spec/lib/gitlab/redis/boolean_spec.rb' - './spec/lib/gitlab/redis/cache_spec.rb' -- './spec/lib/gitlab/redis/duplicate_jobs_spec.rb' - './spec/lib/gitlab/redis/hll_spec.rb' - './spec/lib/gitlab/redis/multi_store_spec.rb' - './spec/lib/gitlab/redis/queues_spec.rb' @@ -7154,8 +6980,6 @@ - './spec/lib/gitlab/seeder_spec.rb' - './spec/lib/gitlab/serializer/ci/variables_spec.rb' - './spec/lib/gitlab/serializer/pagination_spec.rb' -- './spec/lib/gitlab/serverless/service_spec.rb' -- './spec/lib/gitlab/service_desk_email_spec.rb' - './spec/lib/gitlab/service_desk_spec.rb' - './spec/lib/gitlab/session_spec.rb' - './spec/lib/gitlab/setup_helper/praefect_spec.rb' @@ -7225,7 +7049,6 @@ - './spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb' - './spec/lib/gitlab/slash_commands/presenters/run_spec.rb' - './spec/lib/gitlab/slash_commands/run_spec.rb' -- './spec/lib/gitlab/slug/environment_spec.rb' - './spec/lib/gitlab/snippet_search_results_spec.rb' - './spec/lib/gitlab/sourcegraph_spec.rb' - './spec/lib/gitlab/spamcheck/client_spec.rb' @@ -7306,7 +7129,6 @@ - './spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb' - './spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb' - './spec/lib/gitlab/usage_data_counters_spec.rb' -- './spec/lib/gitlab/usage_data_counters/track_unique_events_spec.rb' - './spec/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter_spec.rb' - './spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb' - './spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb' @@ -7445,7 +7267,6 @@ - './spec/lib/sidebars/concerns/link_with_html_options_spec.rb' - './spec/lib/sidebars/groups/menus/ci_cd_menu_spec.rb' - './spec/lib/sidebars/groups/menus/group_information_menu_spec.rb' -- './spec/lib/sidebars/groups/menus/invite_team_members_menu_spec.rb' - './spec/lib/sidebars/groups/menus/issues_menu_spec.rb' - './spec/lib/sidebars/groups/menus/kubernetes_menu_spec.rb' - './spec/lib/sidebars/groups/menus/merge_requests_menu_spec.rb' @@ -7463,7 +7284,6 @@ - './spec/lib/sidebars/projects/menus/external_wiki_menu_spec.rb' - './spec/lib/sidebars/projects/menus/hidden_menu_spec.rb' - './spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb' -- './spec/lib/sidebars/projects/menus/invite_team_members_menu_spec.rb' - './spec/lib/sidebars/projects/menus/issues_menu_spec.rb' - './spec/lib/sidebars/projects/menus/merge_requests_menu_spec.rb' - './spec/lib/sidebars/projects/menus/monitor_menu_spec.rb' @@ -7490,7 +7310,6 @@ - './spec/lib/system_check/sidekiq_check_spec.rb' - './spec/lib/system_check/simple_executor_spec.rb' - './spec/lib/system_check_spec.rb' -- './spec/lib/tasks/gitlab/metrics_exporter_task_spec.rb' - './spec/lib/unnested_in_filters/dsl_spec.rb' - './spec/lib/unnested_in_filters/rewriter_spec.rb' - './spec/lib/uploaded_file_spec.rb' @@ -7513,72 +7332,6 @@ - './spec/mailers/notify_spec.rb' - './spec/mailers/repository_check_mailer_spec.rb' - './spec/metrics_server/metrics_server_spec.rb' -- './spec/migrations/20210603222333_remove_builds_email_service_from_services_spec.rb' -- './spec/migrations/20210610153556_delete_legacy_operations_feature_flags_spec.rb' -- './spec/migrations/2021061716138_cascade_delete_freeze_periods_spec.rb' -- './spec/migrations/20210708130419_reschedule_merge_request_diff_users_background_migration_spec.rb' -- './spec/migrations/20210713042000_fix_ci_sources_pipelines_index_names_spec.rb' -- './spec/migrations/20210722042939_update_issuable_slas_where_issue_closed_spec.rb' -- './spec/migrations/20210722150102_operations_feature_flags_correct_flexible_rollout_values_spec.rb' -- './spec/migrations/20210804150320_create_base_work_item_types_spec.rb' -- './spec/migrations/20210805192450_update_trial_plans_ci_daily_pipeline_schedule_triggers_spec.rb' -- './spec/migrations/20210811122206_update_external_project_bots_spec.rb' -- './spec/migrations/20210812013042_remove_duplicate_project_authorizations_spec.rb' -- './spec/migrations/20210819145000_drop_temporary_columns_and_triggers_for_ci_builds_runner_session_spec.rb' -- './spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb' -- './spec/migrations/20210902144144_drop_temporary_columns_and_triggers_for_ci_build_needs_spec.rb' -- './spec/migrations/20210906100316_drop_temporary_columns_and_triggers_for_ci_build_trace_chunks_spec.rb' -- './spec/migrations/20210906130643_drop_temporary_columns_and_triggers_for_taggings_spec.rb' -- './spec/migrations/20210907013944_cleanup_bigint_conversion_for_ci_builds_metadata_spec.rb' -- './spec/migrations/20210907211557_finalize_ci_builds_bigint_conversion_spec.rb' -- './spec/migrations/20210910194952_update_report_type_for_existing_approval_project_rules_spec.rb' -- './spec/migrations/20210914095310_cleanup_orphan_project_access_tokens_spec.rb' -- './spec/migrations/20210915022415_cleanup_bigint_conversion_for_ci_builds_spec.rb' -- './spec/migrations/20210918201050_remove_old_pending_jobs_for_recalculate_vulnerabilities_occurrences_uuid_spec.rb' -- './spec/migrations/20210922021816_drop_int4_columns_for_ci_job_artifacts_spec.rb' -- './spec/migrations/20210922025631_drop_int4_column_for_ci_sources_pipelines_spec.rb' -- './spec/migrations/20210922082019_drop_int4_column_for_events_spec.rb' -- './spec/migrations/20210922091402_drop_int4_column_for_push_event_payloads_spec.rb' -- './spec/migrations/20211006060436_schedule_populate_topics_total_projects_count_cache_spec.rb' -- './spec/migrations/20211012134316_clean_up_migrate_merge_request_diff_commit_users_spec.rb' -- './spec/migrations/20211018152654_schedule_remove_duplicate_vulnerabilities_findings3_spec.rb' -- './spec/migrations/20211028155449_schedule_fix_merge_request_diff_commit_users_migration_spec.rb' -- './spec/migrations/20211101222614_consume_remaining_user_namespace_jobs_spec.rb' -- './spec/migrations/20211110143306_add_not_null_constraint_to_security_findings_uuid_spec.rb' -- './spec/migrations/20211110151350_schedule_drop_invalid_security_findings_spec.rb' -- './spec/migrations/20211116091751_change_namespace_type_default_to_user_spec.rb' -- './spec/migrations/20211116111644_schedule_remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb' -- './spec/migrations/20211117084814_migrate_remaining_u2f_registrations_spec.rb' -- './spec/migrations/20211126115449_encrypt_static_objects_external_storage_auth_token_spec.rb' -- './spec/migrations/20211126204445_add_task_to_work_item_types_spec.rb' -- './spec/migrations/20211130165043_backfill_sequence_column_for_sprints_table_spec.rb' -- './spec/migrations/20211203091642_add_index_to_projects_on_marked_for_deletion_at_spec.rb' -- './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/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' -- './spec/migrations/20220106112085_add_update_vulnerability_reads_location_trigger_spec.rb' -- './spec/migrations/20220106163326_add_has_issues_on_vulnerability_reads_trigger_spec.rb' -- './spec/migrations/20220107064845_populate_vulnerability_reads_spec.rb' -- './spec/migrations/20220120094340_drop_position_from_security_findings_spec.rb' -- './spec/migrations/20220124130028_dedup_runner_projects_spec.rb' -- './spec/migrations/20220128155251_remove_dangling_running_builds_spec.rb' -- './spec/migrations/20220128155814_fix_approval_rules_code_owners_rule_type_index_spec.rb' -- './spec/migrations/20220202105733_delete_service_template_records_spec.rb' -- './spec/migrations/20220204095121_backfill_namespace_statistics_with_dependency_proxy_size_spec.rb' -- './spec/migrations/20220204194347_encrypt_integration_properties_spec.rb' -- './spec/migrations/20220208080921_schedule_migrate_personal_namespace_project_maintainer_to_owner_spec.rb' -- './spec/migrations/20220211214605_update_integrations_trigger_type_new_on_insert_null_safe_spec.rb' -- './spec/migrations/20220213103859_remove_integrations_type_spec.rb' -- './spec/migrations/20220222192524_create_not_null_constraint_releases_tag_spec.rb' -- './spec/migrations/20220222192525_remove_null_releases_spec.rb' -- './spec/migrations/20220223124428_schedule_merge_topics_with_same_name_spec.rb' -- './spec/migrations/20220305223212_add_security_training_providers_spec.rb' -- './spec/migrations/20220307192610_remove_duplicate_project_tag_releases_spec.rb' -- './spec/migrations/20220309084954_remove_leftover_external_pull_request_deletions_spec.rb' -- './spec/migrations/20220310141349_remove_dependency_list_usage_data_from_redis_spec.rb' - './spec/migrations/20220315171129_cleanup_draft_data_from_faulty_regex_spec.rb' - './spec/migrations/20220316202640_populate_container_repositories_migration_plan_spec.rb' - './spec/migrations/20220321234317_remove_all_issuable_escalation_statuses_spec.rb' @@ -7621,28 +7374,11 @@ - './spec/migrations/20220802114351_reschedule_backfill_container_registry_size_into_project_statistics_spec.rb' - './spec/migrations/20220802204737_remove_deactivated_user_highest_role_stats_spec.rb' - './spec/migrations/active_record/schema_spec.rb' -- './spec/migrations/add_default_project_approval_rules_vuln_allowed_spec.rb' - './spec/migrations/add_epics_relative_position_spec.rb' -- './spec/migrations/add_open_source_plan_spec.rb' -- './spec/migrations/add_premium_and_ultimate_plan_limits_spec.rb' -- './spec/migrations/add_triggers_to_integrations_type_new_spec.rb' -- './spec/migrations/add_upvotes_count_index_to_issues_spec.rb' - './spec/migrations/add_web_hook_calls_to_plan_limits_paid_tiers_spec.rb' -- './spec/migrations/associate_existing_dast_builds_with_variables_spec.rb' -- './spec/migrations/backfill_all_project_namespaces_spec.rb' -- './spec/migrations/backfill_cadence_id_for_boards_scoped_to_iteration_spec.rb' -- './spec/migrations/backfill_cycle_analytics_aggregations_spec.rb' -- './spec/migrations/backfill_group_features_spec.rb' - './spec/migrations/backfill_integrations_enable_ssl_verification_spec.rb' -- './spec/migrations/backfill_integrations_type_new_spec.rb' -- './spec/migrations/backfill_issues_upvotes_count_spec.rb' -- './spec/migrations/backfill_member_namespace_id_for_group_members_spec.rb' -- './spec/migrations/backfill_namespace_id_for_namespace_routes_spec.rb' - './spec/migrations/backfill_namespace_id_for_project_routes_spec.rb' - './spec/migrations/backfill_project_import_level_spec.rb' -- './spec/migrations/backfill_project_namespaces_for_group_spec.rb' -- './spec/migrations/backfill_stage_event_hash_spec.rb' -- './spec/migrations/backfill_user_namespace_spec.rb' - './spec/migrations/bulk_insert_cluster_enabled_grants_spec.rb' - './spec/migrations/change_public_projects_cost_factor_spec.rb' - './spec/migrations/cleanup_after_fixing_issue_when_admin_changed_primary_email_spec.rb' @@ -7650,60 +7386,21 @@ - './spec/migrations/cleanup_backfill_integrations_enable_ssl_verification_spec.rb' - './spec/migrations/cleanup_mr_attention_request_todos_spec.rb' - './spec/migrations/cleanup_orphaned_routes_spec.rb' -- './spec/migrations/cleanup_remaining_orphan_invites_spec.rb' -- './spec/migrations/confirm_security_bot_spec.rb' -- './spec/migrations/disable_expiration_policies_linked_to_no_container_images_spec.rb' -- './spec/migrations/disable_job_token_scope_when_unused_spec.rb' - './spec/migrations/finalize_orphaned_routes_cleanup_spec.rb' - './spec/migrations/finalize_project_namespaces_backfill_spec.rb' - './spec/migrations/finalize_routes_backfilling_for_projects_spec.rb' -- './spec/migrations/finalize_traversal_ids_background_migrations_spec.rb' - './spec/migrations/fix_and_backfill_project_namespaces_for_projects_with_duplicate_name_spec.rb' -- './spec/migrations/fix_batched_migrations_old_format_job_arguments_spec.rb' -- './spec/migrations/generate_customers_dot_jwt_signing_key_spec.rb' -- './spec/migrations/migrate_protected_attribute_to_pending_builds_spec.rb' -- './spec/migrations/orphaned_invite_tokens_cleanup_spec.rb' -- './spec/migrations/populate_audit_event_streaming_verification_token_spec.rb' - './spec/migrations/populate_operation_visibility_permissions_spec.rb' - './spec/migrations/queue_backfill_project_feature_package_registry_access_level_spec.rb' -- './spec/migrations/recreate_index_security_ci_builds_on_name_and_id_parser_features_spec.rb' -- './spec/migrations/recreate_index_security_ci_builds_on_name_and_id_parser_with_new_features_spec.rb' -- './spec/migrations/remove_duplicate_dast_site_tokens_spec.rb' -- './spec/migrations/remove_duplicate_dast_site_tokens_with_same_token_spec.rb' - './spec/migrations/remove_invalid_integrations_spec.rb' -- './spec/migrations/remove_not_null_contraint_on_title_from_sprints_spec.rb' -- './spec/migrations/remove_schedule_and_status_from_pending_alert_escalations_spec.rb' - './spec/migrations/remove_wiki_notes_spec.rb' -- './spec/migrations/rename_services_to_integrations_spec.rb' -- './spec/migrations/replace_external_wiki_triggers_spec.rb' - './spec/migrations/reschedule_backfill_imported_issue_search_data_spec.rb' -- './spec/migrations/reschedule_delete_orphaned_deployments_spec.rb' -- './spec/migrations/re_schedule_latest_pipeline_id_population_with_all_security_related_artifact_types_spec.rb' -- './spec/migrations/reset_job_token_scope_enabled_again_spec.rb' -- './spec/migrations/reset_job_token_scope_enabled_spec.rb' -- './spec/migrations/reset_severity_levels_to_new_default_spec.rb' -- './spec/migrations/retry_backfill_traversal_ids_spec.rb' - './spec/migrations/schedule_backfill_draft_status_on_merge_requests_corrected_regex_spec.rb' - './spec/migrations/schedule_backfilling_the_namespace_id_for_vulnerability_reads_spec.rb' -- './spec/migrations/schedule_copy_ci_builds_columns_to_security_scans2_spec.rb' -- './spec/migrations/schedule_fix_incorrect_max_seats_used2_spec.rb' -- './spec/migrations/schedule_fix_incorrect_max_seats_used_spec.rb' - './spec/migrations/schedule_populate_requirements_issue_id_spec.rb' - './spec/migrations/schedule_purging_stale_security_scans_spec.rb' -- './spec/migrations/schedule_recalculate_vulnerability_finding_signatures_for_findings_spec.rb' -- './spec/migrations/schedule_security_setting_creation_spec.rb' - './spec/migrations/schedule_set_correct_vulnerability_state_spec.rb' -- './spec/migrations/schedule_update_timelogs_null_spent_at_spec.rb' -- './spec/migrations/set_default_job_token_scope_true_spec.rb' -- './spec/migrations/slice_merge_request_diff_commit_migrations_spec.rb' -- './spec/migrations/start_backfill_ci_queuing_tables_spec.rb' -- './spec/migrations/steal_merge_request_diff_commit_users_migration_spec.rb' - './spec/migrations/toggle_vsa_aggregations_enable_spec.rb' -- './spec/migrations/update_application_settings_container_registry_exp_pol_worker_capacity_default_spec.rb' -- './spec/migrations/update_application_settings_protected_paths_spec.rb' -- './spec/migrations/update_default_scan_method_of_dast_site_profile_spec.rb' -- './spec/migrations/update_integrations_trigger_type_new_on_insert_spec.rb' -- './spec/migrations/update_invalid_member_states_spec.rb' - './spec/models/ability_spec.rb' - './spec/models/abuse_report_spec.rb' - './spec/models/active_session_spec.rb' @@ -7718,7 +7415,6 @@ - './spec/models/analytics/cycle_analytics/aggregation_spec.rb' - './spec/models/analytics/cycle_analytics/issue_stage_event_spec.rb' - './spec/models/analytics/cycle_analytics/merge_request_stage_event_spec.rb' -- './spec/models/analytics/cycle_analytics/project_value_stream_spec.rb' - './spec/models/analytics/cycle_analytics/stage_event_hash_spec.rb' - './spec/models/analytics/usage_trends/measurement_spec.rb' - './spec/models/appearance_spec.rb' @@ -7730,7 +7426,6 @@ - './spec/models/audit_event_spec.rb' - './spec/models/authentication_event_spec.rb' - './spec/models/award_emoji_spec.rb' -- './spec/models/awareness_session_spec.rb' - './spec/models/aws/role_spec.rb' - './spec/models/badges/group_badge_spec.rb' - './spec/models/badge_spec.rb' @@ -7754,7 +7449,6 @@ - './spec/models/board_group_recent_visit_spec.rb' - './spec/models/board_project_recent_visit_spec.rb' - './spec/models/board_spec.rb' -- './spec/models/broadcast_message_spec.rb' - './spec/models/bulk_imports/configuration_spec.rb' - './spec/models/bulk_imports/entity_spec.rb' - './spec/models/bulk_imports/export_spec.rb' @@ -7824,20 +7518,8 @@ - './spec/models/ci/unit_test_spec.rb' - './spec/models/ci/variable_spec.rb' - './spec/models/clusters/agents/activity_event_spec.rb' -- './spec/models/clusters/agents/group_authorization_spec.rb' -- './spec/models/clusters/agents/implicit_authorization_spec.rb' - './spec/models/clusters/agent_spec.rb' -- './spec/models/clusters/agents/project_authorization_spec.rb' - './spec/models/clusters/agent_token_spec.rb' -- './spec/models/clusters/applications/cert_manager_spec.rb' -- './spec/models/clusters/applications/cilium_spec.rb' -- './spec/models/clusters/applications/crossplane_spec.rb' -- './spec/models/clusters/applications/helm_spec.rb' -- './spec/models/clusters/applications/ingress_spec.rb' -- './spec/models/clusters/applications/jupyter_spec.rb' -- './spec/models/clusters/applications/knative_spec.rb' -- './spec/models/clusters/applications/prometheus_spec.rb' -- './spec/models/clusters/applications/runner_spec.rb' - './spec/models/clusters/cluster_enabled_grant_spec.rb' - './spec/models/clusters/clusters_hierarchy_spec.rb' - './spec/models/clusters/cluster_spec.rb' @@ -7862,7 +7544,6 @@ - './spec/models/concerns/atomic_internal_id_spec.rb' - './spec/models/concerns/avatarable_spec.rb' - './spec/models/concerns/awardable_spec.rb' -- './spec/models/concerns/awareness_spec.rb' - './spec/models/concerns/batch_destroy_dependent_associations_spec.rb' - './spec/models/concerns/batch_nullify_dependent_associations_spec.rb' - './spec/models/concerns/blob_language_from_git_attributes_spec.rb' @@ -7880,7 +7561,6 @@ - './spec/models/concerns/ci/has_status_spec.rb' - './spec/models/concerns/ci/has_variable_spec.rb' - './spec/models/concerns/ci/maskable_spec.rb' -- './spec/models/concerns/clusters/agents/authorization_config_scopes_spec.rb' - './spec/models/concerns/counter_attribute_spec.rb' - './spec/models/concerns/cron_schedulable_spec.rb' - './spec/models/concerns/cross_database_modification_spec.rb' @@ -7956,7 +7636,6 @@ - './spec/models/concerns/token_authenticatable_strategies/encryption_helper_spec.rb' - './spec/models/concerns/transactions_spec.rb' - './spec/models/concerns/triggerable_hooks_spec.rb' -- './spec/models/concerns/uniquify_spec.rb' - './spec/models/concerns/usage_statistics_spec.rb' - './spec/models/concerns/vulnerability_finding_helpers_spec.rb' - './spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb' @@ -8015,7 +7694,6 @@ - './spec/models/exported_protected_branch_spec.rb' - './spec/models/external_issue_spec.rb' - './spec/models/external_pull_request_spec.rb' -- './spec/models/factories_spec.rb' - './spec/models/fork_network_member_spec.rb' - './spec/models/fork_network_spec.rb' - './spec/models/generic_commit_status_spec.rb' @@ -8131,7 +7809,6 @@ - './spec/models/loose_foreign_keys/modification_tracker_spec.rb' - './spec/models/members/group_member_spec.rb' - './spec/models/members/last_group_owner_assigner_spec.rb' -- './spec/models/members/member_role_spec.rb' - './spec/models/members/member_task_spec.rb' - './spec/models/member_spec.rb' - './spec/models/members/project_member_spec.rb' @@ -8239,7 +7916,6 @@ - './spec/models/preloaders/merge_request_diff_preloader_spec.rb' - './spec/models/preloaders/user_max_access_level_in_groups_preloader_spec.rb' - './spec/models/preloaders/user_max_access_level_in_projects_preloader_spec.rb' -- './spec/models/preloaders/users_max_access_level_in_projects_preloader_spec.rb' - './spec/models/product_analytics_event_spec.rb' - './spec/models/programming_language_spec.rb' - './spec/models/project_authorization_spec.rb' @@ -8300,9 +7976,6 @@ - './spec/models/route_spec.rb' - './spec/models/sent_notification_spec.rb' - './spec/models/sentry_issue_spec.rb' -- './spec/models/serverless/domain_cluster_spec.rb' -- './spec/models/serverless/domain_spec.rb' -- './spec/models/serverless/function_spec.rb' - './spec/models/service_desk_setting_spec.rb' - './spec/models/shard_spec.rb' - './spec/models/snippet_blob_spec.rb' @@ -8566,7 +8239,6 @@ - './spec/requests/api/environments_spec.rb' - './spec/requests/api/error_tracking/client_keys_spec.rb' - './spec/requests/api/error_tracking/collector_spec.rb' -- './spec/requests/api/error_tracking/project_settings_spec.rb' - './spec/requests/api/events_spec.rb' - './spec/requests/api/feature_flags_spec.rb' - './spec/requests/api/feature_flags_user_lists_spec.rb' @@ -8612,7 +8284,6 @@ - './spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb' - './spec/requests/api/graphql/group/group_members_spec.rb' - './spec/requests/api/graphql/group/issues_spec.rb' -- './spec/requests/api/graphql/group/labels_query_spec.rb' - './spec/requests/api/graphql/group/merge_requests_spec.rb' - './spec/requests/api/graphql/group/milestones_spec.rb' - './spec/requests/api/graphql/group/packages_spec.rb' @@ -8650,12 +8321,8 @@ - './spec/requests/api/graphql/mutations/boards/lists/destroy_spec.rb' - './spec/requests/api/graphql/mutations/boards/lists/update_spec.rb' - './spec/requests/api/graphql/mutations/branches/create_spec.rb' -- './spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb' -- './spec/requests/api/graphql/mutations/ci/job_play_spec.rb' -- './spec/requests/api/graphql/mutations/ci/job_retry_spec.rb' - './spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb' - './spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb' -- './spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb' - './spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb' - './spec/requests/api/graphql/mutations/ci/pipeline_destroy_spec.rb' - './spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb' @@ -8956,7 +8623,6 @@ - './spec/requests/groups/settings/access_tokens_controller_spec.rb' - './spec/requests/groups/settings/applications_controller_spec.rb' - './spec/requests/health_controller_spec.rb' -- './spec/requests/ide_controller_spec.rb' - './spec/requests/import/gitlab_groups_controller_spec.rb' - './spec/requests/import/gitlab_projects_controller_spec.rb' - './spec/requests/import/url_controller_spec.rb' @@ -8965,7 +8631,6 @@ - './spec/requests/jira_connect/oauth_application_ids_controller_spec.rb' - './spec/requests/jira_connect/oauth_callbacks_controller_spec.rb' - './spec/requests/jira_connect/subscriptions_controller_spec.rb' -- './spec/requests/jira_connect/users_controller_spec.rb' - './spec/requests/jira_routing_spec.rb' - './spec/requests/jwks_controller_spec.rb' - './spec/requests/jwt_controller_spec.rb' @@ -9024,7 +8689,6 @@ - './spec/requests/runner_setup_controller_spec.rb' - './spec/requests/sandbox_controller_spec.rb' - './spec/requests/search_controller_spec.rb' -- './spec/requests/self_monitoring_project_spec.rb' - './spec/requests/sessions_spec.rb' - './spec/requests/terraform/services_controller_spec.rb' - './spec/requests/user_activity_spec.rb' @@ -9086,7 +8750,6 @@ - './spec/serializers/ci/trigger_entity_spec.rb' - './spec/serializers/ci/trigger_serializer_spec.rb' - './spec/serializers/ci/variable_entity_spec.rb' -- './spec/serializers/cluster_application_entity_spec.rb' - './spec/serializers/cluster_entity_spec.rb' - './spec/serializers/cluster_serializer_spec.rb' - './spec/serializers/clusters/kubernetes_error_entity_spec.rb' @@ -9407,7 +9070,6 @@ - './spec/services/clusters/agents/create_service_spec.rb' - './spec/services/clusters/agents/delete_expired_events_service_spec.rb' - './spec/services/clusters/agents/delete_service_spec.rb' -- './spec/services/clusters/agents/refresh_authorization_service_spec.rb' - './spec/services/clusters/agent_tokens/create_service_spec.rb' - './spec/services/clusters/agent_tokens/track_usage_service_spec.rb' - './spec/services/clusters/build_kubernetes_namespace_service_spec.rb' @@ -9685,7 +9347,6 @@ - './spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb' - './spec/services/metrics/dashboard/panel_preview_service_spec.rb' - './spec/services/metrics/dashboard/pod_dashboard_service_spec.rb' -- './spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb' - './spec/services/metrics/dashboard/system_dashboard_service_spec.rb' - './spec/services/metrics/dashboard/transient_embed_service_spec.rb' - './spec/services/metrics/dashboard/update_dashboard_service_spec.rb' @@ -9795,12 +9456,10 @@ - './spec/services/projects/alerting/notify_service_spec.rb' - './spec/services/projects/all_issues_count_service_spec.rb' - './spec/services/projects/all_merge_requests_count_service_spec.rb' -- './spec/services/projects/android_target_platform_detector_service_spec.rb' - './spec/services/projects/apple_target_platform_detector_service_spec.rb' - './spec/services/projects/autocomplete_service_spec.rb' - './spec/services/projects/auto_devops/disable_service_spec.rb' - './spec/services/projects/batch_open_issues_count_service_spec.rb' -- './spec/services/projects/blame_service_spec.rb' - './spec/services/projects/branches_by_mode_service_spec.rb' - './spec/services/projects/cleanup_service_spec.rb' - './spec/services/projects/container_repository/cleanup_tags_service_spec.rb' @@ -9911,7 +9570,6 @@ - './spec/services/security/ci_configuration/sast_parser_service_spec.rb' - './spec/services/security/ci_configuration/secret_detection_create_service_spec.rb' - './spec/services/security/merge_reports_service_spec.rb' -- './spec/services/serverless/associate_domain_service_spec.rb' - './spec/services/service_desk_settings/update_service_spec.rb' - './spec/services/service_ping/submit_service_ping_service_spec.rb' - './spec/services/service_response_spec.rb' @@ -10057,7 +9715,6 @@ - './spec/tasks/cache/clear/redis_spec.rb' - './spec/tasks/config_lint_spec.rb' - './spec/tasks/dev_rake_spec.rb' -- './spec/tasks/gettext_rake_spec.rb' - './spec/tasks/gitlab/artifacts/check_rake_spec.rb' - './spec/tasks/gitlab/artifacts/migrate_rake_spec.rb' - './spec/tasks/gitlab/background_migrations_rake_spec.rb' @@ -10106,7 +9763,7 @@ - './spec/tooling/danger/customer_success_spec.rb' - './spec/tooling/danger/datateam_spec.rb' - './spec/tooling/danger/feature_flag_spec.rb' -- './spec/tooling/danger/product_intelligence_spec.rb' +- './spec/tooling/danger/analytics_instrumentation_spec.rb' - './spec/tooling/danger/project_helper_spec.rb' - './spec/tooling/danger/sidekiq_queues_spec.rb' - './spec/tooling/danger/specs_spec.rb' @@ -10188,7 +9845,6 @@ - './spec/views/admin/application_settings/_repository_check.html.haml_spec.rb' - './spec/views/admin/application_settings/repository.html.haml_spec.rb' - './spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb' -- './spec/views/admin/broadcast_messages/index.html.haml_spec.rb' - './spec/views/admin/dashboard/index.html.haml_spec.rb' - './spec/views/admin/identities/index.html.haml_spec.rb' - './spec/views/admin/sessions/new.html.haml_spec.rb' @@ -10231,7 +9887,6 @@ - './spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb' - './spec/views/layouts/profile.html.haml_spec.rb' - './spec/views/layouts/_published_experiments.html.haml_spec.rb' -- './spec/views/layouts/_search.html.haml_spec.rb' - './spec/views/layouts/signup_onboarding.html.haml_spec.rb' - './spec/views/layouts/simple_registration.html.haml_spec.rb' - './spec/views/layouts/terms.html.haml_spec.rb' @@ -10307,7 +9962,6 @@ - './spec/views/shared/projects/_inactive_project_deletion_alert.html.haml_spec.rb' - './spec/views/shared/projects/_list.html.haml_spec.rb' - './spec/views/shared/projects/_project.html.haml_spec.rb' -- './spec/views/shared/runners/_runner_details.html.haml_spec.rb' - './spec/views/shared/snippets/_snippet.html.haml_spec.rb' - './spec/views/shared/wikis/_sidebar.html.haml_spec.rb' - './spec/workers/admin_email_worker_spec.rb' @@ -10343,7 +9997,6 @@ - './spec/workers/ci/build_schedule_worker_spec.rb' - './spec/workers/ci/build_trace_chunk_flush_worker_spec.rb' - './spec/workers/ci/cancel_pipeline_worker_spec.rb' -- './spec/workers/ci/create_cross_project_pipeline_worker_spec.rb' - './spec/workers/ci/create_downstream_pipeline_worker_spec.rb' - './spec/workers/ci/daily_build_group_report_results_worker_spec.rb' - './spec/workers/ci/delete_objects_worker_spec.rb' @@ -10381,18 +10034,14 @@ - './spec/workers/clusters/integrations/check_prometheus_health_worker_spec.rb' - './spec/workers/concerns/application_worker_spec.rb' - './spec/workers/concerns/cluster_agent_queue_spec.rb' -- './spec/workers/concerns/cluster_queue_spec.rb' - './spec/workers/concerns/cronjob_queue_spec.rb' - './spec/workers/concerns/gitlab/github_import/object_importer_spec.rb' -- './spec/workers/concerns/gitlab/github_import/queue_spec.rb' - './spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb' - './spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb' - './spec/workers/concerns/gitlab/notify_upon_death_spec.rb' - './spec/workers/concerns/limited_capacity/job_tracker_spec.rb' - './spec/workers/concerns/limited_capacity/worker_spec.rb' - './spec/workers/concerns/packages/cleanup_artifact_worker_spec.rb' -- './spec/workers/concerns/pipeline_background_queue_spec.rb' -- './spec/workers/concerns/pipeline_queue_spec.rb' - './spec/workers/concerns/project_import_options_spec.rb' - './spec/workers/concerns/reenqueuer_spec.rb' - './spec/workers/concerns/repository_check_queue_spec.rb' @@ -10422,7 +10071,6 @@ - './spec/workers/dependency_proxy/cleanup_manifest_worker_spec.rb' - './spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb' - './spec/workers/deployments/archive_in_project_worker_spec.rb' -- './spec/workers/deployments/drop_older_deployments_worker_spec.rb' - './spec/workers/deployments/hooks_worker_spec.rb' - './spec/workers/deployments/link_merge_request_worker_spec.rb' - './spec/workers/deployments/update_environment_worker_spec.rb' @@ -10475,8 +10123,6 @@ - './spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb' - './spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb' - './spec/workers/gitlab_performance_bar_stats_worker_spec.rb' -- './spec/workers/gitlab/phabricator_import/base_worker_spec.rb' -- './spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb' - './spec/workers/gitlab_service_ping_worker_spec.rb' - './spec/workers/gitlab_shell_worker_spec.rb' - './spec/workers/google_cloud/create_cloudsql_instance_worker_spec.rb' @@ -10614,8 +10260,6 @@ - './spec/workers/run_pipeline_schedule_worker_spec.rb' - './spec/workers/schedule_merge_request_cleanup_refs_worker_spec.rb' - './spec/workers/schedule_migrate_external_diffs_worker_spec.rb' -- './spec/workers/self_monitoring_project_create_worker_spec.rb' -- './spec/workers/self_monitoring_project_delete_worker_spec.rb' - './spec/workers/service_desk_email_receiver_worker_spec.rb' - './spec/workers/snippets/schedule_bulk_repository_shard_moves_worker_spec.rb' - './spec/workers/snippets/update_repository_storage_worker_spec.rb' diff --git a/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb b/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb index e8fc498cbf7..7074b073a0c 100644 --- a/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb +++ b/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb @@ -24,16 +24,18 @@ RSpec.shared_context 'bulk imports requests context' do |url| stub_request(:get, "https://gitlab.example.com/api/v4/groups?min_access_level=50&page=1&per_page=20&private_token=demo-pat&search=test&top_level_only=true") .with(headers: request_headers) - .to_return(status: 200, - body: [{ - id: 2595440, - web_url: 'https://gitlab.com/groups/test', - name: 'Test', - path: 'stub-test-group', - full_name: 'Test', - full_path: 'stub-test-group' - }].to_json, - headers: page_response_headers) + .to_return( + status: 200, + body: [{ + id: 2595440, + web_url: 'https://gitlab.com/groups/test', + name: 'Test', + path: 'stub-test-group', + full_name: 'Test', + full_path: 'stub-test-group' + }].to_json, + headers: page_response_headers + ) stub_request(:get, "%{url}/api/v4/groups?min_access_level=50&page=1&per_page=20&private_token=demo-pat&search=&top_level_only=true" % { url: url }) .to_return( @@ -45,6 +47,7 @@ RSpec.shared_context 'bulk imports requests context' do |url| full_name: 'Stub', full_path: 'stub-group' }].to_json, - headers: page_response_headers) + headers: page_response_headers + ) end end diff --git a/spec/support/shared_contexts/design_management_shared_contexts.rb b/spec/support/shared_contexts/design_management_shared_contexts.rb index d89bcada1df..32b66723c5d 100644 --- a/spec/support/shared_contexts/design_management_shared_contexts.rb +++ b/spec/support/shared_contexts/design_management_shared_contexts.rb @@ -13,24 +13,33 @@ RSpec.shared_context 'four designs in three versions' do let_it_be(:design_d) { create(:design, issue: issue) } let_it_be(:first_version) do - create(:design_version, issue: issue, - created_designs: [design_a], - modified_designs: [], - deleted_designs: []) + create( + :design_version, + issue: issue, + created_designs: [design_a], + modified_designs: [], + deleted_designs: [] + ) end let_it_be(:second_version) do - create(:design_version, issue: issue, - created_designs: [design_b, design_c, design_d], - modified_designs: [design_a], - deleted_designs: []) + create( + :design_version, + issue: issue, + created_designs: [design_b, design_c, design_d], + modified_designs: [design_a], + deleted_designs: [] + ) end let_it_be(:third_version) do - create(:design_version, issue: issue, - created_designs: [], - modified_designs: [design_a], - deleted_designs: [design_d]) + create( + :design_version, + issue: issue, + created_designs: [], + modified_designs: [design_a], + deleted_designs: [design_d] + ) end before do diff --git a/spec/support/shared_contexts/features/integrations/instance_and_group_integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/instance_and_group_integrations_shared_context.rb index 58ee341f71f..41b500c0d4d 100644 --- a/spec/support/shared_contexts/features/integrations/instance_and_group_integrations_shared_context.rb +++ b/spec/support/shared_contexts/features/integrations/instance_and_group_integrations_shared_context.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_context 'instance and group integration activation' do - include_context 'integration activation' + include_context 'with integration activation' def click_save_integration click_save_changes_button diff --git a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb index 2c92ef64815..c1f7dd79c08 100644 --- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb +++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb @@ -1,120 +1,135 @@ # frozen_string_literal: true -Integration.available_integration_names.each do |integration| - RSpec.shared_context integration do - include JiraIntegrationHelpers if integration == 'jira' +# Shared context for all integrations +# +# The following let binding should be defined: +# - `integration`: Integration name. See `Integration.available_integration_names`. +RSpec.shared_context 'with integration' do + include JiraIntegrationHelpers - let(:dashed_integration) { integration.dasherize } - let(:integration_method) { Project.integration_association_name(integration) } - let(:integration_klass) { Integration.integration_name_to_model(integration) } - let(:integration_instance) { integration_klass.new } + let(:dashed_integration) { integration.dasherize } + let(:integration_method) { Project.integration_association_name(integration) } + let(:integration_klass) { Integration.integration_name_to_model(integration) } + let(:integration_instance) { integration_klass.new } - # Build a list of all attributes that an integration supports. - let(:integration_attrs_list) do - integration_fields + integration_events + custom_attributes.fetch(integration.to_sym, []) - end + # Build a list of all attributes that an integration supports. + let(:integration_attrs_list) do + integration_fields + integration_events + custom_attributes.fetch(integration.to_sym, []) + end - # Attributes defined as fields. - let(:integration_fields) do - integration_instance.fields.map { _1[:name].to_sym } - end + # Attributes defined as fields. + let(:integration_fields) do + integration_instance.fields.map { |field| field[:name].to_sym } + end - # Attributes for configurable event triggers. - let(:integration_events) do - integration_instance.configurable_events.map { IntegrationsHelper.integration_event_field_name(_1).to_sym } - end + # Attributes for configurable event triggers. + let(:integration_events) do + integration_instance.configurable_events + .map { |event| IntegrationsHelper.integration_event_field_name(event).to_sym } + end - # Other special cases, this list might be incomplete. - # - # Some of these won't be needed anymore after we've converted them to use the field DSL - # in https://gitlab.com/gitlab-org/gitlab/-/issues/354899. - # - # Others like `comment_on_event_disabled` are actual columns on `integrations`, maybe we should migrate - # these to fields as well. - let(:custom_attributes) do - { - jira: %i[comment_on_event_enabled jira_issue_transition_automatic jira_issue_transition_id project_key - issues_enabled vulnerabilities_enabled vulnerabilities_issuetype] - } - end + # Other special cases, this list might be incomplete. + # + # Some of these won't be needed anymore after we've converted them to use the field DSL + # in https://gitlab.com/gitlab-org/gitlab/-/issues/354899. + # + # Others like `comment_on_event_disabled` are actual columns on `integrations`, maybe we should migrate + # these to fields as well. + let(:custom_attributes) do + { + jira: %i[ + comment_on_event_enabled jira_issue_transition_automatic jira_issue_transition_id project_key + issues_enabled vulnerabilities_enabled vulnerabilities_issuetype + ] + } + end - let(:integration_attrs) do - integration_attrs_list.inject({}) do |hash, k| - if k =~ /^(token*|.*_token|.*_key)/ && k =~ /^[^app_store]/ - hash.merge!(k => 'secrettoken') - elsif integration == 'confluence' && k == :confluence_url - hash.merge!(k => 'https://example.atlassian.net/wiki') - elsif integration == 'datadog' && k == :datadog_site - hash.merge!(k => 'datadoghq.com') - elsif integration == 'datadog' && k == :datadog_tags - hash.merge!(k => 'key:value') - elsif integration == 'packagist' && k == :server - hash.merge!(k => 'https://packagist.example.com') - elsif k =~ /^(.*_url|url|webhook)/ - hash.merge!(k => "http://example.com") - elsif integration_klass.method_defined?("#{k}?") - hash.merge!(k => true) - elsif integration == 'irker' && k == :recipients - hash.merge!(k => 'irc://irc.network.net:666/#channel') - elsif integration == 'irker' && k == :server_port - hash.merge!(k => 1234) - elsif integration == 'jira' && k == :jira_issue_transition_id - hash.merge!(k => '1,2,3') - elsif integration == 'jira' && k == :jira_issue_transition_automatic - hash.merge!(k => true) - elsif integration == 'emails_on_push' && k == :recipients - hash.merge!(k => 'foo@bar.com') - elsif (integration == 'slack' || integration == 'mattermost') && k == :labels_to_be_notified_behavior - hash.merge!(k => "match_any") - elsif integration == 'campfire' && k == :room - hash.merge!(k => '1234') - elsif integration == 'apple_app_store' && k == :app_store_issuer_id - hash.merge!(k => 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee') - elsif integration == 'apple_app_store' && k == :app_store_private_key - hash.merge!(k => File.read('spec/fixtures/ssl_key.pem')) - elsif integration == 'apple_app_store' && k == :app_store_key_id - hash.merge!(k => 'ABC1') - else - hash.merge!(k => "someword") - end + let(:integration_attrs) do + integration_attrs_list.inject({}) do |hash, k| + if k =~ /^(token*|.*_token|.*_key)/ && !integration.in?(%w[apple_app_store google_play]) + hash.merge!(k => 'secrettoken') + elsif integration == 'confluence' && k == :confluence_url + hash.merge!(k => 'https://example.atlassian.net/wiki') + elsif integration == 'datadog' && k == :datadog_site + hash.merge!(k => 'datadoghq.com') + elsif integration == 'datadog' && k == :datadog_tags + hash.merge!(k => 'key:value') + elsif integration == 'packagist' && k == :server + hash.merge!(k => 'https://packagist.example.com') + elsif k =~ /^(.*_url|url|webhook)/ + hash.merge!(k => "http://example.com") + elsif integration_klass.method_defined?("#{k}?") + hash.merge!(k => true) + elsif integration == 'irker' && k == :recipients + hash.merge!(k => 'irc://irc.network.net:666/#channel') + elsif integration == 'irker' && k == :server_port + hash.merge!(k => 1234) + elsif integration == 'jira' && k == :jira_issue_transition_id + hash.merge!(k => '1,2,3') + elsif integration == 'jira' && k == :jira_issue_transition_automatic # rubocop:disable Lint/DuplicateBranch + hash.merge!(k => true) + elsif integration == 'jira' && k == :jira_auth_type # rubocop:disable Lint/DuplicateBranch + hash.merge!(k => 0) + elsif integration == 'emails_on_push' && k == :recipients + hash.merge!(k => 'foo@bar.com') + elsif (integration == 'slack' || integration == 'mattermost') && k == :labels_to_be_notified_behavior + hash.merge!(k => "match_any") + elsif integration == 'campfire' && k == :room + hash.merge!(k => '1234') + elsif integration == 'apple_app_store' && k == :app_store_issuer_id + hash.merge!(k => 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee') + elsif integration == 'apple_app_store' && k == :app_store_private_key + hash.merge!(k => File.read('spec/fixtures/ssl_key.pem')) + elsif integration == 'apple_app_store' && k == :app_store_key_id + hash.merge!(k => 'ABC1') + elsif integration == 'apple_app_store' && k == :app_store_private_key_file_name + hash.merge!(k => 'ssl_key.pem') + elsif integration == 'google_play' && k == :package_name + hash.merge!(k => 'com.gitlab.foo.bar') + elsif integration == 'google_play' && k == :service_account_key + hash.merge!(k => File.read('spec/fixtures/service_account.json')) + elsif integration == 'google_play' && k == :service_account_key_file_name + hash.merge!(k => 'service_account.json') + else + hash.merge!(k => "someword") end end + end - let(:licensed_features) do - { - 'github' => :github_integration - } - end + let(:licensed_features) do + { + 'github' => :github_integration + } + end - before do - enable_license_for_integration(integration) - stub_jira_integration_test if integration == 'jira' - end + before do + enable_license_for_integration(integration) + stub_jira_integration_test if integration == 'jira' + end - def initialize_integration(integration, attrs = {}) - record = project.find_or_initialize_integration(integration) - record.reset_updated_properties if integration == 'datadog' - record.attributes = attrs - record.properties = integration_attrs - record.save! - record - end + def initialize_integration(integration, attrs = {}) + record = project.find_or_initialize_integration(integration) + record.reset_updated_properties if integration == 'datadog' + record.attributes = attrs + record.properties = integration_attrs + record.save! + record + end - private + private - def enable_license_for_integration(integration) - return unless respond_to?(:stub_licensed_features) + def enable_license_for_integration(integration) + return unless respond_to?(:stub_licensed_features) - licensed_feature = licensed_features[integration] - return unless licensed_feature + licensed_feature = licensed_features[integration] + return unless licensed_feature - stub_licensed_features(licensed_feature => true) - project.clear_memoization(:disabled_integrations) - end + stub_licensed_features(licensed_feature => true) + project.clear_memoization(:disabled_integrations) end end -RSpec.shared_context 'integration activation' do +RSpec.shared_context 'with integration activation' do def click_active_checkbox find('label', text: 'Active').click end diff --git a/spec/support/shared_contexts/features/integrations/project_integrations_jira_context.rb b/spec/support/shared_contexts/features/integrations/project_integrations_jira_context.rb index fadd46a7e12..f16d19e5858 100644 --- a/spec/support/shared_contexts/features/integrations/project_integrations_jira_context.rb +++ b/spec/support/shared_contexts/features/integrations/project_integrations_jira_context.rb @@ -10,5 +10,6 @@ RSpec.shared_context 'project integration Jira context' do fill_in 'service_url', with: url fill_in 'service_username', with: 'username' fill_in 'service_password', with: 'password' + select('Basic', from: 'service_jira_auth_type') end end diff --git a/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb index bac7bd00f46..a9b9a5246e6 100644 --- a/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb +++ b/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_context 'project integration activation' do - include_context 'integration activation' + include_context 'with integration activation' let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } diff --git a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb index afb3976e3b8..16d23f63fd0 100644 --- a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb +++ b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb @@ -12,47 +12,55 @@ RSpec.shared_context 'IssuesFinder context' do let_it_be(:milestone) { create(:milestone, project: project1, releases: [release]) } let_it_be(:label) { create(:label, project: project2) } let_it_be(:label2) { create(:label, project: project2) } - let_it_be(:item1, reload: true) do - create(:issue, - author: user, - assignees: [user], - project: project1, - milestone: milestone, - title: 'gitlab', - created_at: 1.week.ago, - updated_at: 1.week.ago) + let_it_be_with_reload(:item1) do + create( + :issue, + author: user, + assignees: [user], + project: project1, + milestone: milestone, + title: 'gitlab', + created_at: 1.week.ago, + updated_at: 1.week.ago + ) end - let_it_be(:item2, reload: true) do - create(:issue, - author: user, - assignees: [user], - project: project2, - description: 'gitlab', - created_at: 1.week.from_now, - updated_at: 1.week.from_now) + let_it_be_with_reload(:item2) do + create( + :issue, + author: user, + assignees: [user], + project: project2, + description: 'gitlab', + created_at: 1.week.from_now, + updated_at: 1.week.from_now + ) end - let_it_be(:item3, reload: true) do - create(:issue, - author: user2, - assignees: [user2], - project: project2, - title: 'tanuki', - description: 'tanuki', - created_at: 2.weeks.from_now, - updated_at: 2.weeks.from_now) + let_it_be_with_reload(:item3) do + create( + :issue, + author: user2, + assignees: [user2], + project: project2, + title: 'tanuki', + description: 'tanuki', + created_at: 2.weeks.from_now, + updated_at: 2.weeks.from_now + ) end - let_it_be(:item4, reload: true) { create(:issue, project: project3) } - let_it_be(:item5, reload: true) do - create(:issue, - author: user, - assignees: [user], - project: project1, - title: 'wotnot', - created_at: 3.days.ago, - updated_at: 3.days.ago) + let_it_be_with_reload(:item4) { create(:issue, project: project3) } + let_it_be_with_reload(:item5) do + create( + :issue, + author: user, + assignees: [user], + project: project1, + title: 'wotnot', + created_at: 3.days.ago, + updated_at: 3.days.ago + ) end let_it_be(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: item1) } diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb index 8a64efe9df5..507bcd44ee8 100644 --- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb +++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb @@ -54,34 +54,44 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests let_it_be(:label2) { create(:label, project: project1) } let!(:merge_request1) do - create(:merge_request, assignees: [user], author: user, reviewers: [user2], - source_project: project2, target_project: project1, - target_branch: 'merged-target') + create( + :merge_request, assignees: [user], author: user, reviewers: [user2], + source_project: project2, target_project: project1, + target_branch: 'merged-target' + ) end let!(:merge_request2) do - create(:merge_request, :conflict, assignees: [user], author: user, reviewers: [user2], - source_project: project2, target_project: project1, - state: 'closed') + create( + :merge_request, :conflict, assignees: [user], author: user, reviewers: [user2], + source_project: project2, target_project: project1, + state: 'closed' + ) end let!(:merge_request3) do - create(:merge_request, :simple, author: user, assignees: [user2], reviewers: [user], - source_project: project2, target_project: project2, - state: 'locked', - title: 'thing WIP thing') + create( + :merge_request, :simple, author: user, assignees: [user2], reviewers: [user], + source_project: project2, target_project: project2, + state: 'locked', + title: 'thing WIP thing' + ) end let!(:merge_request4) do - create(:merge_request, :simple, author: user, - source_project: project3, target_project: project3, - title: 'WIP thing') + create( + :merge_request, :simple, author: user, + source_project: project3, target_project: project3, + title: 'WIP thing' + ) end let_it_be(:merge_request5) do - create(:merge_request, :simple, author: user, - source_project: project4, target_project: project4, - title: '[WIP]') + create( + :merge_request, :simple, author: user, + source_project: project4, target_project: project4, + title: '[WIP]' + ) end let!(:label_link) { create(:label_link, label: label, target: merge_request2) } diff --git a/spec/support/shared_contexts/finders/work_items_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/work_items_finder_shared_contexts.rb index 8c5bc339db5..1118039d164 100644 --- a/spec/support/shared_contexts/finders/work_items_finder_shared_contexts.rb +++ b/spec/support/shared_contexts/finders/work_items_finder_shared_contexts.rb @@ -12,47 +12,55 @@ RSpec.shared_context 'WorkItemsFinder context' do let_it_be(:milestone) { create(:milestone, project: project1, releases: [release]) } let_it_be(:label) { create(:label, project: project2) } let_it_be(:label2) { create(:label, project: project2) } - let_it_be(:item1, reload: true) do - create(:work_item, - author: user, - assignees: [user], - project: project1, - milestone: milestone, - title: 'gitlab', - created_at: 1.week.ago, - updated_at: 1.week.ago) + let_it_be_with_reload(:item1) do + create( + :work_item, + author: user, + assignees: [user], + project: project1, + milestone: milestone, + title: 'gitlab', + created_at: 1.week.ago, + updated_at: 1.week.ago + ) end - let_it_be(:item2, reload: true) do - create(:work_item, - author: user, - assignees: [user], - project: project2, - description: 'gitlab', - created_at: 1.week.from_now, - updated_at: 1.week.from_now) + let_it_be_with_reload(:item2) do + create( + :work_item, + author: user, + assignees: [user], + project: project2, + description: 'gitlab', + created_at: 1.week.from_now, + updated_at: 1.week.from_now + ) end - let_it_be(:item3, reload: true) do - create(:work_item, - author: user2, - assignees: [user2], - project: project2, - title: 'tanuki', - description: 'tanuki', - created_at: 2.weeks.from_now, - updated_at: 2.weeks.from_now) + let_it_be_with_reload(:item3) do + create( + :work_item, + author: user2, + assignees: [user2], + project: project2, + title: 'tanuki', + description: 'tanuki', + created_at: 2.weeks.from_now, + updated_at: 2.weeks.from_now + ) end - let_it_be(:item4, reload: true) { create(:work_item, project: project3) } - let_it_be(:item5, reload: true) do - create(:work_item, - author: user, - assignees: [user], - project: project1, - title: 'wotnot', - created_at: 3.days.ago, - updated_at: 3.days.ago) + let_it_be_with_reload(:item4) { create(:work_item, project: project3) } + let_it_be_with_reload(:item5) do + create( + :work_item, + author: user, + assignees: [user], + project: project1, + title: 'wotnot', + created_at: 3.days.ago, + updated_at: 3.days.ago + ) end let_it_be(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: item1) } diff --git a/spec/support/shared_contexts/glfm/example_snapshot_fixtures.rb b/spec/support/shared_contexts/glfm/example_snapshot_fixtures.rb index 22b401bc841..83bf622af67 100644 --- a/spec/support/shared_contexts/glfm/example_snapshot_fixtures.rb +++ b/spec/support/shared_contexts/glfm/example_snapshot_fixtures.rb @@ -8,7 +8,7 @@ RSpec.shared_context 'with GLFM example snapshot fixtures' do # NOTE: We hardcode the IDs on all fixtures to prevent variability in the # rendered HTML/Prosemirror JSON, and to minimize the need for normalization: # https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#normalization - create(:project, :repository, creator: user, group: group, name: 'glfm_project', id: 77777) + create(:project, :repository, creator: user, group: group, path: 'glfm_project', id: 77777) end let_it_be(:project_snippet) { create(:project_snippet, title: 'glfm_project_snippet', id: 88888, project: project) } diff --git a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb index 1585ef0e7fc..095c8639d15 100644 --- a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb +++ b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb @@ -7,6 +7,7 @@ RSpec.shared_context 'with FOSS query type fields' do :board_list, :ci_application_settings, :ci_config, + :ci_pipeline_stage, :ci_variables, :container_repository, :current_user, diff --git a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb index b9cde12c537..35c1511c96a 100644 --- a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb +++ b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_context 'merge request show action' do - include Spec::Support::Helpers::Features::MergeRequestHelpers + include Features::MergeRequestHelpers let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public, :repository) } diff --git a/spec/support/shared_contexts/lib/gitlab/database/load_balancing/wal_tracking_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/database/load_balancing/wal_tracking_shared_context.rb new file mode 100644 index 00000000000..cbbd3754108 --- /dev/null +++ b/spec/support/shared_contexts/lib/gitlab/database/load_balancing/wal_tracking_shared_context.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +RSpec.shared_context 'when tracking WAL location reference' do + let(:current_location) { '0/D525E3A8' } + + around do |example| + Gitlab::Database::LoadBalancing::Session.clear_session + example.run + Gitlab::Database::LoadBalancing::Session.clear_session + end + + def expect_tracked_locations_when_replicas_available + {}.tap do |locations| + Gitlab::Database::LoadBalancing.each_load_balancer do |lb| + expect(lb.host).to receive(:database_replica_location).and_return(current_location) + + locations[lb.name] = current_location + end + end + end + + def expect_tracked_locations_when_no_replicas_available + {}.tap do |locations| + Gitlab::Database::LoadBalancing.each_load_balancer do |lb| + expect(lb).to receive(:host).at_least(:once).and_return(nil) + expect(lb).to receive(:primary_write_location).and_return(current_location) + + locations[lb.name] = current_location + end + end + end + + def expect_tracked_locations_from_primary_only + {}.tap do |locations| + Gitlab::Database::LoadBalancing.each_load_balancer do |lb| + expect(lb).to receive(:primary_write_location).and_return(current_location) + + locations[lb.name] = current_location + end + end + end + + def stub_load_balancing_disabled! + Gitlab::Database::LoadBalancing.each_load_balancer do |lb| + allow(lb).to receive(:primary_only?).and_return(true) + end + end + + def stub_load_balancing_enabled! + Gitlab::Database::LoadBalancing.each_load_balancer do |lb| + allow(lb).to receive(:primary_only?).and_return(false) + end + end + + def stub_no_writes_performed! + allow(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_primary?).and_return(false) + end + + def stub_write_performed! + allow(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_primary?).and_return(true) + end + + def stub_replica_available!(available) + ::Gitlab::Database::LoadBalancing.each_load_balancer do |lb| + allow(lb).to receive(:select_up_to_date_host).with(current_location).and_return(available) + end + end +end diff --git a/spec/support/shared_contexts/lib/gitlab/database/partitioning/list_partitioning_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/database/partitioning/list_partitioning_shared_context.rb new file mode 100644 index 00000000000..e9cd1bdbbf5 --- /dev/null +++ b/spec/support/shared_contexts/lib/gitlab/database/partitioning/list_partitioning_shared_context.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +RSpec.shared_context 'with a table structure for converting a table to a list partition' do + let(:migration_context) do + Gitlab::Database::Migration[2.1].new.tap do |migration| + migration.extend Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers + migration.extend Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers + end + end + + let(:connection) { migration_context.connection } + let(:table_name) { '_test_table_to_partition' } + let(:table_identifier) { "#{connection.current_schema}.#{table_name}" } + let(:partitioning_column) { :partition_number } + let(:partitioning_default) { 1 } + let(:referenced_table_name) { '_test_referenced_table' } + let(:other_referenced_table_name) { '_test_other_referenced_table' } + let(:referencing_table_name) { '_test_referencing_table' } + let(:other_referencing_table_name) { '_test_other_referencing_table' } + let(:parent_table_name) { "#{table_name}_parent" } + let(:parent_table_identifier) { "#{connection.current_schema}.#{parent_table_name}" } + let(:lock_tables) { [] } + + let(:model) { define_batchable_model(table_name, connection: connection) } + + let(:parent_model) { define_batchable_model(parent_table_name, connection: connection) } + let(:referencing_model) { define_batchable_model(referencing_table_name, connection: connection) } + + before do + # Suppress printing migration progress + allow(migration_context).to receive(:puts) + allow(migration_context.connection).to receive(:transaction_open?).and_return(false) + + connection.execute(<<~SQL) + create table #{referenced_table_name} ( + id bigserial primary key not null + ) + SQL + + connection.execute(<<~SQL) + create table #{other_referenced_table_name} ( + id bigserial primary key not null + ) + SQL + + connection.execute(<<~SQL) + insert into #{referenced_table_name} default values; + insert into #{other_referenced_table_name} default values; + SQL + + connection.execute(<<~SQL) + create table #{table_name} ( + id bigserial not null, + #{partitioning_column} bigint not null default #{partitioning_default}, + referenced_id bigint not null references #{referenced_table_name} (id) on delete cascade, + other_referenced_id bigint not null references #{other_referenced_table_name} (id) on delete set null, + primary key (id, #{partitioning_column}) + ) + SQL + + connection.execute(<<~SQL) + create table #{referencing_table_name} ( + id bigserial primary key not null, + #{partitioning_column} bigint not null, + ref_id bigint not null, + constraint fk_referencing foreign key (#{partitioning_column}, ref_id) references #{table_name} (#{partitioning_column}, id) on delete cascade + ) + SQL + + connection.execute(<<~SQL) + create table #{other_referencing_table_name} ( + id bigserial not null, + #{partitioning_column} bigint not null, + ref_id bigint not null, + primary key (#{partitioning_column}, id), + constraint fk_referencing_other foreign key (#{partitioning_column}, ref_id) references #{table_name} (#{partitioning_column}, id) + ) partition by hash(#{partitioning_column}); + + create table #{other_referencing_table_name}_1 + partition of #{other_referencing_table_name} for values with (modulus 2, remainder 0); + + create table #{other_referencing_table_name}_2 + partition of #{other_referencing_table_name} for values with (modulus 2, remainder 1); + SQL + + connection.execute(<<~SQL) + insert into #{table_name} (referenced_id, other_referenced_id) + select #{referenced_table_name}.id, #{other_referenced_table_name}.id + from #{referenced_table_name}, #{other_referenced_table_name}; + SQL + end +end diff --git a/spec/support/shared_contexts/merge_request_create_shared_context.rb b/spec/support/shared_contexts/merge_request_create_shared_context.rb index f2defa4eab9..fc9a3767365 100644 --- a/spec/support/shared_contexts/merge_request_create_shared_context.rb +++ b/spec/support/shared_contexts/merge_request_create_shared_context.rb @@ -15,12 +15,13 @@ RSpec.shared_context 'merge request create context' do target_project.add_maintainer(user2) sign_in(user) - visit project_new_merge_request_path(target_project, - merge_request: { - source_project_id: source_project.id, - target_project_id: target_project.id, - source_branch: 'fix', - target_branch: 'master' - }) + visit project_new_merge_request_path( + target_project, + merge_request: { + source_project_id: source_project.id, + target_project_id: target_project.id, + source_branch: 'fix', + target_branch: 'master' + }) end end diff --git a/spec/support/shared_contexts/merge_request_edit_shared_context.rb b/spec/support/shared_contexts/merge_request_edit_shared_context.rb index d490d26adfb..f0e89b0c5f9 100644 --- a/spec/support/shared_contexts/merge_request_edit_shared_context.rb +++ b/spec/support/shared_contexts/merge_request_edit_shared_context.rb @@ -9,11 +9,13 @@ RSpec.shared_context 'merge request edit context' do let(:target_project) { create(:project, :public, :repository) } let(:source_project) { target_project } let(:merge_request) do - create(:merge_request, - source_project: source_project, - target_project: target_project, - source_branch: 'fix', - target_branch: 'master') + create( + :merge_request, + source_project: source_project, + target_project: target_project, + source_branch: 'fix', + target_branch: 'master' + ) end before do diff --git a/spec/support/shared_contexts/merge_requests_allowing_collaboration_shared_context.rb b/spec/support/shared_contexts/merge_requests_allowing_collaboration_shared_context.rb index 5412a991b22..50761f94c10 100644 --- a/spec/support/shared_contexts/merge_requests_allowing_collaboration_shared_context.rb +++ b/spec/support/shared_contexts/merge_requests_allowing_collaboration_shared_context.rb @@ -8,10 +8,12 @@ RSpec.shared_context 'merge request allowing collaboration' do before do canonical.add_maintainer(user) - create(:merge_request, - target_project: canonical, - source_project: forked_project, - source_branch: 'feature', - allow_collaboration: true) + create( + :merge_request, + target_project: canonical, + source_project: forked_project, + source_branch: 'feature', + allow_collaboration: true + ) end end diff --git a/spec/support/shared_contexts/models/distribution_shared_context.rb b/spec/support/shared_contexts/models/distribution_shared_context.rb new file mode 100644 index 00000000000..30f6b750e22 --- /dev/null +++ b/spec/support/shared_contexts/models/distribution_shared_context.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# This shared context requires: +# - factory: either :debian_project_distribution or :debian_group_distribution +# - can_freeze: whether to freeze the created object or not +RSpec.shared_context 'for Debian Distribution' do |factory, can_freeze| + let_it_be(:distribution_with_suite, freeze: can_freeze) { create(factory, :with_suite) } + let_it_be(:distribution_with_same_container, freeze: can_freeze) do + create(factory, container: distribution_with_suite.container) + end + + let_it_be(:distribution_with_same_codename, freeze: can_freeze) do + create(factory, codename: distribution_with_suite.codename) + end + + let_it_be(:distribution_with_same_suite, freeze: can_freeze) { create(factory, suite: distribution_with_suite.suite) } + let_it_be(:distribution_with_codename_and_suite_flipped, freeze: can_freeze) do + create(factory, codename: distribution_with_suite.suite, suite: distribution_with_suite.codename) + end + + let_it_be_with_refind(:distribution) { create(factory, container: distribution_with_suite.container) } +end diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb index b74819d2ac7..7b839594816 100644 --- a/spec/support/shared_contexts/navbar_structure_context.rb +++ b/spec/support/shared_contexts/navbar_structure_context.rb @@ -5,10 +5,10 @@ RSpec.shared_context 'project navbar structure' do let(:security_and_compliance_nav_item) do { - nav_item: _('Security & Compliance'), + nav_item: _('Security and Compliance'), nav_sub_items: [ (_('Audit events') if Gitlab.ee?), - _('Configuration') + _('Security configuration') ] } end @@ -34,10 +34,10 @@ RSpec.shared_context 'project navbar structure' do _('Commits'), _('Branches'), _('Tags'), - _('Contributors'), + _('Contributor statistics'), _('Graph'), - _('Compare'), - (_('Locked Files') if Gitlab.ee?) + _('Compare revisions'), + (_('Locked files') if Gitlab.ee?) ] }, { @@ -68,7 +68,7 @@ RSpec.shared_context 'project navbar structure' do nav_item: _('Deployments'), nav_sub_items: [ _('Environments'), - _('Feature Flags'), + s_('FeatureFlags|Feature flags'), _('Releases') ] }, @@ -76,7 +76,7 @@ RSpec.shared_context 'project navbar structure' do nav_item: _('Infrastructure'), nav_sub_items: [ _('Kubernetes clusters'), - _('Terraform') + s_('Terraform|Terraform states') ] }, { @@ -85,8 +85,7 @@ RSpec.shared_context 'project navbar structure' do _('Metrics'), _('Error Tracking'), _('Alerts'), - _('Incidents'), - _('Airflow') + _('Incidents') ] }, { @@ -141,6 +140,7 @@ RSpec.shared_context 'group navbar structure' do _('CI/CD'), _('Applications'), _('Packages and registries'), + s_('UsageQuota|Usage Quotas'), _('Domain Verification') ] } @@ -155,18 +155,9 @@ RSpec.shared_context 'group navbar structure' do } end - let(:administration_nav_item) do - { - nav_item: _('Administration'), - nav_sub_items: [ - s_('UsageQuota|Usage Quotas') - ] - } - end - let(:security_and_compliance_nav_item) do { - nav_item: _('Security & Compliance'), + nav_item: _('Security and Compliance'), nav_sub_items: [ _('Audit events') ] @@ -268,3 +259,30 @@ RSpec.shared_context 'dashboard navbar structure' do ] end end + +RSpec.shared_context '"Explore" navbar structure' do + let(:structure) do + [ + { + nav_item: "Explore", + nav_sub_items: [] + }, + { + nav_item: _("Projects"), + nav_sub_items: [] + }, + { + nav_item: _("Groups"), + nav_sub_items: [] + }, + { + nav_item: _("Topics"), + nav_sub_items: [] + }, + { + nav_item: _("Snippets"), + nav_sub_items: [] + } + ] + 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 4c081c8464e..111fd3dc7df 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -12,7 +12,7 @@ RSpec.shared_context 'GroupPolicy context' do let(:public_permissions) do %i[ - read_group read_counts read_achievement + read_group read_counts read_label read_issue_board_list read_milestone read_issue_board ] end @@ -42,9 +42,7 @@ RSpec.shared_context 'GroupPolicy context' do let(:developer_permissions) do %i[ - create_metrics_dashboard_annotation - delete_metrics_dashboard_annotation - update_metrics_dashboard_annotation + admin_metrics_dashboard_annotation create_custom_emoji create_package read_cluster @@ -59,6 +57,7 @@ RSpec.shared_context 'GroupPolicy context' do create_cluster update_cluster admin_cluster add_cluster destroy_upload admin_achievement + award_achievement ] end diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb index afc7fc8766f..5014a810f35 100644 --- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb @@ -38,8 +38,8 @@ RSpec.shared_context 'ProjectPolicy context' do read_commit_status read_confidential_issues read_container_image read_harbor_registry read_deployment read_environment read_merge_request read_metrics_dashboard_annotation read_pipeline read_prometheus - read_sentry_issue update_issue create_merge_request_in read_external_emails - read_internal_note + read_sentry_issue update_issue create_merge_request_in + read_external_emails read_internal_note export_work_items ] end @@ -52,12 +52,11 @@ RSpec.shared_context 'ProjectPolicy context' do admin_merge_request admin_tag create_build create_commit_status create_container_image create_deployment create_environment create_merge_request_from - create_metrics_dashboard_annotation create_pipeline create_release - create_wiki delete_metrics_dashboard_annotation - destroy_container_image push_code read_pod_logs read_terraform_state - resolve_note update_build update_commit_status update_container_image - update_deployment update_environment update_merge_request - update_metrics_dashboard_annotation update_pipeline update_release destroy_release + admin_metrics_dashboard_annotation create_pipeline create_release + create_wiki destroy_container_image push_code read_pod_logs + read_terraform_state resolve_note update_build update_commit_status + update_container_image update_deployment update_environment + update_merge_request update_pipeline update_release destroy_release read_resource_group update_resource_group update_escalation_status ] end diff --git a/spec/support/shared_contexts/rack_attack_shared_context.rb b/spec/support/shared_contexts/rack_attack_shared_context.rb index 12625ead72b..4e8c6e9dec6 100644 --- a/spec/support/shared_contexts/rack_attack_shared_context.rb +++ b/spec/support/shared_contexts/rack_attack_shared_context.rb @@ -5,8 +5,7 @@ RSpec.shared_context 'rack attack cache store' do # Instead of test environment's :null_store so the throttles can increment Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new - # Make time-dependent tests deterministic - freeze_time { example.run } + example.run Rack::Attack.cache.store = Rails.cache end diff --git a/spec/support/shared_contexts/requests/api/debian_repository_shared_context.rb b/spec/support/shared_contexts/requests/api/debian_repository_shared_context.rb index 57967fb9414..ad64e4d5be5 100644 --- a/spec/support/shared_contexts/requests/api/debian_repository_shared_context.rb +++ b/spec/support/shared_contexts/requests/api/debian_repository_shared_context.rb @@ -58,6 +58,9 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_ let(:distribution) { { private: private_distribution, public: public_distribution }[visibility_level] } let(:architecture) { { private: private_architecture, public: public_architecture }[visibility_level] } let(:component) { { private: private_component, public: public_component }[visibility_level] } + let(:component_file) { { private: private_component_file, public: public_component_file }[visibility_level] } + let(:component_file_sources) { { private: private_component_file_sources, public: public_component_file_sources }[visibility_level] } + let(:component_file_di) { { private: private_component_file_di, public: public_component_file_di }[visibility_level] } let(:component_file_older_sha256) { { private: private_component_file_older_sha256, public: public_component_file_older_sha256 }[visibility_level] } let(:component_file_sources_older_sha256) { { private: private_component_file_sources_older_sha256, public: public_component_file_sources_older_sha256 }[visibility_level] } let(:component_file_di_older_sha256) { { private: private_component_file_di_older_sha256, public: public_component_file_di_older_sha256 }[visibility_level] } diff --git a/spec/support/shared_contexts/requests/api/graphql/releases_and_group_releases_shared_context.rb b/spec/support/shared_contexts/requests/api/graphql/releases_and_group_releases_shared_context.rb index 81076ea6fdc..e56cd82e369 100644 --- a/spec/support/shared_contexts/requests/api/graphql/releases_and_group_releases_shared_context.rb +++ b/spec/support/shared_contexts/requests/api/graphql/releases_and_group_releases_shared_context.rb @@ -13,40 +13,40 @@ RSpec.shared_context 'when releases and group releases shared context' do let(:query) do graphql_query_for(resource_type, { fullPath: resource.full_path }, - %( - releases { - count - nodes { - tagName - tagPath - name - commit { - sha - } - assets { - count - sources { + %( + releases { + count + nodes { + tagName + tagPath + name + commit { + sha + } + assets { + count + sources { + nodes { + url + } + } + } + evidences { nodes { - url + sha } } - } - evidences { - nodes { - sha + links { + selfUrl + openedMergeRequestsUrl + mergedMergeRequestsUrl + closedMergeRequestsUrl + openedIssuesUrl + closedIssuesUrl } } - links { - selfUrl - openedMergeRequestsUrl - mergedMergeRequestsUrl - closedMergeRequestsUrl - openedIssuesUrl - closedIssuesUrl - } } - } - )) + )) end let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } } diff --git a/spec/support/shared_contexts/security_and_compliance_permissions_shared_context.rb b/spec/support/shared_contexts/security_and_compliance_permissions_shared_context.rb index dc5195e4b01..1fa1a5c69f4 100644 --- a/spec/support/shared_contexts/security_and_compliance_permissions_shared_context.rb +++ b/spec/support/shared_contexts/security_and_compliance_permissions_shared_context.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_context '"Security & Compliance" permissions' do +RSpec.shared_context '"Security and Compliance" permissions' do let(:project_instance) { an_instance_of(Project) } let(:user_instance) { an_instance_of(User) } let(:before_request_defined) { false } @@ -18,7 +18,7 @@ RSpec.shared_context '"Security & Compliance" permissions' do allow(Ability).to receive(:allowed?).with(user_instance, :access_security_and_compliance, project_instance).and_return(true) end - context 'when the "Security & Compliance" feature is disabled' do + context 'when the "Security and Compliance" feature is disabled' do subject { response } before do diff --git a/spec/support/shared_contexts/services/clusters/create_service_shared_context.rb b/spec/support/shared_contexts/services/clusters/create_service_shared_context.rb new file mode 100644 index 00000000000..393e90da1d3 --- /dev/null +++ b/spec/support/shared_contexts/services/clusters/create_service_shared_context.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +RSpec.shared_context 'with valid cluster create params' do + let(:clusterable) { Clusters::Instance.new } + let(:params) do + { + name: 'test-cluster', + provider_type: :gcp, + provider_gcp_attributes: { + gcp_project_id: 'gcp-project', + zone: 'us-central1-a', + num_nodes: 1, + machine_type: 'machine_type-a', + legacy_abac: 'true' + }, + clusterable: clusterable + } + 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 7db479bcfd2..0cf026749ee 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 @@ -8,9 +8,11 @@ RSpec.shared_context 'container repository delete tags service shared context' d let(:params) { { tags: tags } } before do - stub_container_registry_config(enabled: true, - api_url: 'http://registry.gitlab', - host_port: 'registry.gitlab') + stub_container_registry_config( + enabled: true, + api_url: 'http://registry.gitlab', + host_port: 'registry.gitlab' + ) stub_container_registry_tags( repository: repository.path, diff --git a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb index 9746d287440..cd792ccc4e3 100644 --- a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb +++ b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb @@ -4,9 +4,10 @@ RSpec.shared_context 'stubbed service ping metrics definitions' do include UsageDataHelpers let(:metrics_definitions) { standard_metrics + subscription_metrics + operational_metrics + optional_metrics } + # ToDo: remove during https://gitlab.com/gitlab-org/gitlab/-/issues/396824 (license metrics migration) + let(:subscription_metrics) { [] } let(:standard_metrics) do [ - metric_attributes('uuid', 'standard'), metric_attributes('recorded_at', 'standard'), metric_attributes('settings.collected_data_categories', 'standard', 'object') ] diff --git a/spec/support/shared_examples/analytics/cycle_analytics/flow_metrics_examples.rb b/spec/support/shared_examples/analytics/cycle_analytics/flow_metrics_examples.rb new file mode 100644 index 00000000000..9c096c5a158 --- /dev/null +++ b/spec/support/shared_examples/analytics/cycle_analytics/flow_metrics_examples.rb @@ -0,0 +1,629 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'validation on Time arguments' do + context 'when `to` parameter is higher than `from`' do + let(:variables) do + { + path: full_path, + from: 1.day.ago.iso8601, + to: 2.days.ago.iso8601 + } + end + + it 'returns error' do + expect(result).to be_nil + expect(graphql_errors.first['message']).to include('`from` argument must be before `to` argument') + end + end + + context 'when from and to parameter range is higher than 180 days' do + let(:variables) do + { + path: full_path, + from: Time.now, + to: 181.days.from_now + } + end + + it 'returns error' do + expect(result).to be_nil + expect(graphql_errors.first['message']).to include('Max of 180 days timespan is allowed') + end + end +end + +RSpec.shared_examples 'value stream analytics flow metrics issueCount examples' do + let_it_be(:milestone) { create(:milestone, group: group) } + let_it_be(:label) { create(:group_label, group: group) } + + let_it_be(:author) { create(:user) } + let_it_be(:assignee) { create(:user) } + + let_it_be(:issue1) { create(:issue, project: project1, author: author, created_at: 12.days.ago) } + let_it_be(:issue2) { create(:issue, project: project2, author: author, created_at: 13.days.ago) } + + let_it_be(:issue3) do + create(:labeled_issue, + project: project1, + labels: [label], + author: author, + milestone: milestone, + assignees: [assignee], + created_at: 14.days.ago) + end + + let_it_be(:issue4) do + create(:labeled_issue, + project: project2, + labels: [label], + assignees: [assignee], + created_at: 15.days.ago) + end + + let_it_be(:issue_outside_of_the_range) { create(:issue, project: project2, author: author, created_at: 50.days.ago) } + + let(:query) do + <<~QUERY + query($path: ID!, $assigneeUsernames: [String!], $authorUsername: String, $milestoneTitle: String, $labelNames: [String!], $from: Time!, $to: Time!) { + #{context}(fullPath: $path) { + flowMetrics { + issueCount(assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, milestoneTitle: $milestoneTitle, labelNames: $labelNames, from: $from, to: $to) { + value + unit + identifier + title + } + } + } + } + QUERY + end + + let(:variables) do + { + path: full_path, + from: 20.days.ago.iso8601, + to: 10.days.ago.iso8601 + } + end + + subject(:result) do + post_graphql(query, current_user: current_user, variables: variables) + + graphql_data.dig(context.to_s, 'flowMetrics', 'issueCount') + end + + it 'returns the correct count' do + expect(result).to eq({ + 'identifier' => 'issues', + 'unit' => nil, + 'value' => 4, + 'title' => n_('New Issue', 'New Issues', 4) + }) + end + + context 'with partial filters' do + let(:variables) do + { + path: full_path, + assigneeUsernames: [assignee.username], + labelNames: [label.title], + from: 20.days.ago.iso8601, + to: 10.days.ago.iso8601 + } + end + + it 'returns filtered count' do + expect(result).to eq({ + 'identifier' => 'issues', + 'unit' => nil, + 'value' => 2, + 'title' => n_('New Issue', 'New Issues', 2) + }) + end + end + + context 'with all filters' do + let(:variables) do + { + path: full_path, + assigneeUsernames: [assignee.username], + labelNames: [label.title], + authorUsername: author.username, + milestoneTitle: milestone.title, + from: 20.days.ago.iso8601, + to: 10.days.ago.iso8601 + } + end + + it 'returns filtered count' do + expect(result).to eq({ + 'identifier' => 'issues', + 'unit' => nil, + 'value' => 1, + 'title' => n_('New Issue', 'New Issues', 1) + }) + end + end + + context 'when the user is not authorized' do + let(:current_user) { create(:user) } + + it 'returns nil' do + expect(result).to eq(nil) + end + end + + it_behaves_like 'validation on Time arguments' +end + +RSpec.shared_examples 'value stream analytics flow metrics deploymentCount examples' do + let_it_be(:deployment1) do + create(:deployment, :success, environment: production_environment1, finished_at: 5.days.ago) + end + + let_it_be(:deployment2) do + create(:deployment, :success, environment: production_environment2, finished_at: 10.days.ago) + end + + let_it_be(:deployment3) do + create(:deployment, :success, environment: production_environment2, finished_at: 15.days.ago) + end + + let(:variables) do + { + path: full_path, + from: 12.days.ago.iso8601, + to: 3.days.ago.iso8601 + } + end + + let(:query) do + <<~QUERY + query($path: ID!, $from: Time!, $to: Time!) { + #{context}(fullPath: $path) { + flowMetrics { + deploymentCount(from: $from, to: $to) { + value + unit + identifier + title + } + } + } + } + QUERY + end + + subject(:result) do + post_graphql(query, current_user: current_user, variables: variables) + + graphql_data.dig(context.to_s, 'flowMetrics', 'deploymentCount') + end + + it 'returns the correct count' do + expect(result).to eq({ + 'identifier' => 'deploys', + 'unit' => nil, + 'value' => 2, + 'title' => n_('Deploy', 'Deploys', 2) + }) + end + + context 'when the user is not authorized' do + let(:current_user) { create(:user) } + + it 'returns nil' do + expect(result).to eq(nil) + end + end + + context 'when outside of the date range' do + let(:variables) do + { + path: full_path, + from: 20.days.ago.iso8601, + to: 18.days.ago.iso8601 + } + end + + it 'returns 0 count' do + expect(result).to eq({ + 'identifier' => 'deploys', + 'unit' => nil, + 'value' => 0, + 'title' => n_('Deploy', 'Deploys', 0) + }) + end + end + + it_behaves_like 'validation on Time arguments' +end + +RSpec.shared_examples 'value stream analytics flow metrics leadTime examples' do + let_it_be(:milestone) { create(:milestone, group: group) } + let_it_be(:label) { create(:group_label, group: group) } + + let_it_be(:author) { create(:user) } + let_it_be(:assignee) { create(:user) } + + let_it_be(:issue1) do + create(:issue, project: project1, author: author, created_at: 17.days.ago, closed_at: 12.days.ago) + end + + let_it_be(:issue2) do + create(:issue, project: project2, author: author, created_at: 16.days.ago, closed_at: 13.days.ago) + end + + let_it_be(:issue3) do + create(:labeled_issue, + project: project1, + labels: [label], + author: author, + milestone: milestone, + assignees: [assignee], + created_at: 14.days.ago, + closed_at: 11.days.ago) + end + + let_it_be(:issue4) do + create(:labeled_issue, + project: project2, + labels: [label], + assignees: [assignee], + created_at: 20.days.ago, + closed_at: 15.days.ago) + end + + before do + Analytics::CycleAnalytics::DataLoaderService.new(group: group, model: Issue).execute + end + + let(:query) do + <<~QUERY + query($path: ID!, $assigneeUsernames: [String!], $authorUsername: String, $milestoneTitle: String, $labelNames: [String!], $from: Time!, $to: Time!) { + #{context}(fullPath: $path) { + flowMetrics { + leadTime(assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, milestoneTitle: $milestoneTitle, labelNames: $labelNames, from: $from, to: $to) { + value + unit + identifier + title + links { + label + url + } + } + } + } + } + QUERY + end + + let(:variables) do + { + path: full_path, + from: 21.days.ago.iso8601, + to: 10.days.ago.iso8601 + } + end + + subject(:result) do + post_graphql(query, current_user: current_user, variables: variables) + + graphql_data.dig(context.to_s, 'flowMetrics', 'leadTime') + end + + it 'returns the correct value' do + expect(result).to match(a_hash_including({ + 'identifier' => 'lead_time', + 'unit' => n_('day', 'days', 4), + 'value' => 4, + 'title' => _('Lead Time'), + 'links' => [ + { 'label' => s_('ValueStreamAnalytics|Dashboard'), 'url' => match(/issues_analytics/) }, + { 'label' => s_('ValueStreamAnalytics|Go to docs'), 'url' => match(/definitions/) } + ] + })) + end + + context 'when the user is not authorized' do + let(:current_user) { create(:user) } + + it 'returns nil' do + expect(result).to eq(nil) + end + end + + context 'when outside of the date range' do + let(:variables) do + { + path: full_path, + from: 30.days.ago.iso8601, + to: 25.days.ago.iso8601 + } + end + + it 'returns 0 count' do + expect(result).to match(a_hash_including({ 'value' => nil })) + end + end + + context 'with all filters' do + let(:variables) do + { + path: full_path, + assigneeUsernames: [assignee.username], + labelNames: [label.title], + authorUsername: author.username, + milestoneTitle: milestone.title, + from: 20.days.ago.iso8601, + to: 10.days.ago.iso8601 + } + end + + it 'returns filtered count' do + expect(result).to match(a_hash_including({ 'value' => 3 })) + end + end +end + +RSpec.shared_examples 'value stream analytics flow metrics cycleTime examples' do + let_it_be(:milestone) { create(:milestone, group: group) } + let_it_be(:label) { create(:group_label, group: group) } + + let_it_be(:author) { create(:user) } + let_it_be(:assignee) { create(:user) } + + let_it_be(:issue1) do + create(:issue, project: project1, author: author, closed_at: 12.days.ago).tap do |issue| + issue.metrics.update!(first_mentioned_in_commit_at: 17.days.ago) + end + end + + let_it_be(:issue2) do + create(:issue, project: project2, author: author, closed_at: 13.days.ago).tap do |issue| + issue.metrics.update!(first_mentioned_in_commit_at: 16.days.ago) + end + end + + let_it_be(:issue3) do + create(:labeled_issue, + project: project1, + labels: [label], + author: author, + milestone: milestone, + assignees: [assignee], + closed_at: 11.days.ago).tap do |issue| + issue.metrics.update!(first_mentioned_in_commit_at: 14.days.ago) + end + end + + let_it_be(:issue4) do + create(:labeled_issue, + project: project2, + labels: [label], + assignees: [assignee], + closed_at: 15.days.ago).tap do |issue| + issue.metrics.update!(first_mentioned_in_commit_at: 20.days.ago) + end + end + + before do + Analytics::CycleAnalytics::DataLoaderService.new(group: group, model: Issue).execute + end + + let(:query) do + <<~QUERY + query($path: ID!, $assigneeUsernames: [String!], $authorUsername: String, $milestoneTitle: String, $labelNames: [String!], $from: Time!, $to: Time!) { + #{context}(fullPath: $path) { + flowMetrics { + cycleTime(assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, milestoneTitle: $milestoneTitle, labelNames: $labelNames, from: $from, to: $to) { + value + unit + identifier + title + links { + label + url + } + } + } + } + } + QUERY + end + + let(:variables) do + { + path: full_path, + from: 21.days.ago.iso8601, + to: 10.days.ago.iso8601 + } + end + + subject(:result) do + post_graphql(query, current_user: current_user, variables: variables) + + graphql_data.dig(context.to_s, 'flowMetrics', 'cycleTime') + end + + it 'returns the correct value' do + expect(result).to eq({ + 'identifier' => 'cycle_time', + 'unit' => n_('day', 'days', 4), + 'value' => 4, + 'title' => _('Cycle Time'), + 'links' => [] + }) + end + + context 'when the user is not authorized' do + let(:current_user) { create(:user) } + + it 'returns nil' do + expect(result).to eq(nil) + end + end + + context 'when outside of the date range' do + let(:variables) do + { + path: full_path, + from: 30.days.ago.iso8601, + to: 25.days.ago.iso8601 + } + end + + it 'returns 0 count' do + expect(result).to match(a_hash_including({ 'value' => nil })) + end + end + + context 'with all filters' do + let(:variables) do + { + path: full_path, + assigneeUsernames: [assignee.username], + labelNames: [label.title], + authorUsername: author.username, + milestoneTitle: milestone.title, + from: 20.days.ago.iso8601, + to: 10.days.ago.iso8601 + } + end + + it 'returns filtered count' do + expect(result).to match(a_hash_including({ 'value' => 3 })) + end + end +end + +RSpec.shared_examples 'value stream analytics flow metrics issuesCompleted examples' do + let_it_be(:milestone) { create(:milestone, group: group) } + let_it_be(:label) { create(:group_label, group: group) } + + let_it_be(:author) { create(:user) } + let_it_be(:assignee) { create(:user) } + + # we don't care about opened date, only closed date. + let_it_be(:issue1) do + create(:issue, project: project1, author: author, created_at: 17.days.ago, closed_at: 12.days.ago) + end + + let_it_be(:issue2) do + create(:issue, project: project2, author: author, created_at: 16.days.ago, closed_at: 13.days.ago) + end + + let_it_be(:issue3) do + create(:labeled_issue, + project: project1, + labels: [label], + author: author, + milestone: milestone, + assignees: [assignee], + created_at: 14.days.ago, + closed_at: 11.days.ago) + end + + let_it_be(:issue4) do + create(:labeled_issue, + project: project2, + labels: [label], + assignees: [assignee], + created_at: 20.days.ago, + closed_at: 15.days.ago) + end + + before do + Analytics::CycleAnalytics::DataLoaderService.new(group: group, model: Issue).execute + end + + let(:query) do + <<~QUERY + query($path: ID!, $assigneeUsernames: [String!], $authorUsername: String, $milestoneTitle: String, $labelNames: [String!], $from: Time!, $to: Time!) { + #{context}(fullPath: $path) { + flowMetrics { + issuesCompletedCount(assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, milestoneTitle: $milestoneTitle, labelNames: $labelNames, from: $from, to: $to) { + value + unit + identifier + title + links { + label + url + } + } + } + } + } + QUERY + end + + let(:variables) do + { + path: full_path, + from: 21.days.ago.iso8601, + to: 10.days.ago.iso8601 + } + end + + subject(:result) do + post_graphql(query, current_user: current_user, variables: variables) + + graphql_data.dig(context.to_s, 'flowMetrics', 'issuesCompletedCount') + end + + it 'returns the correct value' do + expect(result).to match(a_hash_including({ + 'identifier' => 'issues_completed', + 'unit' => n_('issue', 'issues', 4), + 'value' => 4, + 'title' => _('Issues Completed'), + 'links' => [ + { 'label' => s_('ValueStreamAnalytics|Dashboard'), 'url' => match(/issues_analytics/) }, + { 'label' => s_('ValueStreamAnalytics|Go to docs'), 'url' => match(/definitions/) } + ] + })) + end + + context 'when the user is not authorized' do + let(:current_user) { create(:user) } + + it 'returns nil' do + expect(result).to eq(nil) + end + end + + context 'when outside of the date range' do + let(:variables) do + { + path: full_path, + from: 30.days.ago.iso8601, + to: 25.days.ago.iso8601 + } + end + + it 'returns 0 count' do + expect(result).to match(a_hash_including({ 'value' => 0.0 })) + end + end + + context 'with all filters' do + let(:variables) do + { + path: full_path, + assigneeUsernames: [assignee.username], + labelNames: [label.title], + authorUsername: author.username, + milestoneTitle: milestone.title, + from: 20.days.ago.iso8601, + to: 10.days.ago.iso8601 + } + end + + it 'returns filtered count' do + expect(result).to match(a_hash_including({ 'value' => 1.0 })) + end + end +end diff --git a/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb b/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb new file mode 100644 index 00000000000..ef9830fbce8 --- /dev/null +++ b/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'unlicensed cycle analytics request params' do + let(:params) do + { + created_after: '2019-01-01', + created_before: '2019-03-01', + project_ids: [2, 3], + namespace: namespace, + current_user: user + } + end + + subject { described_class.new(params) } + + before do + root_group.add_owner(user) + end + + describe 'validations' do + it 'is valid' do + expect(subject).to be_valid + end + + context 'when `created_before` is missing' do + before do + params[:created_before] = nil + end + + it 'is valid', time_travel_to: '2019-03-01' do + expect(subject).to be_valid + end + end + + context 'when `created_before` is earlier than `created_after`' do + before do + params[:created_before] = '2015-01-01' + end + + it 'is invalid' do + expect(subject).not_to be_valid + expect(subject.errors.messages[:created_before]).not_to be_empty + end + end + + context 'when the date range exceeds 180 days' do + before do + params[:created_before] = '2019-07-15' + end + + it 'is invalid' do + expect(subject).not_to be_valid + message = s_('CycleAnalytics|The given date range is larger than 180 days') + expect(subject.errors.messages[:created_after]).to include(message) + end + end + end + + it 'casts `created_after` to `Time`' do + expect(subject.created_after).to be_a_kind_of(Time) + end + + it 'casts `created_before` to `Time`' do + expect(subject.created_before).to be_a_kind_of(Time) + end + + describe 'optional `value_stream`' do + context 'when `value_stream` is not empty' do + let(:value_stream) { instance_double('Analytics::CycleAnalytics::ValueStream') } + + before do + params[:value_stream] = value_stream + end + + it { expect(subject.value_stream).to eq(value_stream) } + end + + context 'when `value_stream` is nil' do + before do + params[:value_stream] = nil + end + + it { expect(subject.value_stream).to eq(nil) } + end + end + + describe 'sorting params' do + before do + params.merge!(sort: 'duration', direction: 'asc') + end + + it 'converts sorting params to symbol when passing it to data collector' do + data_collector_params = subject.to_data_collector_params + + expect(data_collector_params[:sort]).to eq(:duration) + expect(data_collector_params[:direction]).to eq(:asc) + end + + it 'adds sorting params to data attributes' do + data_attributes = subject.to_data_attributes + + expect(data_attributes[:sort]).to eq('duration') + expect(data_attributes[:direction]).to eq('asc') + end + end + + describe 'aggregation params' do + context 'when not licensed' do + it 'returns nil' do + data_collector_params = subject.to_data_attributes + expect(data_collector_params[:aggregation]).to eq(nil) + end + end + end + + describe 'use_aggregated_data_collector param' do + subject(:value) { described_class.new(params).to_data_collector_params[:use_aggregated_data_collector] } + + it { is_expected.to eq(false) } + end + + describe 'feature availablity data attributes' do + subject(:value) { described_class.new(params).to_data_attributes } + + it 'disables all paid features' do + is_expected.to match(a_hash_including(enable_tasks_by_type_chart: 'false', + enable_customizable_stages: 'false', + enable_projects_filter: 'false')) + end + end +end diff --git a/spec/support/shared_examples/banzai/filters/filter_timeout_shared_examples.rb b/spec/support/shared_examples/banzai/filters/filter_timeout_shared_examples.rb new file mode 100644 index 00000000000..618be53cb3b --- /dev/null +++ b/spec/support/shared_examples/banzai/filters/filter_timeout_shared_examples.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +# These shared_examples require the following variables: +# - text: The text to be run through the filter +# +# Usage: +# +# it_behaves_like 'html filter timeout' do +# let(:text) { 'some text' } +# end +RSpec.shared_examples 'html 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 + +# Usage: +# +# it_behaves_like 'text html filter timeout' do +# let(:text) { 'some text' } +# end +RSpec.shared_examples 'text 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::TimeoutTextPipelineFilter::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 eq text + end + end +end diff --git a/spec/support/shared_examples/banzai/filters/inline_embeds_shared_examples.rb b/spec/support/shared_examples/banzai/filters/inline_embeds_shared_examples.rb index 599161abbfe..8f2f3f89914 100644 --- a/spec/support/shared_examples/banzai/filters/inline_embeds_shared_examples.rb +++ b/spec/support/shared_examples/banzai/filters/inline_embeds_shared_examples.rb @@ -7,6 +7,10 @@ RSpec.shared_examples 'a metrics embed filter' do let(:input) { %(<a href="#{url}">example</a>) } let(:doc) { filter(input) } + before do + stub_feature_flags(remove_monitor_metrics: false) + end + context 'when the document has an external link' do let(:url) { 'https://foo.com' } @@ -38,6 +42,18 @@ RSpec.shared_examples 'a metrics embed filter' do expect(doc.at_css('.js-render-metrics')).to be_present end end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'does not append a metrics chart placeholder' do + node = doc.at_css('.js-render-metrics') + + expect(node).not_to be_present + end + end end # Nokogiri escapes the URLs, but we don't care about that diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/shared_examples/banzai/filters/reference_filter_shared_examples.rb index 0046d931e7d..6912bcaee34 100644 --- a/spec/support/banzai/reference_filter_shared_examples.rb +++ b/spec/support/shared_examples/banzai/filters/reference_filter_shared_examples.rb @@ -6,7 +6,7 @@ # let(:reference) { '#42' } RSpec.shared_examples 'a reference containing an element node' do let(:inner_html) { 'element <code>node</code> inside' } - let(:reference_with_element) { %{<a href="#{reference}">#{inner_html}</a>} } + let(:reference_with_element) { %(<a href="#{reference}">#{inner_html}</a>) } it 'does not escape inner html' do doc = reference_filter(reference_with_element) @@ -29,7 +29,7 @@ RSpec.shared_examples 'user reference or project reference' do end end - context 'mentioning a resource' do + context 'when mentioning a resource' do it_behaves_like 'a reference containing an element node' it_behaves_like 'it contains a data- attribute' @@ -66,12 +66,12 @@ RSpec.shared_examples 'user reference or project reference' do doc = reference_filter("Hey #{reference}", only_path: true) link = doc.css('a').first.attr('href') - expect(link).not_to match %r(https?://) + expect(link).not_to match %r{https?://} expect(link).to eq urls.send "#{subject_name}_path", subject end - context 'referencing a resource in a link href' do - let(:reference) { %Q{<a href="#{get_reference(subject)}">Some text</a>} } + describe 'referencing a resource in a link href' do + let(:reference) { %(<a href="#{get_reference(subject)}">Some text</a>) } it_behaves_like 'it contains a data- attribute' diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb index 7df4b7635d3..ddd3bbd636a 100644 --- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb +++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb @@ -80,7 +80,7 @@ RSpec.shared_examples 'multiple issue boards' do click_button 'Select a label' - page.choose(planning.title) + find('label', text: planning.title).click click_button 'Add to board' diff --git a/spec/support/shared_examples/bulk_imports/visibility_level_examples.rb b/spec/support/shared_examples/bulk_imports/visibility_level_examples.rb index 40e9726f89c..02eae250e6a 100644 --- a/spec/support/shared_examples/bulk_imports/visibility_level_examples.rb +++ b/spec/support/shared_examples/bulk_imports/visibility_level_examples.rb @@ -27,14 +27,6 @@ RSpec.shared_examples 'visibility level settings' do expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) end end - - context 'when destination is blank' do - let(:destination_namespace) { '' } - - it 'sets visibility level to public' do - expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PUBLIC) - end - end end context 'when internal' do @@ -63,27 +55,6 @@ RSpec.shared_examples 'visibility level settings' do expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) end end - - context 'when destination is blank' do - let(:destination_namespace) { '' } - - it 'sets visibility level to internal' do - expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::INTERNAL) - end - - context 'when visibility level is restricted' do - it 'sets visibility level to private' do - stub_application_setting( - restricted_visibility_levels: [ - Gitlab::VisibilityLevel::INTERNAL, - Gitlab::VisibilityLevel::PUBLIC - ] - ) - - expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) - end - end - end end context 'when private' do @@ -112,13 +83,5 @@ RSpec.shared_examples 'visibility level settings' do expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) end end - - context 'when destination is blank' do - let(:destination_namespace) { '' } - - it 'sets visibility level to private' do - expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) - end - end end end diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb index de38d1ff9f8..af1843bae28 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 @@ -138,6 +138,19 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do .not_to exceed_all_query_limit(control_count) end + context 'when user is not allowed to import projects' do + let(:user) { create(:user) } + let!(:group) { create(:group).tap { |group| group.add_developer(user) } } + + it 'returns 404' do + expect(stub_client(repos: [], orgs: [])).to receive(:repos) + + get :status, params: { namespace_id: group.id }, format: :html + + expect(response).to have_gitlab_http_status(:not_found) + end + end + context 'when filtering' do let(:repo_2) { repo_fake.new(login: 'emacs', full_name: 'asd/emacs', name: 'emacs', owner: { login: 'owner' }) } let(:project) { create(:project, import_type: provider, namespace: user.namespace, import_status: :finished, import_source: 'example/repo') } diff --git a/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb index 44baadaaade..e94f063399d 100644 --- a/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb +++ b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb @@ -19,4 +19,26 @@ RSpec.shared_examples 'import controller status' do expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id) expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id) end + + context 'when format is html' do + context 'when namespace_id is present' do + let!(:developer_group) { create(:group).tap { |g| g.add_developer(user) } } + + context 'when user cannot import projects' do + it 'returns 404' do + get :status, params: { namespace_id: developer_group.id }, format: :html + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user can import projects' do + it 'returns 200' do + get :status, params: { namespace_id: group.id }, format: :html + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end end diff --git a/spec/support/controllers/project_import_rate_limiter_shared_examples.rb b/spec/support/shared_examples/controllers/project_import_rate_limiter_shared_examples.rb index 66d753a4010..66d753a4010 100644 --- a/spec/support/controllers/project_import_rate_limiter_shared_examples.rb +++ b/spec/support/shared_examples/controllers/project_import_rate_limiter_shared_examples.rb diff --git a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb index cc28a79b4ca..e75188f8249 100644 --- a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb @@ -60,19 +60,6 @@ RSpec.shared_examples Repositories::GitHttpController do expect(response).to have_gitlab_http_status(:ok) end - it 'updates the user activity' do - activity_project = container.is_a?(PersonalSnippet) ? nil : project - - activity_service = instance_double(Users::ActivityService) - - args = { author: user, project: activity_project, namespace: activity_project&.namespace } - expect(Users::ActivityService).to receive(:new).with(args).and_return(activity_service) - - expect(activity_service).to receive(:execute) - - get :info_refs, params: params - end - include_context 'parsed logs' do it 'adds user info to the logs' do get :info_refs, params: params @@ -87,14 +74,20 @@ RSpec.shared_examples Repositories::GitHttpController do end describe 'POST #git_upload_pack' do - before do + it 'returns 200' do allow(controller).to receive(:verify_workhorse_api!).and_return(true) - end - it 'returns 200' do post :git_upload_pack, params: params expect(response).to have_gitlab_http_status(:ok) end + + context 'when JWT token is not provided' do + it 'returns 403' do + post :git_upload_pack, params: params + + expect(response).to have_gitlab_http_status(:forbidden) + end + end end end diff --git a/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb index 112b9cbb204..f658cfac0f5 100644 --- a/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb +++ b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb @@ -15,8 +15,9 @@ RSpec.shared_examples 'snippets sort order' do context 'when no sort param is provided' do it 'calls SnippetsFinder with updated_at sort option' do - expect(SnippetsFinder).to receive(:new).with(user, - hash_including(sort: 'updated_desc')).and_call_original + expect(SnippetsFinder).to receive(:new) + .with(user, hash_including(sort: 'updated_desc')) + .and_call_original subject end @@ -27,8 +28,9 @@ RSpec.shared_examples 'snippets sort order' do let(:sort_argument) { { sort: order } } it 'calls SnippetsFinder with the given sort param' do - expect(SnippetsFinder).to receive(:new).with(user, - hash_including(sort: order)).and_call_original + expect(SnippetsFinder).to receive(:new) + .with(user, hash_including(sort: order)) + .and_call_original subject end diff --git a/spec/support/shared_examples/controllers/unique_hll_events_examples.rb b/spec/support/shared_examples/controllers/unique_hll_events_examples.rb index 38c3157e898..b5528afa0b5 100644 --- a/spec/support/shared_examples/controllers/unique_hll_events_examples.rb +++ b/spec/support/shared_examples/controllers/unique_hll_events_examples.rb @@ -7,6 +7,9 @@ RSpec.shared_examples 'tracking unique hll events' do it 'tracks unique event' do + # Allow any event tracking before we expect the specific event we want to check below + allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).and_call_original + expect(Gitlab::UsageDataCounters::HLLRedisCounter).to( receive(:track_event) .with(target_event, values: expected_value) diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb index 5d77ed5fdfc..32aa566c27e 100644 --- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -15,20 +15,33 @@ RSpec.shared_examples 'wiki controller actions' do sign_in(user) end - shared_examples 'recovers from git timeout' do + shared_examples 'recovers from git errors' do let(:method_name) { :page } - context 'when we encounter git command errors' do + context 'when we encounter CommandTimedOut error' do it 'renders the appropriate template', :aggregate_failures do - expect(controller).to receive(method_name) do - raise ::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded' - end + expect(controller) + .to receive(method_name) + .and_raise(::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded') request expect(response).to render_template('shared/wikis/git_error') end end + + context 'when we encounter a NoRepository error' do + it 'renders the appropriate template', :aggregate_failures do + expect(controller) + .to receive(method_name) + .and_raise(Gitlab::Git::Repository::NoRepository) + + request + + expect(response).to render_template('shared/wikis/empty') + expect(assigns(:error)).to eq('Could not access the Wiki Repository at this time.') + end + end end describe 'GET #new' do @@ -65,7 +78,7 @@ RSpec.shared_examples 'wiki controller actions' do get :pages, params: routing_params.merge(id: wiki_title) end - it_behaves_like 'recovers from git timeout' do + it_behaves_like 'recovers from git errors' do subject(:request) { get :pages, params: routing_params.merge(id: wiki_title) } let(:method_name) { :wiki_pages } @@ -122,7 +135,7 @@ RSpec.shared_examples 'wiki controller actions' do end end - it_behaves_like 'recovers from git timeout' do + it_behaves_like 'recovers from git errors' do subject(:request) { get :history, params: routing_params.merge(id: wiki_title) } let(:allow_read_wiki) { true } @@ -170,7 +183,7 @@ RSpec.shared_examples 'wiki controller actions' do end end - it_behaves_like 'recovers from git timeout' do + it_behaves_like 'recovers from git errors' do subject(:request) { get :diff, params: routing_params.merge(id: wiki_title, version_id: wiki.repository.commit.id) } end end @@ -185,7 +198,7 @@ RSpec.shared_examples 'wiki controller actions' do context 'when page exists' do let(:id) { wiki_title } - it_behaves_like 'recovers from git timeout' + it_behaves_like 'recovers from git errors' it 'renders the page' do request @@ -366,7 +379,7 @@ RSpec.shared_examples 'wiki controller actions' do subject(:request) { get(:edit, params: routing_params.merge(id: id_param)) } it_behaves_like 'edit action' - it_behaves_like 'recovers from git timeout' + it_behaves_like 'recovers from git errors' context 'when page content encoding is valid' do render_views @@ -386,11 +399,10 @@ RSpec.shared_examples 'wiki controller actions' do let(:id_param) { wiki_title } subject(:request) do - patch(:update, - params: routing_params.merge( - id: id_param, - wiki: { title: new_title, content: new_content } - )) + patch(:update, params: routing_params.merge( + id: id_param, + wiki: { title: new_title, content: new_content } + )) end it_behaves_like 'edit action' @@ -426,10 +438,9 @@ RSpec.shared_examples 'wiki controller actions' do let(:new_content) { 'New content' } subject(:request) do - post(:create, - params: routing_params.merge( - wiki: { title: new_title, content: new_content } - )) + post(:create, params: routing_params.merge( + wiki: { title: new_title, content: new_content } + )) end context 'when page is valid' do @@ -463,10 +474,9 @@ RSpec.shared_examples 'wiki controller actions' do let(:delete_user) { user } subject(:request) do - delete(:destroy, - params: routing_params.merge( - id: id_param - )) + delete(:destroy, params: routing_params.merge( + id: id_param + )) end before do diff --git a/spec/support/shared_examples/db/seeds/data_seeder_shared_examples.rb b/spec/support/shared_examples/db/seeds/data_seeder_shared_examples.rb new file mode 100644 index 00000000000..4e8d65ac25e --- /dev/null +++ b/spec/support/shared_examples/db/seeds/data_seeder_shared_examples.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'raises an error when specifying an invalid factory' do + it 'raises an error' do + expect { parser.parse }.to raise_error(RuntimeError, /invalids.*to a valid registered Factory/) + end +end + +RSpec.shared_examples 'specifying invalid traits to a factory' do + it 'raises an error', :aggregate_failures do + expect { parser.parse }.to raise_error do |error| + expect(error).to be_a(RuntimeError) + expect(error.message).to include('Trait not registered: \\"invalid\\"') + expect(error.message).to include('for Factory \\"issue\\"') + end + end +end + +RSpec.shared_examples 'specifying invalid attributes to a factory' do + it 'raises an error' do + expect { parser.parse }.to raise_error(RuntimeError, /is not a valid attribute/) + end + + it 'contains possible alternatives' do + expect { parser.parse }.to raise_error(RuntimeError, /Did you mean/) + end +end + +RSpec.shared_examples 'an id already exists' do + it 'raises a validation error' do + expect { parser.parse }.to raise_error(/id `my_label` must be unique/) + end +end + +RSpec.shared_examples 'name is not specified' do + it 'raises an error when name is not specified' do + expect { parser.parse }.to raise_error(/Seed file must specify a name/) + end +end + +RSpec.shared_examples 'factory definitions' do + it 'has exactly two definitions' do + parser.parse + + expect(parser.definitions.size).to eq(2) + end + + it 'creates the group label' do + expect { parser.parse }.to change { GroupLabel.count }.by(1) + end + + it 'creates the project' do + expect { parser.parse }.to change { Project.count }.by(1) + end +end + +RSpec.shared_examples 'passes traits' do + it 'passes traits' do + expect_next_instance_of(Gitlab::DataSeeder::FactoryDefinitions::FactoryDefinition) do |instance| + # `described` trait will automaticaly generate a description + expect(instance.build(binding).description).to eq('Description of Test Label') + end + + parser.parse + end +end + +RSpec.shared_examples 'has a name' do + it 'has a name' do + parser.parse + + expect(parser.name).to eq('Test') + end +end + +RSpec.shared_examples 'definition has an id' do + it 'binds the object', :aggregate_failures do + parser.parse + + expect(group_labels).to be_a(OpenStruct) # rubocop:disable Style/OpenStructUse + expect(group_labels.my_label).to be_a(GroupLabel) + expect(group_labels.my_label.title).to eq('My Label') + end +end + +RSpec.shared_examples 'id has spaces' do + it 'binds to an underscored variable', :aggregate_failures do + parser.parse + + expect(group_labels).to respond_to(:id_with_spaces) + expect(group_labels.id_with_spaces.title).to eq('With Spaces') + end + + it 'renders a warning' do + expect { parser.parse }.to output(%(parsing id "id with spaces" as "id_with_spaces"\n)).to_stderr + end +end + +RSpec.shared_examples 'definition does not have an id' do + it 'does not bind the object' do + parser.parse + + expect(group_labels.to_h).to be_empty + end +end + +RSpec.shared_examples 'invalid id' do |message| + it 'raises an error' do + expect { parser.parse }.to raise_error(message) + end +end diff --git a/spec/support/shared_examples/features/2fa_shared_examples.rb b/spec/support/shared_examples/features/2fa_shared_examples.rb index 44f30c32472..6c4e98c9989 100644 --- a/spec/support/shared_examples/features/2fa_shared_examples.rb +++ b/spec/support/shared_examples/features/2fa_shared_examples.rb @@ -1,13 +1,11 @@ # frozen_string_literal: true RSpec.shared_examples 'hardware device for 2fa' do |device_type| - include Spec::Support::Helpers::Features::TwoFactorHelpers + include Features::TwoFactorHelpers include Spec::Support::Helpers::ModalHelpers def register_device(device_type, **kwargs) case device_type.downcase - when "u2f" - register_u2f_device(**kwargs) when "webauthn" register_webauthn_device(**kwargs) else @@ -98,9 +96,7 @@ RSpec.shared_examples 'hardware device for 2fa' do |device_type| end it 'provides a button that shows the fallback otp code UI' do - expect(page).to have_link('Sign in via 2FA code') - - click_link('Sign in via 2FA code') + click_button(_('Sign in via 2FA code')) assert_fallback_ui(page) end diff --git a/spec/support/shared_examples/features/abuse_report_shared_examples.rb b/spec/support/shared_examples/features/abuse_report_shared_examples.rb new file mode 100644 index 00000000000..ea9b4e9f4b2 --- /dev/null +++ b/spec/support/shared_examples/features/abuse_report_shared_examples.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'reports the user with an abuse category' do + it 'creates abuse report' do + click_button 'Report abuse' + choose "They're posting spam." + click_button 'Next' + + page.attach_file('spec/fixtures/dk.png') do + click_button "Choose file" + end + + fill_in 'abuse_report_message', with: 'This user sends spam' + click_button 'Send report' + + expect(page).to have_content 'Thank you for your report' + end +end diff --git a/spec/support/shared_examples/features/access_tokens_shared_examples.rb b/spec/support/shared_examples/features/access_tokens_shared_examples.rb index 32a7b32ac72..3c78869ffaa 100644 --- a/spec/support/shared_examples/features/access_tokens_shared_examples.rb +++ b/spec/support/shared_examples/features/access_tokens_shared_examples.rb @@ -9,7 +9,7 @@ RSpec.shared_examples 'resource access tokens missing access rights' do end RSpec.shared_examples 'resource access tokens creation' do |resource_type| - include Spec::Support::Helpers::AccessTokenHelpers + include Features::AccessTokenHelpers it 'allows creation of an access token', :aggregate_failures do name = 'My access token' diff --git a/spec/support/shared_examples/features/confidential_notes_shared_examples.rb b/spec/support/shared_examples/features/confidential_notes_shared_examples.rb index 289da025af6..cd0e8f94934 100644 --- a/spec/support/shared_examples/features/confidential_notes_shared_examples.rb +++ b/spec/support/shared_examples/features/confidential_notes_shared_examples.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.shared_examples 'confidential notes on issuables' do - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers context 'when user does not have permissions' do it 'does not show confidential note checkbox' do 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 6cd9c4ce1c4..41114197ff5 100644 --- a/spec/support/shared_examples/features/content_editor_shared_examples.rb +++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb @@ -1,116 +1,310 @@ # frozen_string_literal: true +require 'spec_helper' + RSpec.shared_examples 'edits content using the content editor' do + include ContentEditorHelpers + let(:content_editor_testid) { '[data-testid="content-editor"] [contenteditable].ProseMirror' } - def switch_to_content_editor - click_button _('View rich text') - click_button _('Rich text') - end + let(:is_mac) { page.evaluate_script('navigator.platform').include?('Mac') } + let(:modifier_key) { is_mac ? :command : :control } - def type_in_content_editor(keys) - find(content_editor_testid).send_keys keys - end + it 'saves page content in local storage if the user navigates away' do + switch_to_content_editor - def open_insert_media_dropdown - page.find('svg[data-testid="media-icon"]').click - end + expect(page).to have_css(content_editor_testid) - def set_source_editor_content(content) - find('.js-gfm-input').set content - end + type_in_content_editor ' Typing text in the content editor' - def expect_formatting_menu_to_be_visible - expect(page).to have_css('[data-testid="formatting-bubble-menu"]') - end + wait_until_hidden_field_is_updated /Typing text in the content editor/ - def expect_formatting_menu_to_be_hidden - expect(page).not_to have_css('[data-testid="formatting-bubble-menu"]') - end + begin + refresh + rescue Selenium::WebDriver::Error::UnexpectedAlertOpenError + end - def expect_media_bubble_menu_to_be_visible - expect(page).to have_css('[data-testid="media-bubble-menu"]') + expect(page).to have_text('Typing text in the content editor') end - def upload_asset(fixture_name) - attach_file('content_editor_image', Rails.root.join('spec', 'fixtures', fixture_name), make_visible: true) - end + describe 'creating and editing links' do + before do + switch_to_content_editor + end - def wait_until_hidden_field_is_updated(value) - expect(page).to have_field('wiki[content]', with: value, type: 'hidden') - end + context 'when clicking the link icon in the toolbar' do + it 'shows the link bubble menu' do + page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click - it 'saves page content in local storage if the user navigates away' do - switch_to_content_editor + expect(page).to have_css('[data-testid="link-bubble-menu"]') + end - expect(page).to have_css(content_editor_testid) + context 'if no text is selected' do + before do + page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click + end + + it 'opens an empty inline modal to create a link' do + page.within '[data-testid="link-bubble-menu"]' do + expect(page).to have_field('link-text', with: '') + expect(page).to have_field('link-href', with: '') + end + end + + context 'when the user clicks the apply button' do + it 'applies the changes to the document' do + page.within '[data-testid="link-bubble-menu"]' do + fill_in 'link-text', with: 'Link to GitLab home page' + fill_in 'link-href', with: 'https://gitlab.com' + + click_button 'Apply' + end + + page.within content_editor_testid do + expect(page).to have_css('a[href="https://gitlab.com"]') + expect(page).to have_text('Link to GitLab home page') + end + end + end + + context 'when the user clicks the cancel button' do + it 'does not apply the changes to the document' do + page.within '[data-testid="link-bubble-menu"]' do + fill_in 'link-text', with: 'Link to GitLab home page' + fill_in 'link-href', with: 'https://gitlab.com' + + click_button 'Cancel' + end + + page.within content_editor_testid do + expect(page).not_to have_css('a') + end + end + end + end - type_in_content_editor ' Typing text in the content editor' + context 'if text is selected' do + before do + type_in_content_editor 'The quick brown fox jumps over the lazy dog' + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + + page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click + end + + it 'prefills inline modal to create a link' do + page.within '[data-testid="link-bubble-menu"]' do + expect(page).to have_field('link-text', with: 'dog') + expect(page).to have_field('link-href', with: '') + end + end + + context 'when the user clicks the apply button' do + it 'applies the changes to the document' do + page.within '[data-testid="link-bubble-menu"]' do + fill_in 'link-text', with: 'new dog' + fill_in 'link-href', with: 'https://en.wikipedia.org/wiki/Shiba_Inu' + + click_button 'Apply' + end + + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://en.wikipedia.org/wiki/Shiba_Inu"]', + text: 'new dog' + ) + end + end + end + end + end - wait_until_hidden_field_is_updated /Typing text in the content editor/ + context 'if cursor is placed on an existing link' do + before do + type_in_content_editor 'Link to [GitLab home **page**](https://gitlab.com)' + type_in_content_editor :left + end - refresh + it 'prefills inline modal to edit the link' do + page.within '[data-testid="link-bubble-menu"]' do + page.find('[data-testid="edit-link"]').click - expect(page).to have_text('Typing text in the content editor') + expect(page).to have_field('link-text', with: 'GitLab home page') + expect(page).to have_field('link-href', with: 'https://gitlab.com') + end + end - refresh # also retained after second refresh + it 'updates the link attributes if text is not updated' do + page.within '[data-testid="link-bubble-menu"]' do + page.find('[data-testid="edit-link"]').click - expect(page).to have_text('Typing text in the content editor') + fill_in 'link-href', with: 'https://about.gitlab.com' - click_link 'Cancel' # draft is deleted on cancel + click_button 'Apply' + end - page.go_back + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://about.gitlab.com"]') + expect(page.find('a')).to have_text('GitLab home page') + expect(page).to have_selector('strong', text: 'page') + end + end - expect(page).not_to have_text('Typing text in the content editor') - end + it 'updates the link attributes and text if text is updated' do + page.within '[data-testid="link-bubble-menu"]' do + page.find('[data-testid="edit-link"]').click - describe 'formatting bubble menu' do - it 'shows a formatting bubble menu for a regular paragraph and headings' do - switch_to_content_editor + fill_in 'link-text', with: 'GitLab about page' + fill_in 'link-href', with: 'https://about.gitlab.com' - expect(page).to have_css(content_editor_testid) + click_button 'Apply' + end - type_in_content_editor 'Typing text in the content editor' - type_in_content_editor [:shift, :left] + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://about.gitlab.com"]', + text: 'GitLab about page' + ) + expect(page).not_to have_selector('strong') + end + end - expect_formatting_menu_to_be_visible + it 'does nothing if Cancel is clicked' do + page.within '[data-testid="link-bubble-menu"]' do + page.find('[data-testid="edit-link"]').click - type_in_content_editor [:right, :right, :enter, '## Heading'] + click_button 'Cancel' + end - expect_formatting_menu_to_be_hidden + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://gitlab.com"]', + text: 'GitLab home page' + ) + expect(page).to have_selector('strong') + end + end - type_in_content_editor [:shift, :left] + context 'when the user clicks the unlink button' do + it 'removes the link' do + page.within '[data-testid="link-bubble-menu"]' do + page.find('[data-testid="remove-link"]').click + end + + page.within content_editor_testid do + expect(page).not_to have_selector('a') + expect(page).to have_selector('strong', text: 'page') + end + end + end + end + + context 'when selection spans more than a link' do + before do + type_in_content_editor 'a [b **c**](https://gitlab.com)' + + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + + page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click + end + + it 'prefills inline modal with the entire selection' do + page.within '[data-testid="link-bubble-menu"]' do + expect(page).to have_field('link-text', with: 'a b c') + expect(page).to have_field('link-href', with: '') + end + end - expect_formatting_menu_to_be_visible + it 'expands the link and updates the link attributes if text is not updated' do + page.within '[data-testid="link-bubble-menu"]' do + fill_in 'link-href', with: 'https://about.gitlab.com' + + click_button 'Apply' + end + + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://about.gitlab.com"]') + expect(page.find('a')).to have_text('a b c') + expect(page).to have_selector('strong', text: 'c') + end + end + + it 'expands the link, updates the link attributes and text if text is updated' do + page.within '[data-testid="link-bubble-menu"]' do + fill_in 'link-text', with: 'new text' + fill_in 'link-href', with: 'https://about.gitlab.com' + + click_button 'Apply' + end + + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://about.gitlab.com"]', + text: 'new text' + ) + expect(page).not_to have_selector('strong') + end + end end end - describe 'media elements bubble menu' do + describe 'selecting text' do before do switch_to_content_editor - open_insert_media_dropdown + # delete all text first + type_in_content_editor [modifier_key, 'a'] + type_in_content_editor :backspace + + type_in_content_editor 'The quick **brown** fox _jumps_ over the lazy dog!' + type_in_content_editor :enter + type_in_content_editor '[Link](https://gitlab.com)' + type_in_content_editor :enter + type_in_content_editor 'Jackdaws love my ~~big~~ sphinx of quartz!' + + # select all text + type_in_content_editor [modifier_key, 'a'] end - def test_displays_media_bubble_menu(media_element_selector, fixture_file) - upload_asset fixture_file + it 'renders selected text in a .content-editor-selection class' do + page.within content_editor_testid do + assert_selected 'The quick' + assert_selected 'brown' + assert_selected 'fox' + assert_selected 'jumps' + assert_selected 'over the lazy dog!' - wait_for_requests + assert_selected 'Link' - expect(page).to have_css(media_element_selector) + assert_selected 'Jackdaws love my' + assert_selected 'big' + assert_selected 'sphinx of quartz!' + end + end - page.find(media_element_selector).click + def assert_selected(text) + expect(page).to have_selector('.content-editor-selection', text: text) + end + end - expect_formatting_menu_to_be_hidden - expect_media_bubble_menu_to_be_visible + describe 'media elements bubble menu' do + before do + switch_to_content_editor + + click_attachment_button end it 'displays correct media bubble menu for images', :js do - test_displays_media_bubble_menu '[data-testid="content_editor_editablebox"] img[src]', 'dk.png' + display_media_bubble_menu '[data-testid="content_editor_editablebox"] img[src]', 'dk.png' + + expect_media_bubble_menu_to_be_visible end it 'displays correct media bubble menu for video', :js do - test_displays_media_bubble_menu '[data-testid="content_editor_editablebox"] video', 'video_sample.mp4' + display_media_bubble_menu '[data-testid="content_editor_editablebox"] video', 'video_sample.mp4' + + expect_media_bubble_menu_to_be_visible end end @@ -150,7 +344,6 @@ RSpec.shared_examples 'edits content using the content editor' do type_in_content_editor 'var a = 0' type_in_content_editor [:shift, :left] - expect_formatting_menu_to_be_hidden expect(page).to have_css('[data-testid="code-block-bubble-menu"]') end @@ -187,8 +380,8 @@ RSpec.shared_examples 'edits content using the content editor' do expect(iframe['src']).to include('/-/sandbox/mermaid') within_frame(iframe) do - expect(find('svg').text).to include('JohnDoe12') - expect(find('svg').text).to include('HelloWorld34') + expect(find('svg .nodes').text).to include('JohnDoe12') + expect(find('svg .nodes').text).to include('HelloWorld34') end expect(iframe['height'].to_i).to be > 100 @@ -198,12 +391,13 @@ RSpec.shared_examples 'edits content using the content editor' do within_frame(iframe) do page.has_content?('JaneDoe34') - expect(find('svg').text).to include('JaneDoe34') - expect(find('svg').text).to include('HelloWorld56') + expect(find('svg .nodes').text).to include('JaneDoe34') + expect(find('svg .nodes').text).to include('HelloWorld56') end end - it 'toggles the diagram when preview button is clicked' do + it 'toggles the diagram when preview button is clicked', + quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/397682' do find('[data-testid="preview-diagram"]').click expect(find(content_editor_testid)).not_to have_selector('iframe') @@ -213,8 +407,61 @@ RSpec.shared_examples 'edits content using the content editor' do iframe = find(content_editor_testid).find('iframe') within_frame(iframe) do - expect(find('svg').text).to include('JohnDoe12') - expect(find('svg').text).to include('HelloWorld34') + expect(find('svg .nodes').text).to include('JohnDoe12') + expect(find('svg .nodes').text).to include('HelloWorld34') + end + end + end + + describe 'pasting text' do + before do + switch_to_content_editor + + type_in_content_editor "Some **rich** _text_ ~~content~~ [link](https://gitlab.com)" + + type_in_content_editor [modifier_key, 'a'] + type_in_content_editor [modifier_key, 'x'] + end + + it 'pastes text with formatting if ctrl + v is pressed' do + type_in_content_editor [modifier_key, 'v'] + + page.within content_editor_testid do + expect(page).to have_selector('strong', text: 'rich') + expect(page).to have_selector('em', text: 'text') + expect(page).to have_selector('s', text: 'content') + expect(page).to have_selector('a[href="https://gitlab.com"]', text: 'link') + end + end + + it 'pastes raw text without formatting if shift + ctrl + v is pressed' do + type_in_content_editor [modifier_key, :shift, 'v'] + + page.within content_editor_testid do + expect(page).to have_text('Some rich text content link') + + expect(page).not_to have_selector('strong') + expect(page).not_to have_selector('em') + expect(page).not_to have_selector('s') + expect(page).not_to have_selector('a') + end + end + + it 'pastes raw text without formatting, stripping whitespaces, if shift + ctrl + v is pressed' do + type_in_content_editor " Some **rich**" + type_in_content_editor :enter + type_in_content_editor " _text_" + type_in_content_editor :enter + type_in_content_editor " ~~content~~" + type_in_content_editor :enter + type_in_content_editor " [link](https://gitlab.com)" + + type_in_content_editor [modifier_key, 'a'] + type_in_content_editor [modifier_key, 'x'] + type_in_content_editor [modifier_key, :shift, 'v'] + + page.within content_editor_testid do + expect(page).to have_text('Some rich text content link') end end end @@ -225,7 +472,7 @@ RSpec.shared_examples 'edits content using the content editor' do before do if defined?(project) create(:issue, project: project, title: 'My Cool Linked Issue') - create(:merge_request, source_project: project, title: 'My Cool Merge Request') + create(:merge_request, source_project: project, source_branch: 'branch-1', title: 'My Cool Merge Request') create(:label, project: project, title: 'My Cool Label') create(:milestone, project: project, title: 'My Cool Milestone') @@ -234,7 +481,7 @@ RSpec.shared_examples 'edits content using the content editor' do project = create(:project, group: group) create(:issue, project: project, title: 'My Cool Linked Issue') - create(:merge_request, source_project: project, title: 'My Cool Merge Request') + create(:merge_request, source_project: project, source_branch: 'branch-1', title: 'My Cool Merge Request') create(:group_label, group: group, title: 'My Cool Label') create(:milestone, group: group, title: 'My Cool Milestone') @@ -251,7 +498,9 @@ RSpec.shared_examples 'edits content using the content editor' do expect(find(suggestions_dropdown)).to have_text('abc123') expect(find(suggestions_dropdown)).to have_text('all') - expect(find(suggestions_dropdown)).to have_text('Group Members (2)') + expect(find(suggestions_dropdown)).to have_text('Group Members') + + type_in_content_editor 'bc' send_keys [:arrow_down, :enter] @@ -332,3 +581,23 @@ RSpec.shared_examples 'edits content using the content editor' do end end end + +RSpec.shared_examples 'inserts diagrams.net diagram using the content editor' do + include ContentEditorHelpers + + before do + switch_to_content_editor + + click_attachment_button + end + + it 'displays correct media bubble menu with edit diagram button' do + display_media_bubble_menu '[data-testid="content_editor_editablebox"] img[src]', 'diagram.drawio.svg' + + expect_media_bubble_menu_to_be_visible + + click_edit_diagram_button + + expect_drawio_editor_is_opened + 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 96e57980c68..7e0e235698e 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 @@ -5,20 +5,20 @@ RSpec.shared_examples 'a creatable merge request' do include ListboxHelpers it 'creates new merge request', :js do - find('.js-assignee-search').click + find('[data-testid="assignee-ids-dropdown-toggle"]').click page.within '.dropdown-menu-user' do click_link user2.name end expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s) - page.within '.js-assignee-search' do + page.within '[data-testid="assignee-ids-dropdown-toggle"]' do expect(page).to have_content user2.name end click_link 'Assign to me' expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s) - page.within '.js-assignee-search' do + page.within '[data-testid="assignee-ids-dropdown-toggle"]' do expect(page).to have_content user.name end diff --git a/spec/support/shared_examples/features/dashboard/sidebar_shared_examples.rb b/spec/support/shared_examples/features/dashboard/sidebar_shared_examples.rb index efbd735c451..9b5d9d66890 100644 --- a/spec/support/shared_examples/features/dashboard/sidebar_shared_examples.rb +++ b/spec/support/shared_examples/features/dashboard/sidebar_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples "a dashboard page with sidebar" do |page_path, menu_label| +RSpec.shared_examples 'a "Your work" page with sidebar and breadcrumbs' do |page_path, menu_label| before do sign_in(user) visit send(page_path) @@ -18,4 +18,13 @@ RSpec.shared_examples "a dashboard page with sidebar" do |page_path, menu_label| expect(page).to have_css(active_menu_item_css) end end + + describe "breadcrumbs" do + it 'has "Your work" as its root breadcrumb' do + breadcrumbs = page.find('[data-testid="breadcrumb-links"]') + within breadcrumbs do + expect(page).to have_css("li:first-child a[href=\"#{root_path}\"]", text: "Your work") + end + end + end end diff --git a/spec/support/shared_examples/features/deploy_token_shared_examples.rb b/spec/support/shared_examples/features/deploy_token_shared_examples.rb index 9fe08e5c996..80f5f1d805c 100644 --- a/spec/support/shared_examples/features/deploy_token_shared_examples.rb +++ b/spec/support/shared_examples/features/deploy_token_shared_examples.rb @@ -17,9 +17,11 @@ RSpec.shared_examples 'a deploy token in settings' do it 'add a new deploy token', :js do visit page_path - fill_in _('Name'), with: 'new_deploy_key' - fill_in _('Expiration date (optional)'), with: (Date.today + 1.month).to_s - fill_in _('Username (optional)'), with: 'deployer' + within('#js-deploy-tokens') do + fill_in _('Name'), with: 'new_deploy_key' + fill_in _('Expiration date (optional)'), with: (Date.today + 1.month).to_s + fill_in _('Username (optional)'), with: 'deployer' + end check 'read_repository' check 'read_registry' click_button 'Create deploy token' diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb index ea6d1655694..14e53dc8655 100644 --- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb @@ -77,15 +77,21 @@ RSpec.shared_examples 'an editable merge request' do expect(page).to have_selector('.js-quick-submit') end - it 'warns about version conflict' do + it 'warns about version conflict', :js do merge_request.update!(title: "New title") fill_in 'merge_request_title', with: 'bug 345' fill_in 'merge_request_description', with: 'bug description' - click_button 'Save changes' + click_button _('Save changes') - expect(page).to have_content 'Someone edited the merge request the same time you did' + expect(page).to have_content( + format( + _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + model_name: _('merge request'), + link_to_model: _('merge request') + ) + ) end it 'preserves description textarea height', :js do @@ -104,8 +110,8 @@ RSpec.shared_examples 'an editable merge request' do fill_in 'merge_request_description', with: long_description height = get_textarea_height - find('.js-md-preview-button').click - find('.js-md-write-button').click + click_button("Preview") + click_button("Continue editing") new_height = get_textarea_height expect(height).to eq(new_height) diff --git a/spec/support/shared_examples/features/explore/sidebar_shared_examples.rb b/spec/support/shared_examples/features/explore/sidebar_shared_examples.rb new file mode 100644 index 00000000000..1754c8bf53d --- /dev/null +++ b/spec/support/shared_examples/features/explore/sidebar_shared_examples.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'an "Explore" page with sidebar and breadcrumbs' do |page_path, menu_label| + before do + visit send(page_path) + end + + let(:sidebar_css) { 'aside.nav-sidebar[aria-label="Explore"]' } + let(:active_menu_item_css) { "li.active[data-track-label=\"#{menu_label}_menu\"]" } + + it 'shows the "Explore" sidebar' do + expect(page).to have_css(sidebar_css) + end + + it 'shows the correct sidebar menu item as active' do + within(sidebar_css) do + expect(page).to have_css(active_menu_item_css) + end + end + + describe 'breadcrumbs' do + it 'has "Explore" as its root breadcrumb' do + within '.breadcrumbs-list' do + expect(page).to have_css("li:first a[href=\"#{explore_root_path}\"]", text: 'Explore') + end + end + end +end diff --git a/spec/support/shared_examples/features/incident_details_routing_shared_examples.rb b/spec/support/shared_examples/features/incident_details_routing_shared_examples.rb index dab125caa60..b8e42843e6f 100644 --- a/spec/support/shared_examples/features/incident_details_routing_shared_examples.rb +++ b/spec/support/shared_examples/features/incident_details_routing_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'for each incident details route' do |example, tab_text:| +RSpec.shared_examples 'for each incident details route' do |example, tab_text:, tab:| before do sign_in(user) visit incident_path @@ -25,4 +25,16 @@ RSpec.shared_examples 'for each incident details route' do |example, tab_text:| it_behaves_like example end + + context "for /-/issues/incident/:id/#{tab} route" do + let(:incident_path) { incident_project_issues_path(project, incident, tab) } + + it_behaves_like example + end + + context "for /-/issues/:id/#{tab} route" do + let(:incident_path) { incident_issue_project_issue_path(project, incident, tab) } + + it_behaves_like example + end end diff --git a/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb b/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb index 4c312b42c0a..148ff2cfb54 100644 --- a/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb +++ b/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb @@ -8,7 +8,7 @@ RSpec.shared_examples 'user activates the Mattermost Slash Command integration' it 'shows a token placeholder' do token_placeholder = find_field('service_token')['placeholder'] - expect(token_placeholder).to eq('XXxxXXxxXXxxXXxxXXxxXXxx') + expect(token_placeholder).to eq('') end it 'redirects to the integrations page after saving but not activating' do diff --git a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb index b6f7094e422..b8c6b85adb2 100644 --- a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb +++ b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'issuable invite members' do - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::InviteMembersModalHelpers context 'when a privileged user can invite' do before do @@ -17,8 +17,6 @@ RSpec.shared_examples 'issuable invite members' do page.within '.dropdown-menu-user' do expect(page).to have_link('Invite Members') - expect(page).to have_selector('[data-track-action="click_invite_members"]') - expect(page).to have_selector('[data-track-label="edit_assignee"]') end click_link 'Invite Members' diff --git a/spec/support/shared_examples/features/manage_applications_shared_examples.rb b/spec/support/shared_examples/features/manage_applications_shared_examples.rb index b59f3f1e27b..b8fd58e7efa 100644 --- a/spec/support/shared_examples/features/manage_applications_shared_examples.rb +++ b/spec/support/shared_examples/features/manage_applications_shared_examples.rb @@ -5,87 +5,40 @@ RSpec.shared_examples 'manage applications' do let_it_be(:application_name_changed) { "#{application_name} changed" } let_it_be(:application_redirect_uri) { 'https://foo.bar' } - context 'when hash_oauth_secrets flag set' do - before do - stub_feature_flags(hash_oauth_secrets: true) - end - - it 'allows user to manage applications', :js do - visit new_application_path + it 'allows user to manage applications', :js do + visit new_application_path - expect(page).to have_content 'Add new application' + expect(page).to have_content 'Add new application' - fill_in :doorkeeper_application_name, with: application_name - fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri - check :doorkeeper_application_scopes_read_user - click_on 'Save application' + fill_in :doorkeeper_application_name, with: application_name + fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri + check :doorkeeper_application_scopes_read_user + click_on 'Save application' - validate_application(application_name, 'Yes') - expect(page).to have_content _('This is the only time the secret is accessible. Copy the secret and store it securely') - expect(page).to have_link('Continue', href: index_path) + validate_application(application_name, 'Yes') + expect(page).to have_content _('This is the only time the secret is accessible. Copy the secret and store it securely') + expect(page).to have_link('Continue', href: index_path) - expect(page).to have_css("button[title=\"Copy secret\"]", text: 'Copy') + expect(page).to have_button(_('Copy secret')) - click_on 'Edit' + click_on 'Edit' - application_name_changed = "#{application_name} changed" + application_name_changed = "#{application_name} changed" - fill_in :doorkeeper_application_name, with: application_name_changed - uncheck :doorkeeper_application_confidential - click_on 'Save application' - - validate_application(application_name_changed, 'No') - expect(page).not_to have_link('Continue') - expect(page).to have_content _('The secret is only available when you first create the application') - - visit_applications_path - - page.within '.oauth-applications' do - click_on 'Destroy' - end - expect(page.find('.oauth-applications')).not_to have_content 'test_changed' - end - end - - context 'when hash_oauth_secrets flag not set' do - before do - stub_feature_flags(hash_oauth_secrets: false) - end - - it 'allows user to manage applications', :js do - visit new_application_path - - expect(page).to have_content 'Add new application' - - fill_in :doorkeeper_application_name, with: application_name - fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri - check :doorkeeper_application_scopes_read_user - click_on 'Save application' - - validate_application(application_name, 'Yes') - expect(page).to have_link('Continue', href: index_path) - - application = Doorkeeper::Application.find_by(name: application_name) - expect(page).to have_css("button[title=\"Copy secret\"][data-clipboard-text=\"#{application.secret}\"]", text: 'Copy') - - click_on 'Edit' - - application_name_changed = "#{application_name} changed" - - fill_in :doorkeeper_application_name, with: application_name_changed - uncheck :doorkeeper_application_confidential - click_on 'Save application' + fill_in :doorkeeper_application_name, with: application_name_changed + uncheck :doorkeeper_application_confidential + click_on 'Save application' - validate_application(application_name_changed, 'No') - expect(page).not_to have_link('Continue') + validate_application(application_name_changed, 'No') + expect(page).not_to have_link('Continue') + expect(page).to have_content _('The secret is only available when you create the application or renew the secret.') - visit_applications_path + visit_applications_path - page.within '.oauth-applications' do - click_on 'Destroy' - end - expect(page.find('.oauth-applications')).not_to have_content 'test_changed' + page.within '.oauth-applications' do + click_on 'Destroy' end + expect(page.find('.oauth-applications')).not_to have_content 'test_changed' end context 'when scopes are blank' do diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb index c2dc87b0fb0..6487e6a94c1 100644 --- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb +++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'Maintainer manages access requests' do - include Spec::Support::Helpers::Features::MembersHelpers + include Features::MembersHelpers let(:user) { create(:user) } let(:maintainer) { create(:user) } diff --git a/spec/support/shared_examples/features/milestone_editing_shared_examples.rb b/spec/support/shared_examples/features/milestone_editing_shared_examples.rb new file mode 100644 index 00000000000..d21bf62ecfa --- /dev/null +++ b/spec/support/shared_examples/features/milestone_editing_shared_examples.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'milestone handling version conflicts' do + it 'warns about version conflict when milestone has been updated in the background' do + # Update the milestone in the background in order to trigger a version conflict + milestone.update!(title: "New title") + + fill_in _('Title'), with: 'Title for version conflict' + fill_in _('Description'), with: 'Description for version conflict' + + click_button _('Save changes') + + expect(page).to have_content( + format( + _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + model_name: _('milestone'), + link_to_model: _('milestone') + ) + ) + end +end diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb index f09cf0613a1..5126e849c2e 100644 --- a/spec/support/shared_examples/features/packages_shared_examples.rb +++ b/spec/support/shared_examples/features/packages_shared_examples.rb @@ -9,7 +9,7 @@ RSpec.shared_examples 'packages list' do |check_project_name: false| expect(package_row).to have_content(pkg.name) expect(package_row).to have_content(pkg.version) - expect(package_row).to have_content(pkg.project.name) if check_project_name + expect(package_row).to have_content(pkg.project.path) if check_project_name end end @@ -18,7 +18,35 @@ RSpec.shared_examples 'packages list' do |check_project_name: false| end end +RSpec.shared_examples 'pipelines on packages list' do + let_it_be(:pipelines) do + %w[c83d6e391c22777fca1ed3012fce84f633d7fed0 + d83d6e391c22777fca1ed3012fce84f633d7fed0].map do |sha| + create(:ci_pipeline, project: project, sha: sha) + end + end + + before do + pipelines.each do |pipeline| + create(:package_build_info, package: package, pipeline: pipeline) + end + end + + it 'shows the latest pipeline' do + # Test after reload + page.evaluate_script 'window.location.reload()' + + wait_for_requests + + expect(page).to have_content('d83d6e39') + end +end + RSpec.shared_examples 'package details link' do |property| + before do + stub_application_setting(npm_package_requests_forwarding: false) + end + it 'navigates to the correct url' do page.within(packages_table_selector) do click_link package.name @@ -30,6 +58,45 @@ RSpec.shared_examples 'package details link' do |property| expect(page).to have_content('Installation') expect(page).to have_content('Registry setup') + expect(page).to have_content('Other versions 0') + end + + context 'with other versions' do + let_it_be(:npm_package1) { create(:npm_package, project: project, name: 'zzz', version: '1.1.0') } + let_it_be(:npm_package2) { create(:npm_package, project: project, name: 'zzz', version: '1.2.0') } + + before do + page.within(packages_table_selector) do + first(:link, package.name).click + end + end + + it 'shows tab with count' do + expect(page).to have_content('Other versions 2') + end + + it 'visiting tab shows total on page' do + click_link 'Other versions' + + expect(page).to have_content('2 versions') + end + + it 'deleting version updates count' do + click_link 'Other versions' + + find('[data-testid="delete-dropdown"]', match: :first).click + find('[data-testid="action-delete"]', match: :first).click + click_button('Permanently delete') + + expect(page).to have_content 'Package deleted successfully' + + expect(page).to have_content('Other versions 1') + expect(page).to have_content('1 version') + + expect(page).not_to have_content('1.0.0') + expect(page).to have_content('1.1.0') + expect(page).to have_content('1.2.0') + end end end diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb index 81d548e000a..2d3f1949716 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb @@ -1,126 +1,67 @@ # frozen_string_literal: true RSpec.shared_examples "protected branches > access control > CE" do - ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)| + let(:no_one) { ProtectedRef::AccessLevel.humanize(::Gitlab::Access::NO_ACCESS) } + + ProtectedRef::AccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can push to" do visit project_protected_branches_path(project) set_protected_branch_name('master') - - find(".js-allowed-to-merge").click - within('[data-testid="allowed-to-merge-dropdown"]') do - expect(first("li")).to have_content("Roles") - find(:link, 'No one').click - end - - within('.js-new-protected-branch') do - allowed_to_push_button = find(".js-allowed-to-push") - - unless allowed_to_push_button.text == access_type_name - allowed_to_push_button.click - within(".dropdown.show .dropdown-menu") { click_on access_type_name } - end - end - + set_allowed_to('merge', no_one) + set_allowed_to('push', access_type_name) click_on_protect - wait_for_requests expect(ProtectedBranch.count).to eq(1) expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to eq([access_type_id]) end - it "allows updating protected branches so that #{access_type_name} can push to them" do + it "allows creating protected branches that #{access_type_name} can merge to" do visit project_protected_branches_path(project) set_protected_branch_name('master') - - find(".js-allowed-to-merge").click - within('[data-testid="allowed-to-merge-dropdown"]') do - expect(first("li")).to have_content("Roles") - find(:link, 'No one').click - end - - find(".js-allowed-to-push").click - within('[data-testid="allowed-to-push-dropdown"]') do - expect(first("li")).to have_content("Roles") - find(:link, 'No one').click - end - + set_allowed_to('merge', access_type_name) + set_allowed_to('push', no_one) click_on_protect expect(ProtectedBranch.count).to eq(1) - - within(".protected-branches-list") do - find(".js-allowed-to-push").click - - within('.js-allowed-to-push-container') do - expect(first("li")).to have_content("Roles") - find(:link, access_type_name).click - end - - find(".js-allowed-to-push").click - end - - wait_for_requests - - expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id) + expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id]) end - end - ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)| - it "allows creating protected branches that #{access_type_name} can merge to" do + it "allows updating protected branches so that #{access_type_name} can push to them" do visit project_protected_branches_path(project) set_protected_branch_name('master') + set_allowed_to('merge', no_one) + set_allowed_to('push', no_one) + click_on_protect - within('.js-new-protected-branch') do - allowed_to_merge_button = find(".js-allowed-to-merge") + expect(ProtectedBranch.count).to eq(1) - unless allowed_to_merge_button.text == access_type_name - allowed_to_merge_button.click - within(".dropdown.show .dropdown-menu") { click_on access_type_name } + within(".protected-branches-list") do + within_select(".js-allowed-to-push") do + click_on(access_type_name) end end - find(".js-allowed-to-push").click - within('[data-testid="allowed-to-push-dropdown"]') do - expect(first("li")).to have_content("Roles") - find(:link, 'No one').click - end - - click_on_protect + wait_for_requests - expect(ProtectedBranch.count).to eq(1) - expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id]) + expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id) end it "allows updating protected branches so that #{access_type_name} can merge to them" do visit project_protected_branches_path(project) set_protected_branch_name('master') - - find(".js-allowed-to-merge").click - within('[data-testid="allowed-to-merge-dropdown"]') do - expect(first("li")).to have_content("Roles") - find(:link, 'No one').click - end - - find(".js-allowed-to-push").click - within('[data-testid="allowed-to-push-dropdown"]') do - expect(first("li")).to have_content("Roles") - find(:link, 'No one').click - end - + set_allowed_to('merge', no_one) + set_allowed_to('push', no_one) click_on_protect expect(ProtectedBranch.count).to eq(1) within(".protected-branches-list") do - find(".js-allowed-to-merge").click - - within('.js-allowed-to-merge-container') do - expect(first("li")).to have_content("Roles") - find(:link, access_type_name).click + within_select(".js-allowed-to-merge") do + click_on(access_type_name) end end diff --git a/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb new file mode 100644 index 00000000000..cc0984b6226 --- /dev/null +++ b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'Deploy keys with protected tags' do + let(:dropdown_sections_minus_deploy_keys) { all_dropdown_sections - ['Deploy Keys'] } + + context 'when deploy keys are enabled to this project' do + let!(:deploy_key_1) { create(:deploy_key, title: 'title 1', projects: [project]) } + let!(:deploy_key_2) { create(:deploy_key, title: 'title 2', projects: [project]) } + + context 'when only one deploy key can push' do + before do + deploy_key_1.deploy_keys_projects.first.update!(can_push: true) + end + + it "shows all dropdown sections in the 'Allowed to create' main dropdown, with only one deploy key" do + visit project_protected_tags_path(project) + + find(".js-allowed-to-create").click + wait_for_requests + + within('[data-testid="allowed-to-create-dropdown"]') do + dropdown_headers = page.all('.dropdown-header').map(&:text) + + expect(dropdown_headers).to contain_exactly(*all_dropdown_sections) + expect(page).to have_content('title 1') + expect(page).not_to have_content('title 2') + end + end + + it "shows all sections in the 'Allowed to create' update dropdown" do + create(:protected_tag, :no_one_can_create, project: project, name: 'v1.0.0') + + visit project_protected_tags_path(project) + + within(".js-protected-tag-edit-form") do + find(".js-allowed-to-create").click + wait_for_requests + + dropdown_headers = page.all('.dropdown-header').map(&:text) + + expect(dropdown_headers).to contain_exactly(*all_dropdown_sections) + end + end + end + + context 'when no deploy key can push' do + it "just shows all sections but not deploy keys in the 'Allowed to create' dropdown" do + visit project_protected_tags_path(project) + + find(".js-allowed-to-create").click + wait_for_requests + + within('[data-testid="allowed-to-create-dropdown"]') do + dropdown_headers = page.all('.dropdown-header').map(&:text) + + expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys) + end + end + end + end +end diff --git a/spec/support/shared_examples/features/reportable_note_shared_examples.rb b/spec/support/shared_examples/features/reportable_note_shared_examples.rb index bb3fab5b23e..133da230bed 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_button('Report abuse to administrator') + expect(dropdown).to have_button('Report abuse') 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_button('Report abuse to administrator') + dropdown.click_button('Report abuse') choose "They're posting spam." click_button "Next" @@ -48,6 +48,6 @@ RSpec.shared_examples 'reportable note' do |type| restore_window_size dropdown.find('.more-actions-toggle').click - dropdown.find('.dropdown-menu li', match: :first) + dropdown.find('.more-actions li', match: :first) end end diff --git a/spec/support/shared_examples/features/rss_shared_examples.rb b/spec/support/shared_examples/features/rss_shared_examples.rb index ad865b084e1..f6566214e32 100644 --- a/spec/support/shared_examples/features/rss_shared_examples.rb +++ b/spec/support/shared_examples/features/rss_shared_examples.rb @@ -13,6 +13,12 @@ RSpec.shared_examples "it has an RSS button with current_user's feed token" do end end +RSpec.shared_examples "it has an RSS link with current_user's feed token" do + it "shows the RSS link with current_user's feed token" do + expect(page).to have_link 'Subscribe to RSS feed', href: /feed_token=#{user.feed_token}/ + end +end + RSpec.shared_examples "an autodiscoverable RSS feed without a feed token" do it "has an RSS autodiscovery link tag without a feed token" do expect(page).to have_css("link[type*='atom+xml']:not([href*='feed_token'])", visible: false) @@ -26,10 +32,18 @@ RSpec.shared_examples "it has an RSS button without a feed token" do end end +RSpec.shared_examples "it has an RSS link without a feed token" do + it "shows the RSS link without a feed token" do + expect(page).to have_link 'Subscribe to RSS feed' + expect(page).not_to have_link 'Subscribe to RSS feed', href: /feed_token/ + end +end + RSpec.shared_examples "updates atom feed link" do |type| it "for #{type}" do sign_in(user) visit path + click_button 'Actions', match: :first link = find_link('Subscribe to RSS feed') params = CGI.parse(URI.parse(link[:href]).query) diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb index 63a0832117d..7edf306183e 100644 --- a/spec/support/shared_examples/features/runners_shared_examples.rb +++ b/spec/support/shared_examples/features/runners_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'shows and resets runner registration token' do - include Spec::Support::Helpers::Features::RunnersHelpers + include Features::RunnersHelpers include Spec::Support::Helpers::ModalHelpers before do @@ -63,16 +63,15 @@ RSpec.shared_examples 'shows and resets runner registration token' do end RSpec.shared_examples 'shows no runners registered' do - it 'shows total count with 0' do + it 'shows 0 count and the empty state' 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 + # "no runners" message expect(page).to have_text s_('Runners|Get started with runners') end end @@ -84,16 +83,14 @@ RSpec.shared_examples 'shows no runners found' do end RSpec.shared_examples 'shows runner in list' do - it 'does not show empty state' do - expect(page).not_to have_content s_('Runners|Get started with runners') - end - - it 'shows runner row' do + it 'shows runner row and no empty state' do within_runner_row(runner.id) do expect(page).to have_text "##{runner.id}" expect(page).to have_text runner.short_sha expect(page).to have_text runner.description end + + expect(page).not_to have_content s_('Runners|Get started with runners') end end @@ -229,3 +226,33 @@ RSpec.shared_examples 'submits edit runner form' do end end end + +RSpec.shared_examples 'creates runner and shows register page' do + context 'when runner is saved' do + before do + fill_in s_('Runners|Runner description'), with: 'runner-foo' + fill_in s_('Runners|Tags'), with: 'tag1' + click_on _('Submit') + wait_for_requests + end + + it 'navigates to registration page and opens install instructions drawer' do + expect(page.find('[data-testid="alert-success"]')).to have_content(s_('Runners|Runner created.')) + expect(current_url).to match(register_path_pattern) + + click_on 'How do I install GitLab Runner?' + expect(page.find('[data-testid="runner-platforms-drawer"]')).to have_content('gitlab-runner install') + end + + it 'warns from leaving page without finishing registration' do + click_on s_('Runners|Go to runners page') + + alert = page.driver.browser.switch_to.alert + + expect(alert).not_to be_nil + alert.dismiss + + expect(current_url).to match(register_path_pattern) + end + end +end diff --git a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb index 4d242d0e719..cbd0ffbab21 100644 --- a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb +++ b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb @@ -48,14 +48,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible issue' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'Issue', id: unreadable.id, ability: :read_issue } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Issue', id: unreadable.id, ability: :read_issue } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -95,16 +99,18 @@ RSpec.shared_examples 'a redacted search results' do end let(:unredacted_results) do - ar_relation(Note, - readable_note_on_commit, - readable_diff_note, - readable_note_on_mr, - readable_diff_note_on_mr, - readable_note_on_project_snippet, - unreadable_note_on_commit, - unreadable_diff_note, - unreadable_note_on_mr, - unreadable_note_on_project_snippet) + ar_relation( + Note, + readable_note_on_commit, + readable_diff_note, + readable_note_on_mr, + readable_diff_note_on_mr, + readable_note_on_project_snippet, + unreadable_note_on_commit, + unreadable_diff_note, + unreadable_note_on_mr, + unreadable_note_on_project_snippet + ) end let(:scope) { 'notes' } @@ -112,23 +118,29 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible notes' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'Note', id: unreadable_note_on_commit.id, ability: :read_note }, - { class_name: 'DiffNote', id: unreadable_diff_note.id, ability: :read_note }, - { class_name: 'DiscussionNote', id: unreadable_note_on_mr.id, ability: :read_note }, - { class_name: 'Note', id: unreadable_note_on_project_snippet.id, ability: :read_note } - ]))) - - expect(result).to contain_exactly(readable_note_on_commit, - readable_diff_note, - readable_note_on_mr, - readable_diff_note_on_mr, - readable_note_on_project_snippet) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Note', id: unreadable_note_on_commit.id, ability: :read_note }, + { class_name: 'DiffNote', id: unreadable_diff_note.id, ability: :read_note }, + { class_name: 'DiscussionNote', id: unreadable_note_on_mr.id, ability: :read_note }, + { class_name: 'Note', id: unreadable_note_on_project_snippet.id, ability: :read_note } + ] + ) + ) + ) + + expect(result).to contain_exactly( + readable_note_on_commit, + readable_diff_note, + readable_note_on_mr, + readable_diff_note_on_mr, + readable_note_on_project_snippet + ) end end @@ -141,14 +153,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible merge request' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'MergeRequest', id: unreadable.id, ability: :read_merge_request } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'MergeRequest', id: unreadable.id, ability: :read_merge_request } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -169,14 +185,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible blob' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'Gitlab::Search::FoundBlob', id: unreadable.id, ability: :read_blob } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Gitlab::Search::FoundBlob', id: unreadable.id, ability: :read_blob } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -191,14 +211,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible blob' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'Gitlab::Search::FoundWikiPage', id: unreadable.id, ability: :read_wiki_page } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Gitlab::Search::FoundWikiPage', id: unreadable.id, ability: :read_wiki_page } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -213,14 +237,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible snippet' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'ProjectSnippet', id: unreadable.id, ability: :read_snippet } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'ProjectSnippet', id: unreadable.id, ability: :read_snippet } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -239,14 +267,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible snippet' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'PersonalSnippet', id: unreadable.id, ability: :read_snippet } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'PersonalSnippet', id: unreadable.id, ability: :read_snippet } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -265,14 +297,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible commit' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'Commit', id: unreadable.id, ability: :read_commit } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Commit', id: unreadable.id, ability: :read_commit } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end diff --git a/spec/support/shared_examples/features/secure_oauth_authorizations_shared_examples.rb b/spec/support/shared_examples/features/secure_oauth_authorizations_shared_examples.rb index 028e075c87a..231406289b4 100644 --- a/spec/support/shared_examples/features/secure_oauth_authorizations_shared_examples.rb +++ b/spec/support/shared_examples/features/secure_oauth_authorizations_shared_examples.rb @@ -10,7 +10,7 @@ RSpec.shared_examples 'Secure OAuth Authorizations' do end context 'when user is unconfirmed' do - let(:user) { create(:user, confirmed_at: nil) } + let(:user) { create(:user, :unconfirmed) } it 'displays an error' do expect(page).to have_text I18n.t('doorkeeper.errors.messages.unconfirmed_email') diff --git a/spec/support/shared_examples/features/trial_email_validation_shared_example.rb b/spec/support/shared_examples/features/trial_email_validation_shared_example.rb index 8304a91af86..81c9ac1164b 100644 --- a/spec/support/shared_examples/features/trial_email_validation_shared_example.rb +++ b/spec/support/shared_examples/features/trial_email_validation_shared_example.rb @@ -1,59 +1,38 @@ # frozen_string_literal: true RSpec.shared_examples 'user email validation' do - let(:email_hint_message) { 'We recommend a work email address.' } - let(:email_error_message) { 'Please provide a valid email address.' } + let(:email_hint_message) { _('We recommend a work email address.') } + let(:email_error_message) { _('Please provide a valid email address.') } let(:email_warning_message) do - 'This email address does not look right, are you sure you typed it correctly?' + _('This email address does not look right, are you sure you typed it correctly?') end - context 'with trial_email_validation flag enabled' do - it 'shows an error message until a correct email is entered' do - visit path - expect(page).to have_content(email_hint_message) - expect(page).not_to have_content(email_error_message) - expect(page).not_to have_content(email_warning_message) + it 'shows an error message until a correct email is entered' do + visit path + expect(page).to have_content(email_hint_message) + expect(page).not_to have_content(email_error_message) + expect(page).not_to have_content(email_warning_message) - fill_in 'new_user_email', with: 'foo@' - fill_in 'new_user_first_name', with: '' + fill_in 'new_user_email', with: 'foo@' + fill_in 'new_user_first_name', with: '' - expect(page).not_to have_content(email_hint_message) - expect(page).to have_content(email_error_message) - expect(page).not_to have_content(email_warning_message) + expect(page).not_to have_content(email_hint_message) + expect(page).to have_content(email_error_message) + expect(page).not_to have_content(email_warning_message) - fill_in 'new_user_email', with: 'foo@bar' - fill_in 'new_user_first_name', with: '' + fill_in 'new_user_email', with: 'foo@bar' + fill_in 'new_user_first_name', with: '' - expect(page).not_to have_content(email_hint_message) - expect(page).not_to have_content(email_error_message) - expect(page).to have_content(email_warning_message) + expect(page).not_to have_content(email_hint_message) + expect(page).not_to have_content(email_error_message) + expect(page).to have_content(email_warning_message) - fill_in 'new_user_email', with: 'foo@gitlab.com' - fill_in 'new_user_first_name', with: '' + fill_in 'new_user_email', with: 'foo@gitlab.com' + fill_in 'new_user_first_name', with: '' - expect(page).not_to have_content(email_hint_message) - expect(page).not_to have_content(email_error_message) - expect(page).not_to have_content(email_warning_message) - end - end - - context 'when trial_email_validation flag disabled' do - before do - stub_feature_flags trial_email_validation: false - end - - it 'does not show an error message' do - visit path - expect(page).to have_content(email_hint_message) - expect(page).not_to have_content(email_error_message) - expect(page).not_to have_content(email_warning_message) - - fill_in 'new_user_email', with: 'foo@' - - expect(page).to have_content(email_hint_message) - expect(page).not_to have_content(email_error_message) - expect(page).not_to have_content(email_warning_message) - end + expect(page).not_to have_content(email_hint_message) + expect(page).not_to have_content(email_error_message) + expect(page).not_to have_content(email_warning_message) end end diff --git a/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb b/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb new file mode 100644 index 00000000000..0b0c9edcb42 --- /dev/null +++ b/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'variable list pagination' do |variable_type| + first_page_count = 20 + + before do + first_page_count.times do |i| + case variable_type + when :ci_variable + create(variable_type, key: "test_key_#{i}", value: 'test_value', masked: true, project: project) + when :ci_group_variable + create(variable_type, key: "test_key_#{i}", value: 'test_value', masked: true, group: group) + else + create(variable_type, key: "test_key_#{i}", value: 'test_value', masked: true) + end + end + + visit page_path + wait_for_requests + end + + it 'can navigate between pages' do + page.within('[data-testid="ci-variable-table"]') do + expect(page.all('.js-ci-variable-row').length).to be(first_page_count) + end + + click_button 'Next' + wait_for_requests + + page.within('[data-testid="ci-variable-table"]') do + expect(page.all('.js-ci-variable-row').length).to be(1) + end + + click_button 'Previous' + wait_for_requests + + page.within('[data-testid="ci-variable-table"]') do + expect(page.all('.js-ci-variable-row').length).to be(first_page_count) + end + end + + it 'sorts variables alphabetically in ASC and DESC order' do + page.within('[data-testid="ci-variable-table"]') do + expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key) + expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]').text).to eq('test_key_8') + end + + click_button 'Next' + wait_for_requests + + page.within('[data-testid="ci-variable-table"]') do + expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('test_key_9') + end + + page.within('[data-testid="ci-variable-table"]') do + find('.b-table-sort-icon-left').click + end + + wait_for_requests + + page.within('[data-testid="ci-variable-table"]') do + expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('test_key_9') + expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]').text).to eq('test_key_0') + end + end +end diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb index f0b72cfaee3..1211c9d19e6 100644 --- a/spec/support/shared_examples/features/variable_list_shared_examples.rb +++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'variable list' do |is_admin| +RSpec.shared_examples 'variable list' do it 'shows a list of variables' do page.within('[data-testid="ci-variable-table"]') do expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key) @@ -256,14 +256,6 @@ RSpec.shared_examples 'variable list' do |is_admin| expect(find('[data-testid="ci-variable-protected-checkbox"]')).to be_checked end end - - it 'shows a message regarding the changed default' do - if is_admin - expect(page).to have_content 'Environment variables on this GitLab instance are configured to be protected by default' - else - expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default' - end - end end context 'application setting is false' do 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 7a3b94ad81d..6451c531aec 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 @@ -62,7 +62,7 @@ RSpec.shared_examples 'wiki file attachments' do attach_with_dropzone(true) wait_for_requests - find('.js-md-preview-button').click + click_button("Preview") file_path = page.find('input[name="files[]"]', visible: :hidden).value link = page.find('a.no-attachment-icon')['href'] img_link = page.find('a.no-attachment-icon img')['src'] diff --git a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb index 3e285bb8ad7..ca68df9a89b 100644 --- a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb @@ -78,7 +78,7 @@ RSpec.shared_examples 'User previews wiki changes' do it_behaves_like 'relative links' do before do - click_on 'Preview' + click_button("Preview") end let(:element) { preview } @@ -88,7 +88,7 @@ RSpec.shared_examples 'User previews wiki changes' do # using two `\n` ensures we're sublist to it's own line due # to list auto-continue fill_in :wiki_content, with: "1. one\n\n - sublist\n" - click_on "Preview" + click_button("Preview") # the above generates two separate lists (not embedded) in CommonMark expect(preview).to have_content("sublist") @@ -102,7 +102,7 @@ RSpec.shared_examples 'User previews wiki changes' do [[also_do_not_linkify]] ``` HEREDOC - click_on "Preview" + click_button("Preview") expect(preview).to have_content("do_not_linkify") expect(preview).to have_content('[[do_not_linkify]]') diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb index 0334187e4b1..c1e4185e058 100644 --- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb @@ -150,6 +150,7 @@ RSpec.shared_examples 'User updates wiki page' do end it_behaves_like 'edits content using the content editor' + it_behaves_like 'inserts diagrams.net diagram using the content editor' it_behaves_like 'autocompletes items' end diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb index a7c32932ba7..767caffd417 100644 --- a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb @@ -9,9 +9,11 @@ RSpec.shared_examples 'User views a wiki page' do let(:path) { 'image.png' } let(:wiki_page) do - create(:wiki_page, - wiki: wiki, - title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})") + create( + :wiki_page, + wiki: wiki, + title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})" + ) end before do diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_sidebar_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_sidebar_shared_examples.rb index 639eb3f2b99..21c7e2b6c75 100644 --- a/spec/support/shared_examples/features/wiki/user_views_wiki_sidebar_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_views_wiki_sidebar_shared_examples.rb @@ -84,6 +84,44 @@ RSpec.shared_examples 'User views wiki sidebar' do expect(page).not_to have_link('View All Pages') end + it 'shows all collapse buttons in the sidebar' do + visit wiki_path(wiki) + + within('.right-sidebar') do + expect(page.all("[data-testid='chevron-down-icon']").size).to eq(3) + end + end + + it 'collapses/expands children when click collapse/expand button in the sidebar', :js do + visit wiki_path(wiki) + + within('.right-sidebar') do + first("[data-testid='chevron-down-icon']").click + (11..15).each { |i| expect(page).not_to have_content("my page #{i}") } + expect(page.all("[data-testid='chevron-down-icon']").size).to eq(1) + expect(page.all("[data-testid='chevron-right-icon']").size).to eq(1) + + first("[data-testid='chevron-right-icon']").click + (11..15).each { |i| expect(page).to have_content("my page #{i}") } + expect(page.all("[data-testid='chevron-down-icon']").size).to eq(3) + expect(page.all("[data-testid='chevron-right-icon']").size).to eq(0) + end + end + + it 'shows create child page button when hover to the page title in the sidebar', :js do + visit wiki_path(wiki) + + within('.right-sidebar') do + first_wiki_list = first("[data-testid='wiki-list']") + wiki_link = first("[data-testid='wiki-list'] a:last-of-type")['href'] + + first_wiki_list.hover + wiki_new_page_link = first("[data-testid='wiki-list'] a")['href'] + + expect(wiki_new_page_link).to eq "#{wiki_link}/%7Bnew_page_title%7D" + end + end + context 'when there are more than 15 existing pages' do before do create(:wiki_page, wiki: wiki, title: 'my page 16') diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb index 4f3d957ad71..526a56e7dab 100644 --- a/spec/support/shared_examples/features/work_items_shared_examples.rb +++ b/spec/support/shared_examples/features/work_items_shared_examples.rb @@ -1,5 +1,20 @@ # frozen_string_literal: true +RSpec.shared_examples 'work items title' do + let(:title_selector) { '[data-testid="work-item-title"]' } + + it 'successfully shows and changes the title of the work item' do + expect(work_item.reload.title).to eq work_item.title + + find(title_selector).set("Work item title") + find(title_selector).native.send_keys(:return) + + wait_for_requests + + expect(work_item.reload.title).to eq 'Work item title' + end +end + RSpec.shared_examples 'work items status' do let(:state_selector) { '[data-testid="work-item-state-select"]' } @@ -15,18 +30,110 @@ RSpec.shared_examples 'work items status' do end end -RSpec.shared_examples 'work items comments' do +RSpec.shared_examples 'work items comments' do |type| let(:form_selector) { '[data-testid="work-item-add-comment"]' } + let(:textarea_selector) { '[data-testid="work-item-add-comment"] #work-item-add-or-edit-comment' } + let(:is_mac) { page.evaluate_script('navigator.platform').include?('Mac') } + let(:modifier_key) { is_mac ? :command : :control } + let(:comment) { 'Test comment' } + + def set_comment + find(form_selector).fill_in(with: comment) + end it 'successfully creates and shows comments' do - click_button 'Add a comment' + set_comment - find(form_selector).fill_in(with: "Test comment") click_button "Comment" wait_for_requests - expect(page).to have_content "Test comment" + page.within(".main-notes-list") do + expect(page).to have_content comment + end + end + + context 'for work item note actions signed in user with developer role' do + it 'shows work item note actions' do + set_comment + + click_button "Comment" + + wait_for_requests + + page.within(".main-notes-list") do + expect(page).to have_selector('[data-testid="work-item-note-actions"]') + + find('[data-testid="work-item-note-actions"]', match: :first).click + + expect(page).to have_selector('[data-testid="copy-link-action"]') + expect(page).not_to have_selector('[data-testid="assign-note-action"]') + end + end + end + + it 'successfully posts comments using shortcut and checks if textarea is blank when reinitiated' do + set_comment + + send_keys([modifier_key, :enter]) + + wait_for_requests + + page.within(".main-notes-list") do + expect(page).to have_content comment + end + + expect(find(textarea_selector)).to have_content "" + end + + context 'when using quick actions' do + it 'autocompletes quick actions common to all work item types', :aggregate_failures do + click_reply_and_enter_slash + + page.within('#at-view-commands') do + expect(page).to have_text("/title") + expect(page).to have_text("/shrug") + expect(page).to have_text("/tableflip") + expect(page).to have_text("/close") + expect(page).to have_text("/cc") + end + end + + context 'when a widget is enabled' do + before do + WorkItems::Type.default_by_type(type).widget_definitions + .find_by_widget_type(:assignees).update!(disabled: false) + end + + it 'autocompletes quick action for the enabled widget' do + click_reply_and_enter_slash + + page.within('#at-view-commands') do + expect(page).to have_text("/assign") + end + end + end + + context 'when a widget is disabled' do + before do + WorkItems::Type.default_by_type(type).widget_definitions + .find_by_widget_type(:assignees).update!(disabled: true) + end + + it 'does not autocomplete quick action for the disabled widget' do + click_reply_and_enter_slash + + page.within('#at-view-commands') do + expect(page).not_to have_text("/assign") + end + end + end + + def click_reply_and_enter_slash + find(form_selector).fill_in(with: "/") + + wait_for_all_requests + end end end @@ -39,7 +146,6 @@ RSpec.shared_examples 'work items assignees' do # submit and simulate blur to save send_keys(:enter) find("body").click - wait_for_requests expect(work_item.assignees).to include(user) @@ -47,6 +153,8 @@ RSpec.shared_examples 'work items assignees' do end RSpec.shared_examples 'work items labels' do + let(:label_title_selector) { '[data-testid="labels-title"]' } + it 'successfully assigns a label' do label = create(:label, project: work_item.project, title: "testing-label") @@ -55,8 +163,7 @@ RSpec.shared_examples 'work items labels' do # submit and simulate blur to save send_keys(:enter) - find("body").click - + find(label_title_selector).click wait_for_requests expect(work_item.labels).to include(label) @@ -83,7 +190,7 @@ RSpec.shared_examples 'work items description' do wait_for_requests - page.within('.atwho-container') do + page.within('#at-view-commands') do expect(page).to have_text("title") expect(page).to have_text("shrug") expect(page).to have_text("tableflip") @@ -125,7 +232,7 @@ RSpec.shared_examples 'work items description' do end RSpec.shared_examples 'work items invite members' do - include Spec::Support::Helpers::Features::InviteMembersModalHelper + include Features::InviteMembersModalHelpers it 'successfully assigns the current user by searching' do # The button is only when the mouse is over the input @@ -139,3 +246,143 @@ RSpec.shared_examples 'work items invite members' do end end end + +RSpec.shared_examples 'work items milestone' do + def set_milestone(milestone_dropdown, milestone_text) + milestone_dropdown.click + + find('[data-testid="work-item-milestone-dropdown"] .gl-form-input', visible: true).send_keys "\"#{milestone_text}\"" + wait_for_requests + + click_button(milestone_text) + wait_for_requests + end + + let(:milestone_dropdown_selector) { '[data-testid="work-item-milestone-dropdown"]' } + + it 'searches and sets or removes milestone for the work item' do + set_milestone(find(milestone_dropdown_selector), milestone.title) + + expect(page.find(milestone_dropdown_selector)).to have_text(milestone.title) + + set_milestone(find(milestone_dropdown_selector), 'No milestone') + + expect(page.find(milestone_dropdown_selector)).to have_text('Add to milestone') + end +end + +RSpec.shared_examples 'work items comment actions for guest users' do + context 'for guest user' do + it 'hides other actions other than copy link' do + page.within(".main-notes-list") do + expect(page).to have_selector('[data-testid="work-item-note-actions"]') + + find('[data-testid="work-item-note-actions"]', match: :first).click + + expect(page).to have_selector('[data-testid="copy-link-action"]') + expect(page).not_to have_selector('[data-testid="assign-note-action"]') + end + end + end +end + +RSpec.shared_examples 'work items notifications' do + let(:actions_dropdown_selector) { '[data-testid="work-item-actions-dropdown"]' } + let(:notifications_toggle_selector) { '[data-testid="notifications-toggle-action"] > button' } + + it 'displays toast when notification is toggled' do + find(actions_dropdown_selector).click + + page.within('[data-testid="notifications-toggle-form"]') do + expect(page).not_to have_css(".is-checked") + + find(notifications_toggle_selector).click + wait_for_requests + + expect(page).to have_css(".is-checked") + end + + page.within('.gl-toast') do + expect(find('.toast-body')).to have_content(_('Notifications turned on.')) + end + end +end + +RSpec.shared_examples 'work items todos' do + let(:todos_action_selector) { '[data-testid="work-item-todos-action"]' } + let(:todos_icon_selector) { '[data-testid="work-item-todos-icon"]' } + let(:header_section_selector) { '[data-testid="work-item-body"]' } + + def toggle_todo_action + find(todos_action_selector).click + wait_for_requests + end + + it 'adds item to the list' do + page.within(header_section_selector) do + expect(find(todos_action_selector)['aria-label']).to eq('Add a to do') + + toggle_todo_action + + expect(find(todos_action_selector)['aria-label']).to eq('Mark as done') + end + + page.within ".header-content span[aria-label='#{_('Todos count')}']" do + expect(page).to have_content '1' + end + end + + it 'marks a todo as done' do + page.within(header_section_selector) do + toggle_todo_action + toggle_todo_action + end + + expect(find(todos_action_selector)['aria-label']).to eq('Add a to do') + expect(page).to have_selector(".header-content span[aria-label='#{_('Todos count')}']", visible: :hidden) + end +end + +RSpec.shared_examples 'work items award emoji' do + let(:award_section_selector) { '[data-testid="work-item-award-list"]' } + let(:award_action_selector) { '[data-testid="award-button"]' } + let(:selected_award_action_selector) { '[data-testid="award-button"].selected' } + let(:emoji_picker_action_selector) { '[data-testid="emoji-picker"]' } + let(:basketball_emoji_selector) { 'gl-emoji[data-name="basketball"]' } + + def select_emoji + first(award_action_selector).click + + wait_for_requests + end + + it 'adds award to the work item' do + within(award_section_selector) do + select_emoji + + expect(page).to have_selector(selected_award_action_selector) + expect(first(award_action_selector)).to have_content '1' + end + end + + it 'removes award from work item' do + within(award_section_selector) do + select_emoji + + expect(first(award_action_selector)).to have_content '1' + + select_emoji + + expect(first(award_action_selector)).to have_content '0' + end + end + + it 'add custom award to the work item' do + within(award_section_selector) do + find(emoji_picker_action_selector).click + find(basketball_emoji_selector).click + + expect(page).to have_selector(basketball_emoji_selector) + end + end +end diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb index 93f9e42241b..67fed00b5ca 100644 --- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb +++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb @@ -161,10 +161,12 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context 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') + create( + factory, + project: project1, + milestone: another_milestone, + title: 'another item' + ) end let(:params) { { not: { release_tag: release.tag, project_id: project1.id } } } @@ -421,8 +423,11 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context let!(:created_items) do milestones.map do |milestone| - create(factory, project: milestone.project || project_in_group, - milestone: milestone, author: user, assignees: [user]) + create( + factory, + project: milestone.project || project_in_group, + milestone: milestone, author: user, assignees: [user] + ) end end @@ -593,7 +598,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context end context 'filtering by no label' do - let(:params) { { label_name: described_class::Params::FILTER_NONE } } + let(:params) { { label_name: IssuableFinder::Params::FILTER_NONE } } it 'returns items with no labels' do expect(items).to contain_exactly(item1, item4, item5) @@ -601,7 +606,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context end context 'filtering by any label' do - let(:params) { { label_name: described_class::Params::FILTER_ANY } } + let(:params) { { label_name: IssuableFinder::Params::FILTER_ANY } } it 'returns items that have one or more label' do create_list(:label_link, 2, label: create(:label, project: project2), target: item3) @@ -909,9 +914,9 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context end 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) } + let_it_be(:incident_item) { create(factory, :incident, project: project1) } + let_it_be(:objective) { create(factory, :objective, project: project1) } + let_it_be(:key_result) { create(factory, :key_result, project: project1) } context 'no type given' do let(:params) { { issue_types: [] } } @@ -983,9 +988,9 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context let_it_be(:root_group) { create(:group) } let_it_be(:group) { create(:group, parent: root_group) } let_it_be(:project_crm) { create(:project, :public, group: group) } - let_it_be(:organization) { create(:organization, group: root_group) } - let_it_be(:contact1) { create(:contact, group: root_group, organization: organization) } - let_it_be(:contact2) { create(:contact, group: root_group, organization: organization) } + let_it_be(:crm_organization) { create(:crm_organization, group: root_group) } + let_it_be(:contact1) { create(:contact, group: root_group, organization: crm_organization) } + let_it_be(:contact2) { create(:contact, group: root_group, organization: crm_organization) } let_it_be(:contact1_item1) { create(factory, project: project_crm) } let_it_be(:contact1_item2) { create(factory, project: project_crm) } @@ -1023,10 +1028,10 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context end context 'filtering by crm organization' do - let(:params) { { project_id: project_crm.id, crm_organization_id: organization.id } } + let(:params) { { project_id: project_crm.id, crm_organization_id: crm_organization.id } } context 'when the user can read crm organization' do - it 'returns for that organization' do + it 'returns for that crm organization' do root_group.add_reporter(user) expect(items).to contain_exactly(contact1_item1, contact1_item2, contact2_item1) @@ -1034,7 +1039,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context end context 'when the user can not read crm organization' do - it 'does not filter by organization' do + it 'does not filter by crm organization' do expect(items).to match_array(all_project_issues) end end diff --git a/spec/support/shared_examples/graphql/members_shared_examples.rb b/spec/support/shared_examples/graphql/members_shared_examples.rb index 5cba8baa829..5ab17f5a49d 100644 --- a/spec/support/shared_examples/graphql/members_shared_examples.rb +++ b/spec/support/shared_examples/graphql/members_shared_examples.rb @@ -39,8 +39,10 @@ RSpec.shared_examples 'querying members with a group' do let(:base_args) { { relations: described_class.arguments['relations'].default_value } } subject do - resolve(described_class, obj: resource, args: base_args.merge(args), - ctx: { current_user: user_4 }, arg_style: :internal) + resolve( + described_class, obj: resource, args: base_args.merge(args), + ctx: { current_user: user_4 }, arg_style: :internal + ) end describe '#resolve' do @@ -83,8 +85,10 @@ RSpec.shared_examples 'querying members with a group' do let_it_be(:other_user) { create(:user) } subject do - resolve(described_class, obj: resource, args: base_args.merge(args), - ctx: { current_user: other_user }, arg_style: :internal) + resolve( + described_class, obj: resource, args: base_args.merge(args), + ctx: { current_user: other_user }, arg_style: :internal + ) end it 'generates an error' do diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb index dc590e23ace..808fb097f29 100644 --- a/spec/support/shared_examples/graphql/mutation_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples 'a mutation that returns top-level errors' do |errors: []| expect(graphql_errors).to be_present - error_messages = graphql_errors.map { |e| e['message'] } + error_messages = graphql_errors.pluck('message') expect(error_messages).to match_errors end @@ -25,7 +25,7 @@ end # the mutation. RSpec.shared_examples 'a mutation that returns a top-level access error' do include_examples 'a mutation that returns top-level errors', - errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] end RSpec.shared_examples 'an invalid argument to the mutation' do |argument_name:| diff --git a/spec/support/shared_examples/graphql/mutations/members/bulk_update_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/members/bulk_update_shared_examples.rb new file mode 100644 index 00000000000..e885b5d283e --- /dev/null +++ b/spec/support/shared_examples/graphql/mutations/members/bulk_update_shared_examples.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'members bulk update mutation' do + let_it_be(:current_user) { create(:user) } + let_it_be(:user1) { create(:user) } + let_it_be(:user2) { create(:user) } + let_it_be(:member1) { create(member_type, source: source, user: user1) } + let_it_be(:member2) { create(member_type, source: source, user: user2) } + + let(:extra_params) { { expires_at: 10.days.from_now } } + let(:input_params) { input.merge(extra_params) } + let(:mutation) { graphql_mutation(mutation_name, input_params) } + let(:mutation_response) { graphql_mutation_response(mutation_name) } + + let(:input) do + { + source_id_key => source.to_global_id.to_s, + 'user_ids' => [user1.to_global_id.to_s, user2.to_global_id.to_s], + 'access_level' => 'GUEST' + } + end + + context 'when user is not logged-in' do + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when user is not an owner' do + before do + source.add_developer(current_user) + end + + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when user is an owner' do + before do + source.add_owner(current_user) + end + + shared_examples 'updates the user access role' do + specify do + post_graphql_mutation(mutation, current_user: current_user) + + new_access_levels = mutation_response[response_member_field].map do |member| + member['accessLevel']['integerValue'] + end + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['errors']).to be_empty + expect(new_access_levels).to all(be Gitlab::Access::GUEST) + end + end + + it_behaves_like 'updates the user access role' + + context 'when inherited members are passed' do + let(:input) do + { + source_id_key => source.to_global_id.to_s, + 'user_ids' => [user1.to_global_id.to_s, user2.to_global_id.to_s, parent_group_member.user.to_global_id.to_s], + 'access_level' => 'GUEST' + } + end + + it 'does not update the members' do + post_graphql_mutation(mutation, current_user: current_user) + + error = Mutations::Members::BulkUpdateBase::INVALID_MEMBERS_ERROR + expect(json_response['errors'].first['message']).to include(error) + end + end + + context 'when members count is more than the allowed limit' do + let(:max_members_update_limit) { 1 } + + before do + stub_const('Mutations::Members::BulkUpdateBase::MAX_MEMBERS_UPDATE_LIMIT', max_members_update_limit) + end + + it 'does not update the members' do + post_graphql_mutation(mutation, current_user: current_user) + + error = Mutations::Members::BulkUpdateBase::MAX_MEMBERS_UPDATE_ERROR + expect(json_response['errors'].first['message']).to include(error) + end + end + + context 'when the update service raises access denied error' do + before do + allow_next_instance_of(Members::UpdateService) do |instance| + allow(instance).to receive(:execute).and_raise(Gitlab::Access::AccessDeniedError) + end + end + + it 'does not update the members' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response[response_member_field]).to be_nil + expect(mutation_response['errors']) + .to contain_exactly("Unable to update members, please check user permissions.") + end + end + + context 'when the update service returns an error message' do + before do + allow_next_instance_of(Members::UpdateService) do |instance| + error_result = { + message: 'Expires at cannot be a date in the past', + status: :error, + members: [member1] + } + allow(instance).to receive(:execute).and_return(error_result) + end + end + + it 'will pass through the error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response[response_member_field].first['id']).to eq(member1.to_global_id.to_s) + expect(mutation_response['errors']).to contain_exactly('Expires at cannot be a date in the past') + end + end + end +end diff --git a/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb index 022e2308517..3b9dadf2e80 100644 --- a/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb @@ -16,10 +16,12 @@ RSpec.shared_examples 'an assignable resource' do let(:mode) { described_class.arguments['operationMode'].default_value } subject do - mutation.resolve(project_path: resource.project.full_path, - iid: resource.iid, - operation_mode: mode, - assignee_usernames: assignee_usernames) + mutation.resolve( + project_path: resource.project.full_path, + iid: resource.iid, + operation_mode: mode, + assignee_usernames: assignee_usernames + ) end it 'raises an error if the resource is not accessible to the user' do diff --git a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb index 0d2e9f6ec8c..99d122e8254 100644 --- a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb +++ b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb @@ -4,9 +4,11 @@ RSpec.shared_context 'exposing regular notes on a noteable in GraphQL' do include GraphqlHelpers let(:note) do - create(:note, - noteable: noteable, - project: (noteable.project if noteable.respond_to?(:project))) + create( + :note, + noteable: noteable, + project: (noteable.project if noteable.respond_to?(:project)) + ) end let(:user) { note.author } @@ -46,7 +48,7 @@ RSpec.shared_context 'exposing regular notes on a noteable in GraphQL' do discussions { edges { node { - #{all_graphql_fields_for('Discussion', max_depth: 4)} + #{all_graphql_fields_for('Discussion', max_depth: 4, excluded: ['productAnalyticsState'])} } } } diff --git a/spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb b/spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb new file mode 100644 index 00000000000..52908c5b6df --- /dev/null +++ b/spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'work item supports assignee widget updates via quick actions' do + let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } } + + context 'when assigning a user' do + let(:body) { "/assign @#{developer.username}" } + + it 'updates the work item assignee' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + noteable.reload + end.to change { noteable.assignee_ids }.from([]).to([developer.id]) + + expect(response).to have_gitlab_http_status(:success) + end + end + + context 'when unassigning a user' do + let(:body) { "/unassign @#{developer.username}" } + + before do + noteable.update!(assignee_ids: [developer.id]) + end + + it 'updates the work item assignee' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + noteable.reload + end.to change { noteable.assignee_ids }.from([developer.id]).to([]) + + expect(response).to have_gitlab_http_status(:success) + end + end +end + +RSpec.shared_examples 'work item does not support assignee widget updates via quick actions' do + let(:developer) { create(:user).tap { |user| project.add_developer(user) } } + let(:body) { "Updating assignee.\n/assign @#{developer.username}" } + + before do + WorkItems::Type.default_by_type(:task).widget_definitions + .find_by_widget_type(:assignees).update!(disabled: true) + end + + it 'ignores the quick action' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + noteable.reload + end.not_to change { noteable.assignee_ids } + end +end + +RSpec.shared_examples 'work item supports labels widget updates via quick actions' do + shared_examples 'work item labels are updated' do + it do + expect do + post_graphql_mutation(mutation, current_user: current_user) + noteable.reload + end.to change { noteable.labels.count }.to(expected_labels.count) + + expect(noteable.labels).to match_array(expected_labels) + end + end + + let_it_be(:existing_label) { create(:label, project: project) } + let_it_be(:label1) { create(:label, project: project) } + let_it_be(:label2) { create(:label, project: project) } + + let(:add_label_ids) { [] } + let(:remove_label_ids) { [] } + + before_all do + noteable.update!(labels: [existing_label]) + end + + context 'when only removing labels' do + let(:remove_label_ids) { [existing_label.to_gid.to_s] } + let(:expected_labels) { [] } + let(:body) { "/remove_label ~\"#{existing_label.name}\"" } + + it_behaves_like 'work item labels are updated' + end + + context 'when only adding labels' do + let(:add_label_ids) { [label1.to_gid.to_s, label2.to_gid.to_s] } + let(:expected_labels) { [label1, label2, existing_label] } + let(:body) { "/labels ~\"#{label1.name}\" ~\"#{label2.name}\"" } + + it_behaves_like 'work item labels are updated' + end + + context 'when adding and removing labels' do + let(:remove_label_ids) { [existing_label.to_gid.to_s] } + let(:add_label_ids) { [label1.to_gid.to_s, label2.to_gid.to_s] } + let(:expected_labels) { [label1, label2] } + let(:body) { "/label ~\"#{label1.name}\" ~\"#{label2.name}\"\n/remove_label ~\"#{existing_label.name}\"" } + + it_behaves_like 'work item labels are updated' + end +end + +RSpec.shared_examples 'work item does not support labels widget updates via quick actions' do + let(:label1) { create(:label, project: project) } + let(:body) { "Updating labels.\n/labels ~\"#{label1.name}\"" } + + before do + WorkItems::Type.default_by_type(:task).widget_definitions + .find_by_widget_type(:labels).update!(disabled: true) + end + + it 'ignores the quick action' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + noteable.reload + end.not_to change { noteable.labels.count } + + expect(noteable.labels).to be_empty + end +end + +RSpec.shared_examples 'work item supports start and due date widget updates via quick actions' do + let(:due_date) { Date.today } + let(:body) { "/remove_due_date" } + + before do + noteable.update!(due_date: due_date) + end + + it 'updates start and due date' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + noteable.reload + end.to not_change(noteable, :start_date).and( + change { noteable.due_date }.from(due_date).to(nil) + ) + end +end + +RSpec.shared_examples 'work item does not support start and due date widget updates via quick actions' do + let(:body) { "Updating due date.\n/due today" } + + before do + WorkItems::Type.default_by_type(:task).widget_definitions + .find_by_widget_type(:start_and_due_date).update!(disabled: true) + end + + it 'ignores the quick action' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + noteable.reload + end.not_to change { noteable.due_date } + end +end + +RSpec.shared_examples 'work item supports type change via quick actions' do + let_it_be(:assignee) { create(:user) } + let_it_be(:task_type) { WorkItems::Type.default_by_type(:task) } + + let(:body) { "Updating type.\n/type Issue" } + + before do + noteable.update!(work_item_type: task_type, issue_type: task_type.base_type) + end + + it 'updates type' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + noteable.reload + end.to change { noteable.work_item_type.base_type }.from('task').to('issue') + + expect(response).to have_gitlab_http_status(:success) + end + + context 'when quick command for unsupported widget is present' do + let(:body) { "\n/type Issue\n/assign @#{assignee.username}" } + + before do + WorkItems::Type.default_by_type(:issue).widget_definitions + .find_by_widget_type(:assignees).update!(disabled: true) + end + + it 'updates only type' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + noteable.reload + end.to change { noteable.work_item_type.base_type }.from('task').to('issue') + .and change { noteable.assignees }.to([]) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['errors']) + .to include("Commands only Type changed successfully. Assigned @#{assignee.username}.") + end + end +end diff --git a/spec/support/shared_examples/graphql/resolvers/data_transfer_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/resolvers/data_transfer_resolver_shared_examples.rb new file mode 100644 index 00000000000..8551bd052ce --- /dev/null +++ b/spec/support/shared_examples/graphql/resolvers/data_transfer_resolver_shared_examples.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'Data transfer resolver' do + it 'returns mock data' do |_query_object| + mocked_data = ['mocked_data'] + + allow_next_instance_of(DataTransfer::MockedTransferFinder) do |instance| + allow(instance).to receive(:execute).and_return(mocked_data) + end + + expect(resolve_egress[:egress_nodes]).to eq(mocked_data) + end + + context 'when data_transfer_monitoring is disabled' do + before do + stub_feature_flags(data_transfer_monitoring: false) + end + + it 'returns empty result' do + expect(resolve_egress).to eq(egress_nodes: []) + end + end +end diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb index 4dc2ce61c4d..b346f35bdc9 100644 --- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb +++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb @@ -65,7 +65,7 @@ RSpec.shared_examples 'Gitlab-style deprecations' do deprecable = subject(deprecated: { milestone: '1.10', reason: :alpha }) expect(deprecable.deprecation_reason).to eq( - 'This feature is in Alpha. It can be changed or removed at any time. Introduced in 1.10.' + 'This feature is an Experiment. It can be changed or removed at any time. Introduced in 1.10.' ) end @@ -73,7 +73,7 @@ RSpec.shared_examples 'Gitlab-style deprecations' do deprecable = subject(alpha: { milestone: '1.10' }) expect(deprecable.deprecation_reason).to eq( - 'This feature is in Alpha. It can be changed or removed at any time. Introduced in 1.10.' + 'This feature is an Experiment. It can be changed or removed at any time. Introduced in 1.10.' ) end @@ -82,7 +82,7 @@ RSpec.shared_examples 'Gitlab-style deprecations' do subject(alpha: { milestone: '1.10' }, deprecated: { milestone: '1.10', reason: 'my reason' } ) end.to raise_error( ArgumentError, - eq("`alpha` and `deprecated` arguments cannot be passed at the same time") + eq("`experiment` and `deprecated` arguments cannot be passed at the same time") ) end diff --git a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb index bb33a7559dc..3dffc2066ae 100644 --- a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb +++ b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb @@ -42,8 +42,13 @@ RSpec.shared_examples "a user type with merge request interaction type" do profileEnableGitpodPath savedReplies savedReply + user_achievements ] + # TODO: 'workspaces' needs to be included, but only when this spec is run in EE context, to account for the + # ee-only extension in ee/app/graphql/ee/types/user_interface.rb. Not sure how else to handle this. + expected_fields << 'workspaces' if Gitlab.ee? + expect(described_class).to have_graphql_fields(*expected_fields) end diff --git a/spec/support/shared_examples/helpers/callouts_for_web_hooks.rb b/spec/support/shared_examples/helpers/callouts_for_web_hooks.rb new file mode 100644 index 00000000000..b3d3000aa06 --- /dev/null +++ b/spec/support/shared_examples/helpers/callouts_for_web_hooks.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'CalloutsHelper#web_hook_disabled_dismissed shared examples' do + context 'when the web-hook failure callout has never been dismissed' do + it 'is false' do + expect(helper).not_to be_web_hook_disabled_dismissed(container) + end + end + + context 'when the web-hook failure callout has been dismissed', :freeze_time, :clean_gitlab_redis_shared_state do + before do + create(factory, + feature_name: Users::CalloutsHelper::WEB_HOOK_DISABLED, + user: user, + dismissed_at: 1.week.ago, + container_key => container) + end + + it 'is true' do + expect(helper).to be_web_hook_disabled_dismissed(container) + end + + it 'is true when passed as a presenter' do + skip "Does not apply to #{container.class}" unless container.is_a?(Presentable) + + expect(helper).to be_web_hook_disabled_dismissed(container.present) + end + + context 'when there was an older failure' do + before do + Gitlab::Redis::SharedState.with { |r| r.set(key, 1.month.ago.iso8601) } + end + + it 'is true' do + expect(helper).to be_web_hook_disabled_dismissed(container) + end + end + + context 'when there has been a more recent failure' do + before do + Gitlab::Redis::SharedState.with { |r| r.set(key, 1.day.ago.iso8601) } + end + + it 'is false' do + expect(helper).not_to be_web_hook_disabled_dismissed(container) + end + end + end +end diff --git a/spec/support/shared_examples/integrations/integration_settings_form.rb b/spec/support/shared_examples/integrations/integration_settings_form.rb index aeb4e0feb12..c43bdfa53ff 100644 --- a/spec/support/shared_examples/integrations/integration_settings_form.rb +++ b/spec/support/shared_examples/integrations/integration_settings_form.rb @@ -2,12 +2,16 @@ RSpec.shared_examples 'integration settings form' do include IntegrationsHelper + + before do + stub_feature_flags(remove_monitor_metrics: false) + end + # Note: these specs don't validate channel fields # which are present on a few integrations it 'displays all the integrations', feature_category: :integrations do aggregate_failures do integrations.each do |integration| - stub_feature_flags(integration_slack_app_notifications: false) navigate_to_integration(integration) page.within('form.integration-settings-form') do diff --git a/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb b/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb new file mode 100644 index 00000000000..7ace223723c --- /dev/null +++ b/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'delegates AI request to Workhorse' do |provider_flag| + context "when #{provider_flag} is disabled" do + before do + stub_feature_flags(provider_flag => false) + end + + it 'responds as not found' do + post api(url, current_user), params: input_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when ai_experimentation_api is disabled' do + before do + stub_feature_flags(ai_experimentation_api: false) + end + + it 'responds as not found' do + post api(url, current_user), params: input_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + it 'responds with Workhorse send-url headers' do + post api(url, current_user), params: input_params + + expect(response.body).to eq('""') + expect(response).to have_gitlab_http_status(:ok) + + send_url_prefix, encoded_data = response.headers['Gitlab-Workhorse-Send-Data'].split(':') + data = Gitlab::Json.parse(Base64.urlsafe_decode64(encoded_data)) + + expect(send_url_prefix).to eq('send-url') + expect(data).to eq({ + 'AllowRedirects' => false, + 'Method' => 'POST' + }.merge(expected_params)) + end +end diff --git a/spec/support/shared_examples/lib/api/terraform_state_enabled_shared_examples.rb b/spec/support/shared_examples/lib/api/terraform_state_enabled_shared_examples.rb new file mode 100644 index 00000000000..b88eade7db2 --- /dev/null +++ b/spec/support/shared_examples/lib/api/terraform_state_enabled_shared_examples.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'it depends on value of the `terraform_state.enabled` config' do |params = {}| + let(:expected_success_status) { params[:success_status] || :ok } + + context 'when terraform_state.enabled=false' do + before do + stub_config(terraform_state: { enabled: false }) + end + + it 'returns `forbidden` response' do + request + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when terraform_state.enabled=true' do + before do + stub_config(terraform_state: { enabled: true }) + end + + it 'returns a successful response' do + request + + expect(response).to have_gitlab_http_status(expected_success_status) + end + end +end 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 d471a758f3e..c8d62205c1e 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 @@ -1,14 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'deployment metrics examples' do - def create_deployment(args) - project = args[:project] - environment = project.environments.production.first || create(:environment, :production, project: project) - create(:deployment, :success, args.merge(environment: environment)) - - # this is needed for the DORA API so we have aggregated data - ::Dora::DailyMetrics::RefreshWorker.new.perform(environment.id, Time.current.to_date.to_s) if Gitlab.ee? - end + include CycleAnalyticsHelpers describe "#deploys" do subject { stage_summary.third } diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb index bce889b454d..5740adb3f0e 100644 --- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb @@ -68,3 +68,64 @@ RSpec.shared_examples_for 'LEFT JOIN-able value stream analytics event' do end end end + +RSpec.shared_examples_for 'value stream analytics first assignment event methods' do + let_it_be(:model1) { create(model_factory) } # rubocop: disable Rails/SaveBang + let_it_be(:model2) { create(model_factory) } # rubocop: disable Rails/SaveBang + + let_it_be(:assignment_event1) do + create(event_factory, action: :add, created_at: 3.years.ago, model_factory => model1) + end + + let_it_be(:assignment_event2) do + create(event_factory, action: :add, created_at: 2.years.ago, model_factory => model1) + end + + let_it_be(:unassignment_event1) do + create(event_factory, action: :remove, created_at: 1.year.ago, model_factory => model1) + end + + let(:query) { model1.class.where(id: [model1.id, model2.id]) } + let(:event) { described_class.new({}) } + + describe '#apply_query_customization' do + subject(:records) { event.apply_query_customization(query).pluck(:id, *event.column_list).to_a } + + it 'looks up the first assignment event timestamp' do + expect(records).to match_array([[model1.id, be_within(1.second).of(assignment_event1.created_at)]]) + end + end + + describe '#apply_negated_query_customization' do + subject(:records) { event.apply_negated_query_customization(query).pluck(:id).to_a } + + it 'returns records where the event has not happened yet' do + expect(records).to eq([model2.id]) + end + end + + describe '#include_in' do + subject(:records) { event.include_in(query).pluck(:id, *event.column_list).to_a } + + it 'returns both records' do + expect(records).to match_array([ + [model1.id, be_within(1.second).of(assignment_event1.created_at)], + [model2.id, nil] + ]) + end + + context 'when invoked multiple times' do + subject(:records) do + scope = event.include_in(query) + event.include_in(scope).pluck(:id, *event.column_list).to_a + end + + it 'returns both records' do + expect(records).to match_array([ + [model1.id, be_within(1.second).of(assignment_event1.created_at)], + [model2.id, nil] + ]) + end + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/async_constraints_validation_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/async_constraints_validation_shared_examples.rb new file mode 100644 index 00000000000..b9d71183851 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/async_constraints_validation_shared_examples.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'async constraints validation' do + include ExclusiveLeaseHelpers + + let!(:lease) { stub_exclusive_lease(lease_key, :uuid, timeout: lease_timeout) } + let(:lease_key) { "gitlab/database/asyncddl/actions/#{Gitlab::Database::PRIMARY_DATABASE_NAME}" } + let(:lease_timeout) { described_class::TIMEOUT_PER_ACTION } + + let(:constraints_model) { Gitlab::Database::AsyncConstraints::PostgresAsyncConstraintValidation } + let(:table_name) { '_test_async_constraints' } + let(:constraint_name) { 'constraint_parent_id' } + + let(:validation) do + create(:postgres_async_constraint_validation, + table_name: table_name, + name: constraint_name, + constraint_type: constraint_type) + end + + let(:connection) { validation.connection } + + subject { described_class.new(validation) } + + it 'validates the constraint while controlling statement timeout' do + allow(connection).to receive(:execute).and_call_original + expect(connection).to receive(:execute) + .with("SET statement_timeout TO '43200s'").ordered.and_call_original + expect(connection).to receive(:execute) + .with(/ALTER TABLE "#{table_name}" VALIDATE CONSTRAINT "#{constraint_name}";/).ordered.and_call_original + expect(connection).to receive(:execute) + .with("RESET statement_timeout").ordered.and_call_original + + subject.perform + end + + it 'removes the constraint validation record from table' do + expect(validation).to receive(:destroy!).and_call_original + + expect { subject.perform }.to change { constraints_model.count }.by(-1) + end + + it 'skips logic if not able to acquire exclusive lease' do + expect(lease).to receive(:try_obtain).ordered.and_return(false) + expect(connection).not_to receive(:execute).with(/ALTER TABLE/) + expect(validation).not_to receive(:destroy!) + + expect { subject.perform }.not_to change { constraints_model.count } + end + + it 'logs messages around execution' do + allow(Gitlab::AppLogger).to receive(:info).and_call_original + + subject.perform + + expect(Gitlab::AppLogger) + .to have_received(:info) + .with(a_hash_including(message: 'Starting to validate constraint')) + + expect(Gitlab::AppLogger) + .to have_received(:info) + .with(a_hash_including(message: 'Finished validating constraint')) + end + + context 'when the constraint does not exist' do + before do + connection.create_table(table_name, force: true) + end + + it 'skips validation and removes the record' do + expect(connection).not_to receive(:execute).with(/ALTER TABLE/) + + expect { subject.perform }.to change { constraints_model.count }.by(-1) + end + + it 'logs an appropriate message' do + expected_message = /Skipping #{constraint_name} validation since it does not exist/ + + allow(Gitlab::AppLogger).to receive(:info).and_call_original + + subject.perform + + expect(Gitlab::AppLogger) + .to have_received(:info) + .with(a_hash_including(message: expected_message)) + end + end + + context 'with error handling' do + before do + allow(connection).to receive(:execute).and_call_original + + allow(connection).to receive(:execute) + .with(/ALTER TABLE "#{table_name}" VALIDATE CONSTRAINT "#{constraint_name}";/) + .and_raise(ActiveRecord::StatementInvalid) + end + + context 'on production' do + before do + allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false) + end + + it 'increases execution attempts' do + expect { subject.perform }.to change { validation.attempts }.by(1) + + expect(validation.last_error).to be_present + expect(validation).not_to be_destroyed + end + + it 'logs an error message including the constraint_name' do + expect(Gitlab::AppLogger) + .to receive(:error) + .with(a_hash_including(:message, :constraint_name)) + .and_call_original + + subject.perform + end + end + + context 'on development' do + it 'also raises errors' do + expect { subject.perform } + .to raise_error(ActiveRecord::StatementInvalid) + .and change { validation.attempts }.by(1) + + expect(validation.last_error).to be_present + expect(validation).not_to be_destroyed + end + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/index_validators_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/index_validators_shared_examples.rb new file mode 100644 index 00000000000..6f0cede7130 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/index_validators_shared_examples.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples "index validators" do |validator, expected_result| + let(:structure_file_path) { Rails.root.join('spec/fixtures/structure.sql') } + let(:database_indexes) do + [ + ['wrong_index', 'CREATE UNIQUE INDEX wrong_index ON public.table_name (column_name)'], + ['extra_index', 'CREATE INDEX extra_index ON public.table_name (column_name)'], + ['index', 'CREATE UNIQUE INDEX "index" ON public.achievements USING btree (namespace_id, lower(name))'] + ] + end + + let(:inconsistency_type) { validator.name.demodulize.underscore } + + let(:database_name) { 'main' } + + let(:database_model) { Gitlab::Database.database_base_models[database_name] } + + let(:connection) { database_model.connection } + + let(:schema) { connection.current_schema } + + let(:database) { Gitlab::Database::SchemaValidation::Database.new(connection) } + let(:structure_file) { Gitlab::Database::SchemaValidation::StructureSql.new(structure_file_path, schema) } + + subject(:result) { validator.new(structure_file, database).execute } + + before do + allow(connection).to receive(:select_rows).and_return(database_indexes) + end + + it 'returns index inconsistencies' do + expect(result.map(&:object_name)).to match_array(expected_result) + expect(result.map(&:type)).to all(eql inconsistency_type) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/schema_objects_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/schema_objects_shared_examples.rb new file mode 100644 index 00000000000..ec7a881f7ce --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/schema_objects_shared_examples.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples "schema objects assertions for" do |stmt_name| + let(:stmt) { PgQuery.parse(statement).tree.stmts.first.stmt } + let(:schema_object) { described_class.new(stmt.public_send(stmt_name)) } + + describe '#name' do + it 'returns schema object name' do + expect(schema_object.name).to eq(name) + end + end + + describe '#statement' do + it 'returns schema object statement' do + expect(schema_object.statement).to eq(statement) + end + end + + describe '#table_name' do + it 'returns schema object table_name' do + expect(schema_object.table_name).to eq(table_name) + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/table_validators_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/table_validators_shared_examples.rb new file mode 100644 index 00000000000..96e58294675 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/table_validators_shared_examples.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples "table validators" do |validator, expected_result| + subject(:result) { validator.new(structure_file, database).execute } + + let(:structure_file_path) { Rails.root.join('spec/fixtures/structure.sql') } + let(:inconsistency_type) { validator.name.demodulize.underscore } + let(:database_model) { Gitlab::Database.database_base_models['main'] } + let(:connection) { database_model.connection } + let(:schema) { connection.current_schema } + let(:database) { Gitlab::Database::SchemaValidation::Database.new(connection) } + let(:structure_file) { Gitlab::Database::SchemaValidation::StructureSql.new(structure_file_path, schema) } + let(:database_tables) do + [ + { + 'table_name' => 'wrong_table', + 'column_name' => 'id', + 'not_null' => true, + 'data_type' => 'integer', + 'column_default' => "nextval('audit_events_id_seq'::regclass)" + }, + { + 'table_name' => 'wrong_table', + 'column_name' => 'description', + 'not_null' => true, + 'data_type' => 'character varying', + 'column_default' => nil + }, + { + 'table_name' => 'extra_table', + 'column_name' => 'id', + 'not_null' => true, + 'data_type' => 'integer', + 'column_default' => "nextval('audit_events_id_seq'::regclass)" + }, + { + 'table_name' => 'extra_table', + 'column_name' => 'email', + 'not_null' => true, + 'data_type' => 'character varying', + 'column_default' => nil + }, + { + 'table_name' => 'extra_table_columns', + 'column_name' => 'id', + 'not_null' => true, + 'data_type' => 'bigint', + 'column_default' => "nextval('audit_events_id_seq'::regclass)" + }, + { + 'table_name' => 'extra_table_columns', + 'column_name' => 'name', + 'not_null' => true, + 'data_type' => 'character varying(255)', + 'column_default' => nil + }, + { + 'table_name' => 'extra_table_columns', + 'column_name' => 'extra_column', + 'not_null' => true, + 'data_type' => 'character varying(255)', + 'column_default' => nil + }, + { + 'table_name' => 'missing_table_columns', + 'column_name' => 'id', + 'not_null' => true, + 'data_type' => 'bigint', + 'column_default' => 'NOT NULL' + } + ] + end + + before do + allow(connection).to receive(:exec_query).and_return(database_tables) + end + + it 'returns table inconsistencies' do + expect(result.map(&:object_name)).to match_array(expected_result) + expect(result.map(&:type)).to all(eql inconsistency_type) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/trigger_validators_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/trigger_validators_shared_examples.rb new file mode 100644 index 00000000000..13a112275c2 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/trigger_validators_shared_examples.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'trigger validators' do |validator, expected_result| + subject(:result) { validator.new(structure_file, database).execute } + + let(:structure_file_path) { Rails.root.join('spec/fixtures/structure.sql') } + let(:structure_file) { Gitlab::Database::SchemaValidation::StructureSql.new(structure_file_path, schema) } + let(:inconsistency_type) { validator.name.demodulize.underscore } + let(:database_name) { 'main' } + let(:schema) { 'public' } + let(:database_model) { Gitlab::Database.database_base_models[database_name] } + let(:connection) { database_model.connection } + let(:database) { Gitlab::Database::SchemaValidation::Database.new(connection) } + + let(:database_triggers) do + [ + ['trigger', 'CREATE TRIGGER trigger AFTER INSERT ON public.t1 FOR EACH ROW EXECUTE FUNCTION t1()'], + ['wrong_trigger', 'CREATE TRIGGER wrong_trigger BEFORE UPDATE ON public.t2 FOR EACH ROW EXECUTE FUNCTION t2()'], + ['extra_trigger', 'CREATE TRIGGER extra_trigger BEFORE INSERT ON public.t4 FOR EACH ROW EXECUTE FUNCTION t4()'] + ] + end + + before do + allow(connection).to receive(:select_rows).and_return(database_triggers) + end + + it 'returns trigger inconsistencies' do + expect(result.map(&:object_name)).to match_array(expected_result) + expect(result.map(&:type)).to all(eql inconsistency_type) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb index f26b9a4a7bd..d388abb16c6 100644 --- a/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true def raw_repo_without_container(repository) - Gitlab::Git::Repository.new(repository.shard, - "#{repository.disk_path}.git", - repository.repo_type.identifier_for_container(repository.container), - repository.container.full_path) + Gitlab::Git::Repository.new( + repository.shard, + "#{repository.disk_path}.git", + repository.repo_type.identifier_for_container(repository.container), + repository.container.full_path + ) end RSpec.shared_examples 'Gitaly feature flag actors are inferred from repository' do diff --git a/spec/support/shared_examples/lib/gitlab/json_logger_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/json_logger_shared_examples.rb new file mode 100644 index 00000000000..8a5e8397c3d --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/json_logger_shared_examples.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a json logger' do |extra_params| + let(:now) { Time.now } + let(:correlation_id) { Labkit::Correlation::CorrelationId.current_id } + + it 'formats strings' do + output = subject.format_message('INFO', now, 'test', 'Hello world') + data = Gitlab::Json.parse(output) + + expect(data['severity']).to eq('INFO') + expect(data['time']).to eq(now.utc.iso8601(3)) + expect(data['message']).to eq('Hello world') + expect(data['correlation_id']).to eq(correlation_id) + expect(data).to include(extra_params) + end + + it 'formats hashes' do + output = subject.format_message('INFO', now, 'test', { hello: 1 }) + data = Gitlab::Json.parse(output) + + expect(data['severity']).to eq('INFO') + expect(data['time']).to eq(now.utc.iso8601(3)) + expect(data['hello']).to eq(1) + expect(data['message']).to be_nil + expect(data['correlation_id']).to eq(correlation_id) + expect(data).to include(extra_params) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb index 27ca27a9035..4b0e3234750 100644 --- a/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb @@ -8,9 +8,9 @@ RSpec.shared_examples 'local and remote storage migration' do where(:start_store, :end_store, :method) do ObjectStorage::Store::LOCAL | ObjectStorage::Store::REMOTE | :migrate_to_remote_storage - ObjectStorage::Store::REMOTE | ObjectStorage::Store::REMOTE | :migrate_to_remote_storage # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + ObjectStorage::Store::REMOTE | ObjectStorage::Store::REMOTE | :migrate_to_remote_storage ObjectStorage::Store::REMOTE | ObjectStorage::Store::LOCAL | :migrate_to_local_storage - ObjectStorage::Store::LOCAL | ObjectStorage::Store::LOCAL | :migrate_to_local_storage # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + ObjectStorage::Store::LOCAL | ObjectStorage::Store::LOCAL | :migrate_to_local_storage end with_them do diff --git a/spec/support/shared_examples/lib/gitlab/project_search_results_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/project_search_results_shared_examples.rb index f83fecee4ea..0016f1e670d 100644 --- a/spec/support/shared_examples/lib/gitlab/project_search_results_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/project_search_results_shared_examples.rb @@ -38,8 +38,7 @@ RSpec.shared_examples 'access restricted confidential issues' do let(:user) { author } it 'lists project confidential issues' do - expect(objects).to contain_exactly(issue, - security_issue_1) + expect(objects).to contain_exactly(issue, security_issue_1) expect(results.limited_issues_count).to eq 2 end end @@ -48,8 +47,7 @@ RSpec.shared_examples 'access restricted confidential issues' do let(:user) { assignee } it 'lists project confidential issues for assignee' do - expect(objects).to contain_exactly(issue, - security_issue_2) + expect(objects).to contain_exactly(issue, security_issue_2) expect(results.limited_issues_count).to eq 2 end end @@ -60,9 +58,7 @@ RSpec.shared_examples 'access restricted confidential issues' do end it 'lists project confidential issues' do - expect(objects).to contain_exactly(issue, - security_issue_1, - security_issue_2) + expect(objects).to contain_exactly(issue, security_issue_1, security_issue_2) expect(results.limited_issues_count).to eq 3 end end @@ -72,9 +68,7 @@ RSpec.shared_examples 'access restricted confidential issues' do context 'when admin mode is enabled', :enable_admin_mode do it 'lists all project issues' do - expect(objects).to contain_exactly(issue, - security_issue_1, - security_issue_2) + expect(objects).to contain_exactly(issue, security_issue_1, security_issue_2) end end diff --git a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb index 025f0d5c7ea..c2898513424 100644 --- a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples 'a repo type' do describe '#repository_for' do it 'finds the repository for the repo type' do - expect(described_class.repository_for(expected_container)).to eq(expected_repository) + expect(described_class.repository_for(expected_repository_resolver)).to eq(expected_repository) end it 'returns nil when container is nil' do diff --git a/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb index a3e4379f4d3..18545698c27 100644 --- a/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb @@ -26,29 +26,4 @@ RSpec.shared_examples 'search results filtered by language' do expect(blob_results.size).to eq(5) expect(paths).to match_array(expected_paths) end - - context 'when the search_blobs_language_aggregation feature flag is disabled' do - before do - stub_feature_flags(search_blobs_language_aggregation: false) - end - - it 'does not filter by language', :sidekiq_inline, :aggregate_failures do - expected_paths = %w[ - CHANGELOG - CONTRIBUTING.md - bar/branch-test.txt - custom-highlighting/test.gitlab-custom - files/ruby/popen.rb - files/ruby/regex.rb - files/ruby/version_info.rb - files/whitespace - encoding/test.txt - files/markdown/ruby-style-guide.md - ] - - paths = blob_results.map { |blob| blob.binary_path } - expect(blob_results.size).to eq(10) - expect(paths).to match_array(expected_paths) - end - end end diff --git a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb index ff03051ed37..74570a4da5c 100644 --- a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb @@ -5,7 +5,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name| instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, duplicate_key_ttl: Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob::DEFAULT_DUPLICATE_KEY_TTL) end - let(:expected_message) { "dropped #{strategy_name.to_s.humanize.downcase}" } + let(:humanized_strategy_name) { strategy_name.to_s.humanize.downcase } subject(:strategy) { Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies.for(strategy_name).new(fake_duplicate_job) } @@ -155,7 +155,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name| fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger) expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger) - expect(fake_logger).to receive(:deduplicated_log).with(a_hash_including({ 'jid' => 'new jid' }), expected_message, {}) + expect(fake_logger).to receive(:deduplicated_log).with(a_hash_including({ 'jid' => 'new jid' }), humanized_strategy_name, {}) strategy.schedule({ 'jid' => 'new jid' }) {} end @@ -165,7 +165,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name| expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger) allow(fake_duplicate_job).to receive(:options).and_return({ foo: :bar }) - expect(fake_logger).to receive(:deduplicated_log).with(a_hash_including({ 'jid' => 'new jid' }), expected_message, { foo: :bar }) + expect(fake_logger).to receive(:deduplicated_log).with(a_hash_including({ 'jid' => 'new jid' }), humanized_strategy_name, { foo: :bar }) strategy.schedule({ 'jid' => 'new jid' }) {} 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 d4802a19202..169fceced7a 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,11 +1,11 @@ # frozen_string_literal: true -RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events for given event params' do +RSpec.shared_examples 'tracked issuable snowplow and service ping events for given event params' 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) + def count_unique(date_from: Date.today.beginning_of_week, date_to: 1.week.from_now) Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to) end @@ -27,35 +27,23 @@ RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events expect_snowplow_event(**{ category: category, action: event_action, user: user1 }.merge(event_params)) end - - context 'with route_hll_to_snowplow_phase2 disabled' do - before do - stub_feature_flags(route_hll_to_snowplow_phase2: false) - end - - it 'does not emit snowplow event' do - track_action({ author: user1 }.merge(track_params)) - - expect_no_snowplow_event - end - end 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 +RSpec.shared_examples 'tracked issuable snowplow and service ping events with project' do + it_behaves_like '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, context: [context]) } + let(:track_params) { original_params || { project: project } } + let(:event_params) { { project: project }.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 +RSpec.shared_examples 'tracked issuable snowplow and service ping events with namespace' do + it_behaves_like '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) diff --git a/spec/support/shared_examples/lib/gitlab/utils/username_and_email_generator_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/utils/username_and_email_generator_shared_examples.rb new file mode 100644 index 00000000000..a42d1450e4d --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/utils/username_and_email_generator_shared_examples.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'username and email pair is generated by Gitlab::Utils::UsernameAndEmailGenerator' do + let(:randomhex) { 'randomhex' } + + it 'check email domain' do + expect(subject.email).to end_with("@#{email_domain}") + end + + it 'contains SecureRandom part' do + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + + expect(subject.username).to include("_#{randomhex}") + expect(subject.email).to include("_#{randomhex}@") + end + + it 'email name is the same as username' do + expect(subject.email).to include("#{subject.username}@") + end + + context 'when conflicts' do + let(:reserved_username) { "#{username_prefix}_#{randomhex}" } + let(:reserved_email) { "#{reserved_username}@#{email_domain}" } + + shared_examples 'uniquifies username and email' do + it 'uniquifies username and email' do + expect(subject.username).to eq("#{reserved_username}1") + expect(subject.email).to include("#{subject.username}@") + end + end + + context 'when username is reserved' do + context 'when username is reserved by user' do + before do + create(:user, username: reserved_username) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + + context 'when it conflicts with top-level group namespace' do + before do + create(:group, path: reserved_username) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + + context 'when it conflicts with top-level group namespace that includes upcased characters' do + before do + create(:group, path: reserved_username.upcase) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + end + + context 'when email is reserved' do + context 'when it conflicts with confirmed primary email' do + before do + create(:user, email: reserved_email) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + + context 'when it conflicts with unconfirmed primary email' do + before do + create(:user, :unconfirmed, email: reserved_email) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + + context 'when it conflicts with confirmed secondary email' do + before do + create(:email, :confirmed, email: reserved_email) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + end + + context 'when email and username is reserved' do + before do + create(:user, email: reserved_email) + create(:user, username: "#{reserved_username}1") + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + it 'uniquifies username and email' do + expect(subject.username).to eq("#{reserved_username}2") + + expect(subject.email).to include("#{subject.username}@") + end + end + end +end diff --git a/spec/support/shared_examples/lib/menus_shared_examples.rb b/spec/support/shared_examples/lib/menus_shared_examples.rb index 2c2cb362b07..0aa98517444 100644 --- a/spec/support/shared_examples/lib/menus_shared_examples.rb +++ b/spec/support/shared_examples/lib/menus_shared_examples.rb @@ -37,3 +37,58 @@ RSpec.shared_examples_for 'pill_count formatted results' do expect(pill_count).to eq('112.6k') end end + +RSpec.shared_examples_for 'serializable as super_sidebar_menu_args' do + let(:extra_attrs) { raise NotImplementedError } + + it 'returns hash with provided attributes' do + expect(menu.serialize_as_menu_item_args).to eq({ + title: menu.title, + link: menu.link, + active_routes: menu.active_routes, + container_html_options: menu.container_html_options, + **extra_attrs + }) + end + + it 'returns hash with an item_id' do + expect(menu.serialize_as_menu_item_args[:item_id]).not_to be_nil + end +end + +RSpec.shared_examples_for 'not serializable as super_sidebar_menu_args' do + it 'returns nil' do + expect(menu.serialize_as_menu_item_args).to be_nil + end +end + +RSpec.shared_examples_for 'a panel with uniquely identifiable menu items' do + let(:menu_items) do + subject.instance_variable_get(:@menus) + .flat_map { |menu| menu.instance_variable_get(:@items) } + end + + it 'all menu_items have unique item_id' do + duplicated_ids = menu_items.group_by(&:item_id).reject { |_, v| (v.size < 2) } + + expect(duplicated_ids).to eq({}) + end + + it 'all menu_items have an item_id' do + items_with_nil_id = menu_items.select { |item| item.item_id.nil? } + + expect(items_with_nil_id).to match_array([]) + end +end + +RSpec.shared_examples_for 'a panel with all menu_items categorized' do + let(:uncategorized_menu) do + subject.instance_variable_get(:@menus) + .find { |menu| menu.instance_of?(::Sidebars::UncategorizedMenu) } + end + + it 'has no uncategorized menu_items' do + uncategorized_menu_items = uncategorized_menu.instance_variable_get(:@items) + expect(uncategorized_menu_items).to eq([]) + end +end diff --git a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb index e0b411e1e2a..fa3e9bf5340 100644 --- a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb +++ b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb @@ -90,7 +90,9 @@ RSpec.shared_examples 'Sentry API response size limit' do end it 'raises an exception when response is too large' do - expect { subject }.to raise_error(ErrorTracking::SentryClient::ResponseInvalidSizeError, - 'Sentry API response is too big. Limit is 1 MB.') + expect { subject }.to raise_error( + ErrorTracking::SentryClient::ResponseInvalidSizeError, + 'Sentry API response is too big. Limit is 1 MB.' + ) end end diff --git a/spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb new file mode 100644 index 00000000000..f913c6b8a9e --- /dev/null +++ b/spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'Admin menu' do |link:, title:, icon:, separated: false| + let_it_be(:user) { build(:user, :admin) } + + before do + allow(user).to receive(:can_admin_all_resources?).and_return(true) + end + + let(:context) { Sidebars::Context.new(current_user: user, container: nil) } + + subject { described_class.new(context) } + + it 'renders the correct link' do + expect(subject.link).to match link + end + + it 'renders the correct title' do + expect(subject.title).to eq title + end + + it 'renders the correct icon' do + expect(subject.sprite_icon).to be icon + end + + it 'renders the separator if needed' do + expect(subject.separated?).to be separated + end + + describe '#render?' do + context 'when user is admin' do + it 'renders' do + expect(subject.render?).to be true + end + end + + context 'when user is not admin' do + it 'does not render' do + expect(described_class.new(Sidebars::Context.new(current_user: build(:user), + container: nil)).render?).to be false + end + end + + context 'when user is not logged in' do + it 'does not render' do + expect(described_class.new(Sidebars::Context.new(current_user: nil, container: nil)).render?).to be false + end + end + end +end + +RSpec.shared_examples 'Admin menu without sub menus' do |active_routes:| + let_it_be(:user) { build(:user, :admin) } + + let(:context) { Sidebars::Context.new(current_user: user, container: nil) } + + subject { described_class.new(context) } + + it 'does not contain any sub menu(s)' do + expect(subject.has_items?).to be false + end + + it 'defines correct active route' do + expect(subject.active_routes).to eq active_routes + end +end + +RSpec.shared_examples 'Admin menu with sub menus' do + let_it_be(:user) { build(:user, :admin) } + + let(:context) { Sidebars::Context.new(current_user: user, container: nil) } + + subject { described_class.new(context) } + + it 'contains submemus' do + expect(subject.has_items?).to be true + end +end diff --git a/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb new file mode 100644 index 00000000000..5e8aebb4f29 --- /dev/null +++ b/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'User profile menu' do |title:, icon:, active_route:| + let_it_be(:current_user) { build(:user) } + let_it_be(:user) { build(:user) } + + let(:context) { Sidebars::Context.new(current_user: current_user, container: user) } + + subject { described_class.new(context) } + + it 'does not contain any sub menu' do + expect(subject.has_items?).to be false + end + + it 'renders the correct link' do + expect(subject.link).to match link + end + + it 'renders the correct title' do + expect(subject.title).to eq title + end + + it 'renders the correct icon' do + expect(subject.sprite_icon).to eq icon + end + + it 'defines correct active route' do + expect(subject.active_routes[:path]).to be active_route + end + + it 'renders if user is logged in' do + expect(subject.render?).to be true + end + + [:blocked, :banned].each do |trait| + context "when viewed user is #{trait}" do + let_it_be(:viewed_user) { build(:user, trait) } + let(:context) { Sidebars::Context.new(current_user: user, container: viewed_user) } + + context 'when user is not logged in' do + it 'is not allowed to view the menu item' do + expect(described_class.new(Sidebars::Context.new(current_user: nil, + container: viewed_user)).render?).to be false + end + end + + context 'when current user has permission' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_user_profile, viewed_user).and_return(true) + end + + it 'is allowed to view the menu item' do + expect(described_class.new(context).render?).to be true + end + end + + context 'when current user does not have permission' do + it 'is not allowed to view the menu item' do + expect(described_class.new(context).render?).to be false + end + end + end + end +end + +RSpec.shared_examples 'Followers/followees counts' do |symbol| + let_it_be(:current_user) { build(:user) } + let_it_be(:user) { build(:user) } + + let(:context) { Sidebars::Context.new(current_user: current_user, container: user) } + + subject { described_class.new(context) } + + context 'when there are items' do + before do + allow(user).to receive(symbol).and_return([1, 2]) + end + + it 'renders the pill' do + expect(subject.has_pill?).to be(true) + end + + it 'returns the count' do + expect(subject.pill_count).to be(2) + end + end + + context 'when there are no items' do + it 'does not render the pill' do + expect(subject.has_pill?).to be(false) + end + end +end diff --git a/spec/support/shared_examples/lib/sidebars/user_settings/menus/user_settings_menus_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/user_settings/menus/user_settings_menus_shared_examples.rb new file mode 100644 index 00000000000..b91386d1935 --- /dev/null +++ b/spec/support/shared_examples/lib/sidebars/user_settings/menus/user_settings_menus_shared_examples.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'User settings menu' do |link:, title:, icon:, active_routes:| + let_it_be(:user) { create(:user) } + + let(:context) { Sidebars::Context.new(current_user: user, container: nil) } + + subject { described_class.new(context) } + + it 'does not contain any sub menu' do + expect(subject.has_items?).to be false + end + + it 'renders the correct link' do + expect(subject.link).to match link + end + + it 'renders the correct title' do + expect(subject.title).to eq title + end + + it 'renders the correct icon' do + expect(subject.sprite_icon).to be icon + end + + it 'defines correct active route' do + expect(subject.active_routes).to eq active_routes + end +end + +RSpec.shared_examples 'User settings menu #render? method' do + describe '#render?' do + subject { described_class.new(context) } + + context 'when user is logged in' do + let_it_be(:user) { build(:user) } + let(:context) { Sidebars::Context.new(current_user: user, container: nil) } + + it 'renders' do + expect(subject.render?).to be true + end + end + + context 'when user is not logged in' do + let(:context) { Sidebars::Context.new(current_user: nil, container: nil) } + + it 'does not render' do + expect(subject.render?).to be false + end + end + end +end diff --git a/spec/support/shared_examples/mailers/export_csv_shared_examples.rb b/spec/support/shared_examples/mailers/export_csv_shared_examples.rb new file mode 100644 index 00000000000..731d7c810f9 --- /dev/null +++ b/spec/support/shared_examples/mailers/export_csv_shared_examples.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'export csv email' do |collection_type| + include_context 'gitlab email notification' + + it 'attachment has csv mime type' do + expect(attachment.mime_type).to eq 'text/csv' + end + + it 'generates a useful filename' do + expect(attachment.filename).to include(Date.today.year.to_s) + expect(attachment.filename).to include(collection_type) + expect(attachment.filename).to include('myproject') + expect(attachment.filename).to end_with('.csv') + end + + it 'mentions number of objects and project name' do + expect(subject).to have_content '3' + expect(subject).to have_content empty_project.name + end + + it "doesn't need to mention truncation by default" do + expect(subject).not_to have_content 'truncated' + end + + context 'when truncated' do + let(:export_status) { { truncated: true, rows_expected: 12, rows_written: 10 } } + + it 'mentions that the csv has been truncated' do + expect(subject).to have_content 'truncated' + end + + it 'mentions the number of objects written and expected' do + expect(subject).to have_content "10 of 12 #{collection_type.humanize.downcase}" + end + end +end diff --git a/spec/support/shared_examples/mailers/notify_shared_examples.rb b/spec/support/shared_examples/mailers/notify_shared_examples.rb index 2e182fb399d..cf1ab7697ab 100644 --- a/spec/support/shared_examples/mailers/notify_shared_examples.rb +++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb @@ -59,7 +59,7 @@ end RSpec.shared_examples 'an email with X-GitLab headers containing project details' do it 'has X-GitLab-Project headers' do aggregate_failures do - full_path_as_domain = "#{project.name}.#{project.namespace.path}" + full_path_as_domain = "#{project.path}.#{project.namespace.path}" is_expected.to have_header('X-GitLab-Project', /#{project.name}/) is_expected.to have_header('X-GitLab-Project-Id', /#{project.id}/) is_expected.to have_header('X-GitLab-Project-Path', /#{project.full_path}/) @@ -294,3 +294,17 @@ RSpec.shared_examples 'does not render a manage notifications link' do end end end + +RSpec.shared_examples 'email with default notification reason' do + it do + is_expected.to have_body_text("You're receiving this email because of your account") + is_expected.to have_plain_text_content("You're receiving this email because of your account") + end +end + +RSpec.shared_examples 'email with link to issue' do + it do + is_expected.to have_body_text(%(<a href="#{project_issue_url(project, issue)}">view it on GitLab</a>)) + is_expected.to have_plain_text_content("view it on GitLab: #{project_issue_url(project, issue)}") + end +end diff --git a/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb b/spec/support/shared_examples/metrics_instrumentation_shared_examples.rb index cef9860fe25..cef9860fe25 100644 --- a/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb +++ b/spec/support/shared_examples/metrics_instrumentation_shared_examples.rb diff --git a/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb b/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb new file mode 100644 index 00000000000..28eac52256f --- /dev/null +++ b/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'migration that adds widget to work items definitions' do |widget_name:| + let(:migration) { described_class.new } + let(:work_item_definitions) { table(:work_item_widget_definitions) } + + describe '#up' do + it "creates widget definition in all types" do + work_item_definitions.where(name: widget_name).delete_all + + expect { migrate! }.to change { work_item_definitions.count }.by(7) + expect(work_item_definitions.all.pluck(:name)).to include(widget_name) + end + + it 'logs a warning if the type is missing' do + allow(described_class::WorkItemType).to receive(:find_by_name_and_namespace_id).and_call_original + allow(described_class::WorkItemType).to receive(:find_by_name_and_namespace_id) + .with('Issue', nil).and_return(nil) + + expect(Gitlab::AppLogger).to receive(:warn).with('type Issue is missing, not adding widget') + migrate! + end + end + + describe '#down' do + it "removes definitions for widget" do + migrate! + + expect { migration.down }.to change { work_item_definitions.count }.by(-7) + expect(work_item_definitions.all.pluck(:name)).not_to include(widget_name) + end + end +end diff --git a/spec/support/shared_examples/models/active_record_enum_shared_examples.rb b/spec/support/shared_examples/models/active_record_enum_shared_examples.rb index 3d765b6ca93..10f3263d4fc 100644 --- a/spec/support/shared_examples/models/active_record_enum_shared_examples.rb +++ b/spec/support/shared_examples/models/active_record_enum_shared_examples.rb @@ -10,3 +10,13 @@ RSpec.shared_examples 'having unique enum values' do end end end + +RSpec.shared_examples 'having enum with nil value' do + it 'has enum with nil value' do + subject.public_send("#{attr_value}!") + + expect(subject.public_send("#{attr}_for_database")).to be_nil + expect(subject.public_send("#{attr}?")).to eq(true) + expect(subject.class.public_send(attr_value)).to eq([subject]) + end +end diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb index 085fec6ff1e..addd37cde32 100644 --- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb @@ -221,11 +221,13 @@ RSpec.shared_examples "chat integration" do |integration_name| context "with commit comment" do let_it_be(:note) do - create(:note_on_commit, - author: user, - project: project, - commit_id: project.repository.commit.id, - note: "a comment on a commit") + create( + :note_on_commit, + author: user, + project: project, + commit_id: project.repository.commit.id, + note: "a comment on a commit" + ) end it_behaves_like "triggered #{integration_name} integration" @@ -261,9 +263,11 @@ RSpec.shared_examples "chat integration" do |integration_name| context "with failed pipeline" do let_it_be(:pipeline) do - create(:ci_pipeline, - project: project, status: "failed", - sha: project.commit.sha, ref: project.default_branch) + create( + :ci_pipeline, + project: project, status: "failed", + sha: project.commit.sha, ref: project.default_branch + ) end it_behaves_like "triggered #{integration_name} integration" @@ -271,9 +275,11 @@ RSpec.shared_examples "chat integration" do |integration_name| context "with succeeded pipeline" do let_it_be(:pipeline) do - create(:ci_pipeline, - project: project, status: "success", - sha: project.commit.sha, ref: project.default_branch) + create( + :ci_pipeline, + project: project, status: "success", + sha: project.commit.sha, ref: project.default_branch + ) end context "with default notify_only_broken_pipelines" do diff --git a/spec/support/shared_examples/models/ci/token_format_shared_examples.rb b/spec/support/shared_examples/models/ci/token_format_shared_examples.rb new file mode 100644 index 00000000000..0272982e2d0 --- /dev/null +++ b/spec/support/shared_examples/models/ci/token_format_shared_examples.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +RSpec.shared_examples_for 'ensures runners_token is prefixed' do |factory| + subject(:record) { FactoryBot.build(factory) } + + describe '#runners_token', feature_category: :system_access do + let(:runners_prefix) { RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX } + + it 'generates runners_token which starts with runner prefix' do + expect(record.runners_token).to match(a_string_starting_with(runners_prefix)) + end + + context 'when record has an invalid token' do + subject(:record) { FactoryBot.build(factory, runners_token: invalid_runners_token) } + + let(:invalid_runners_token) { "not_start_with_runners_prefix" } + + it 'generates runners_token which starts with runner prefix' do + expect(record.runners_token).to match(a_string_starting_with(runners_prefix)) + end + + it 'changes the attribute values for runners_token and runners_token_encrypted' do + expect { record.runners_token } + .to change { record[:runners_token] }.from(invalid_runners_token).to(nil) + .and change { record[:runners_token_encrypted] }.from(nil) + end + end + end +end diff --git a/spec/support/shared_examples/models/clusters/prometheus_client_shared.rb b/spec/support/shared_examples/models/clusters/prometheus_client_shared.rb index 8d6dcfef925..140968da272 100644 --- a/spec/support/shared_examples/models/clusters/prometheus_client_shared.rb +++ b/spec/support/shared_examples/models/clusters/prometheus_client_shared.rb @@ -41,10 +41,12 @@ RSpec.shared_examples '#prometheus_client shared' do subject.cluster.platform_kubernetes.namespace = 'a-namespace' stub_kubeclient_discover(cluster.platform_kubernetes.api_url) - create(:cluster_kubernetes_namespace, - cluster: cluster, - cluster_project: cluster.cluster_project, - project: cluster.cluster_project.project) + create( + :cluster_kubernetes_namespace, + cluster: cluster, + cluster_project: cluster.cluster_project, + project: cluster.cluster_project.project + ) end it 'creates proxy prometheus_client' do diff --git a/spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb b/spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb index 122774a9028..a196b63585c 100644 --- a/spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb @@ -17,8 +17,12 @@ RSpec.shared_examples 'a hook that gets automatically disabled on failure' do [4, 1.second.from_now], # Exceeded the grace period, set by #backoff! [4, Time.current] # Exceeded the grace period, set by #backoff!, edge-case ].map do |(recent_failures, disabled_until)| - create(hook_factory, **default_factory_arguments, recent_failures: recent_failures, -disabled_until: disabled_until) + create( + hook_factory, + **default_factory_arguments, + recent_failures: recent_failures, + disabled_until: disabled_until + ) end end @@ -45,8 +49,12 @@ disabled_until: disabled_until) [0, suspended], [0, expired] ].map do |(recent_failures, disabled_until)| - create(hook_factory, **default_factory_arguments, recent_failures: recent_failures, -disabled_until: disabled_until) + create( + hook_factory, + **default_factory_arguments, + recent_failures: recent_failures, + disabled_until: disabled_until + ) end end @@ -61,6 +69,20 @@ disabled_until: disabled_until) # Nothing is missing expect(find_hooks.executable.to_a + find_hooks.disabled.to_a).to match_array(find_hooks.to_a) end + + context 'when the flag is disabled' do + before do + stub_feature_flags(auto_disabling_web_hooks: false) + end + + it 'causes all hooks to be considered executable' do + expect(find_hooks.executable.count).to eq(16) + end + + it 'causes no hooks to be considered disabled' do + expect(find_hooks.disabled).to be_empty + end + end end describe '#executable?', :freeze_time do @@ -108,6 +130,16 @@ disabled_until: disabled_until) it 'has the correct state' do expect(web_hook.executable?).to eq(executable) end + + context 'when the flag is disabled' do + before do + stub_feature_flags(auto_disabling_web_hooks: false) + end + + it 'is always executable' do + expect(web_hook).to be_executable + end + end end end @@ -151,7 +183,7 @@ disabled_until: disabled_until) context 'when we have exhausted the grace period' do before do - hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD) + hook.update!(recent_failures: WebHooks::AutoDisabling::FAILURE_THRESHOLD) end context 'when the hook is permanently disabled' do @@ -172,6 +204,16 @@ disabled_until: disabled_until) def run_expectation expect { hook.backoff! }.to change { hook.backoff_count }.by(1) end + + context 'when the flag is disabled' do + before do + stub_feature_flags(auto_disabling_web_hooks: false) + end + + it 'does not increment backoff count' do + expect { hook.failed! }.not_to change { hook.backoff_count } + end + end end end end @@ -181,6 +223,16 @@ disabled_until: disabled_until) def run_expectation expect { hook.failed! }.to change { hook.recent_failures }.by(1) end + + context 'when the flag is disabled' do + before do + stub_feature_flags(auto_disabling_web_hooks: false) + end + + it 'does not increment recent failure count' do + expect { hook.failed! }.not_to change { hook.recent_failures } + end + end end end @@ -189,6 +241,16 @@ disabled_until: disabled_until) expect { hook.disable! }.to change { hook.executable? }.from(true).to(false) end + context 'when the flag is disabled' do + before do + stub_feature_flags(auto_disabling_web_hooks: false) + end + + it 'does not disable the hook' do + expect { hook.disable! }.not_to change { hook.executable? } + end + end + it 'does nothing if the hook is already disabled' do allow(hook).to receive(:permanently_disabled?).and_return(true) @@ -210,7 +272,7 @@ disabled_until: disabled_until) end it 'allows FAILURE_THRESHOLD initial failures before we back-off' do - WebHook::FAILURE_THRESHOLD.times do + WebHooks::AutoDisabling::FAILURE_THRESHOLD.times do hook.backoff! expect(hook).not_to be_temporarily_disabled end @@ -221,13 +283,23 @@ disabled_until: disabled_until) context 'when hook has been told to back off' do before do - hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD) + hook.update!(recent_failures: WebHooks::AutoDisabling::FAILURE_THRESHOLD) hook.backoff! end it 'is true' do expect(hook).to be_temporarily_disabled end + + context 'when the flag is disabled' do + before do + stub_feature_flags(auto_disabling_web_hooks: false) + end + + it 'is false' do + expect(hook).not_to be_temporarily_disabled + end + end end end @@ -244,6 +316,16 @@ disabled_until: disabled_until) it 'is true' do expect(hook).to be_permanently_disabled end + + context 'when the flag is disabled' do + before do + stub_feature_flags(auto_disabling_web_hooks: false) + end + + it 'is false' do + expect(hook).not_to be_permanently_disabled + end + end end end @@ -258,15 +340,31 @@ disabled_until: disabled_until) end it { is_expected.to eq :disabled } + + context 'when the flag is disabled' do + before do + stub_feature_flags(auto_disabling_web_hooks: false) + end + + it { is_expected.to eq(:executable) } + end end context 'when hook has been backed off' do before do - hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1) + hook.update!(recent_failures: WebHooks::AutoDisabling::FAILURE_THRESHOLD + 1) hook.disabled_until = 1.hour.from_now end it { is_expected.to eq :temporarily_disabled } + + context 'when the flag is disabled' do + before do + stub_feature_flags(auto_disabling_web_hooks: false) + end + + it { is_expected.to eq(:executable) } + end end end end diff --git a/spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb b/spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb index a4db4e25db3..c51e4999e81 100644 --- a/spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/cascading_namespace_setting_shared_examples.rb @@ -112,9 +112,10 @@ RSpec.shared_examples 'a cascading namespace setting boolean attribute' do it 'does not allow the local value to be saved' do subgroup_settings.send("#{settings_attribute_name}=", nil) - expect { subgroup_settings.save! } - .to raise_error(ActiveRecord::RecordInvalid, - /cannot be changed because it is locked by an ancestor/) + expect { subgroup_settings.save! }.to raise_error( + ActiveRecord::RecordInvalid, + /cannot be changed because it is locked by an ancestor/ + ) end end @@ -171,15 +172,59 @@ RSpec.shared_examples 'a cascading namespace setting boolean attribute' do end describe "##{settings_attribute_name}=" do - before do - subgroup_settings.update!(settings_attribute_name => nil) - group_settings.update!(settings_attribute_name => true) + using RSpec::Parameterized::TableSyntax + + where(:parent_value, :current_subgroup_value, :new_subgroup_value, :expected_subgroup_value_after_update) do + true | nil | true | nil + true | nil | "true" | nil + true | false | true | true + true | false | "true" | true + true | true | false | false + true | true | "false" | false + false | nil | false | nil + false | nil | true | true + false | true | false | false + false | false | true | true end - it 'does not save the value locally when it matches the cascaded value' do - subgroup_settings.update!(settings_attribute_name => true) + with_them do + before do + subgroup_settings.update!(settings_attribute_name => current_subgroup_value) + group_settings.update!(settings_attribute_name => parent_value) + end - expect(subgroup_settings.read_attribute(settings_attribute_name)).to eq(nil) + it 'validates starting values from before block', :aggregate_failures do + expect(group_settings.reload.read_attribute(settings_attribute_name)).to eq(parent_value) + expect(subgroup_settings.reload.read_attribute(settings_attribute_name)).to eq(current_subgroup_value) + end + + it 'does not save the value locally when it matches cascaded value', :aggregate_failures do + subgroup_settings.send("#{settings_attribute_name}=", new_subgroup_value) + + # Verify dirty value + expect(subgroup_settings.read_attribute(settings_attribute_name)).to eq(expected_subgroup_value_after_update) + + subgroup_settings.save! + + # Verify persisted value + expect(subgroup_settings.reload.read_attribute(settings_attribute_name)) + .to eq(expected_subgroup_value_after_update) + end + + context 'when mass assigned' do + before do + subgroup_settings.attributes = + { settings_attribute_name => new_subgroup_value, "lock_#{settings_attribute_name}" => false } + end + + it 'does not save the value locally when it matches cascaded value', :aggregate_failures do + subgroup_settings.save! + + # Verify persisted value + expect(subgroup_settings.reload.read_attribute(settings_attribute_name)) + .to eq(expected_subgroup_value_after_update) + end + end end end @@ -277,9 +322,10 @@ RSpec.shared_examples 'a cascading namespace setting boolean attribute' do it 'does not allow the attribute to be saved' do subgroup_settings.send("lock_#{settings_attribute_name}=", true) - expect { subgroup_settings.save! } - .to raise_error(ActiveRecord::RecordInvalid, - /cannot be changed because it is locked by an ancestor/) + expect { subgroup_settings.save! }.to raise_error( + ActiveRecord::RecordInvalid, + /cannot be changed because it is locked by an ancestor/ + ) end end @@ -299,9 +345,10 @@ RSpec.shared_examples 'a cascading namespace setting boolean attribute' do it 'does not allow the lock to be saved when the attribute is nil' do subgroup_settings.send("#{settings_attribute_name}=", nil) - expect { subgroup_settings.save! } - .to raise_error(ActiveRecord::RecordInvalid, - /cannot be nil when locking the attribute/) + expect { subgroup_settings.save! }.to raise_error( + ActiveRecord::RecordInvalid, + /cannot be nil when locking the attribute/ + ) end it 'copies the cascaded value when locking the attribute if the local value is nil', :aggregate_failures do @@ -320,9 +367,10 @@ RSpec.shared_examples 'a cascading namespace setting boolean attribute' do it 'does not allow the attribute to be saved' do subgroup_settings.send("lock_#{settings_attribute_name}=", true) - expect { subgroup_settings.save! } - .to raise_error(ActiveRecord::RecordInvalid, - /cannot be changed because it is locked by an ancestor/) + expect { subgroup_settings.save! }.to raise_error( + ActiveRecord::RecordInvalid, + /cannot be changed because it is locked by an ancestor/ + ) end end diff --git a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb index 5755b9a56b1..9d189842b28 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 @@ -17,6 +17,11 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| let(:amount) { 10 } let(:increment) { Gitlab::Counters::Increment.new(amount: amount, ref: 3) } let(:counter_key) { model.counter(attribute).key } + let(:returns_current) do + model.class.counter_attributes + .find { |a| a[:attribute] == attribute } + .fetch(:returns_current, false) + end subject { model.increment_counter(attribute, increment) } @@ -61,6 +66,33 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| end end + describe '#increment_amount' do + it 'increases the egress in cache' do + model.increment_amount(attribute, 3) + + expect(model.counter(attribute).get).to eq(3) + end + end + + describe '#current_counter' do + let(:data_transfer_node) do + args = { project: project } + args[attribute] = 2 + create(:project_data_transfer, **args) + end + + it 'increases the amount in cache' do + if returns_current + incremented_by = 4 + db_state = model.read_attribute(attribute) + + model.send("increment_#{attribute}".to_sym, incremented_by) + + expect(model.send(attribute)).to eq(db_state + incremented_by) + end + end + end + context 'when increment amount is 0' do let(:amount) { 0 } @@ -155,14 +187,24 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| end describe '#update_counters_with_lease' do - let(:increments) { { build_artifacts_size: 1, packages_size: 2 } } + let_it_be(:first_attribute) { counter_attributes.first } + let_it_be(:second_attribute) { counter_attributes.second } + + let_it_be(:increments) do + increments_hash = {} + + increments_hash[first_attribute] = 1 + increments_hash[second_attribute] = 2 + + increments_hash + end subject { model.update_counters_with_lease(increments) } it 'updates counters of the record' do expect { subject } - .to change { model.reload.build_artifacts_size }.by(1) - .and change { model.reload.packages_size }.by(2) + .to change { model.reload.send(first_attribute) }.by(1) + .and change { model.reload.send(second_attribute) }.by(2) end it_behaves_like 'obtaining lease to update database' do @@ -193,17 +235,4 @@ RSpec.shared_examples 'obtaining lease to update database' do expect { subject }.not_to raise_error end end - - context 'when feature flag counter_attribute_db_lease_for_update is disabled' do - before do - stub_feature_flags(counter_attribute_db_lease_for_update: false) - allow(model).to receive(:in_lock).and_call_original - end - - it 'does not attempt to get a lock' do - expect(model).not_to receive(:in_lock) - - subject - end - 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 2e528f7996c..2dad35dc46e 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 @@ -35,7 +35,6 @@ RSpec.shared_examples Integrations::BaseSlackNotification do |factory:| end 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' } let(:namespace) { project.namespace } diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb index 0ef9ab25505..28d2d4f1597 100644 --- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb @@ -465,10 +465,13 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name context 'when commit comment event executed' do let(:commit_note) do - create(:note_on_commit, author: user, - project: project, - commit_id: project.repository.commit.id, - note: 'a comment on a commit') + create( + :note_on_commit, + author: user, + project: project, + commit_id: project.repository.commit.id, + note: 'a comment on a commit' + ) end let(:data) do @@ -480,8 +483,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name context 'when merge request comment event executed' do let(:merge_request_note) do - create(:note_on_merge_request, project: project, - note: 'a comment on a merge request') + create(:note_on_merge_request, project: project, note: 'a comment on a merge request') end let(:data) do @@ -493,8 +495,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name context 'when issue comment event executed' do let(:issue_note) do - create(:note_on_issue, project: project, - note: 'a comment on an issue') + create(:note_on_issue, project: project, note: 'a comment on an issue') end let(:data) do @@ -506,8 +507,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name context 'when snippet comment event executed' do let(:snippet_note) do - create(:note_on_project_snippet, project: project, - note: 'a comment on a snippet') + create(:note_on_project_snippet, project: project, note: 'a comment on a snippet') end let(:data) do @@ -522,9 +522,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name let_it_be(:user) { create(:user) } let_it_be_with_refind(:project) { create(:project, :repository, creator: user) } let(:pipeline) do - create(:ci_pipeline, - project: project, status: status, - sha: project.commit.sha, ref: project.default_branch) + create(:ci_pipeline, project: project, status: status, sha: project.commit.sha, ref: project.default_branch) end before do @@ -557,9 +555,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name context 'with failed pipeline' do context 'on default branch' do let(:pipeline) do - create(:ci_pipeline, - project: project, status: :failed, - sha: project.commit.sha, ref: project.default_branch) + create(:ci_pipeline, project: project, status: :failed, sha: project.commit.sha, ref: project.default_branch) end let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } @@ -587,9 +583,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name end let(:pipeline) do - create(:ci_pipeline, - project: project, status: :failed, - sha: project.commit.sha, ref: 'a-protected-branch') + create(:ci_pipeline, project: project, status: :failed, sha: project.commit.sha, ref: 'a-protected-branch') end let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } @@ -617,9 +611,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name end let(:pipeline) do - create(:ci_pipeline, - project: project, status: :failed, - sha: project.commit.sha, ref: '1-stable') + create(:ci_pipeline, project: project, status: :failed, sha: project.commit.sha, ref: '1-stable') end let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } @@ -643,9 +635,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name context 'on a neither protected nor default branch' do let(:pipeline) do - create(:ci_pipeline, - project: project, status: :failed, - sha: project.commit.sha, ref: 'a-random-branch') + create(:ci_pipeline, project: project, status: :failed, sha: project.commit.sha, ref: 'a-random-branch') end let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } diff --git a/spec/support/shared_examples/models/concerns/protected_branch_access_examples.rb b/spec/support/shared_examples/models/concerns/protected_branch_access_examples.rb new file mode 100644 index 00000000000..dd27ff3844f --- /dev/null +++ b/spec/support/shared_examples/models/concerns/protected_branch_access_examples.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'protected branch access' do + include_examples 'protected ref access', :protected_branch + + it { is_expected.to belong_to(:protected_branch) } + + describe '#project' do + before do + allow(protected_ref).to receive(:project) + end + + it 'delegates project to protected_branch association' do + described_class.new(protected_branch: protected_ref).project + + expect(protected_ref).to have_received(:project) + end + end +end diff --git a/spec/support/shared_examples/models/concerns/protected_ref_access_allowed_access_levels_examples.rb b/spec/support/shared_examples/models/concerns/protected_ref_access_allowed_access_levels_examples.rb new file mode 100644 index 00000000000..8e15720c79a --- /dev/null +++ b/spec/support/shared_examples/models/concerns/protected_ref_access_allowed_access_levels_examples.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'protected ref access allowed_access_levels' do |excludes: []| + describe '::allowed_access_levels' do + subject { described_class.allowed_access_levels } + + let(:all_levels) do + [ + Gitlab::Access::DEVELOPER, + Gitlab::Access::MAINTAINER, + Gitlab::Access::ADMIN, + Gitlab::Access::NO_ACCESS + ] + end + + context 'when running on Gitlab.com?' do + let(:levels) { all_levels.excluding(Gitlab::Access::ADMIN, *excludes) } + + before do + allow(Gitlab).to receive(:com?).and_return(true) + end + + it { is_expected.to match_array(levels) } + end + + context 'when self hosted?' do + let(:levels) { all_levels.excluding(*excludes) } + + before do + allow(Gitlab).to receive(:com?).and_return(false) + end + + it { is_expected.to match_array(levels) } + end + end +end diff --git a/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb b/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb new file mode 100644 index 00000000000..4753d7a4556 --- /dev/null +++ b/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'protected ref access' do |association| + include ExternalAuthorizationServiceHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:protected_ref) { create(association, project: project) } # rubocop:disable Rails/SaveBang + + it { is_expected.to validate_inclusion_of(:access_level).in_array(described_class.allowed_access_levels) } + + it { is_expected.to validate_presence_of(:access_level) } + + context 'when not role?' do + before do + allow(subject).to receive(:role?).and_return(false) + end + + it { is_expected.not_to validate_presence_of(:access_level) } + end + + describe '::human_access_levels' do + subject { described_class.human_access_levels } + + let(:levels) do + { + Gitlab::Access::DEVELOPER => "Developers + Maintainers", + Gitlab::Access::MAINTAINER => "Maintainers", + Gitlab::Access::ADMIN => 'Instance admins', + Gitlab::Access::NO_ACCESS => "No one" + }.slice(*described_class.allowed_access_levels) + end + + it { is_expected.to eq(levels) } + end + + describe '#check_access' do + let_it_be(:current_user) { create(:user) } + + let(:access_level) { ::Gitlab::Access::DEVELOPER } + + before_all do + project.add_developer(current_user) + end + + subject do + described_class.new( + association => protected_ref, + access_level: access_level + ) + end + + context 'when current_user is nil' do + it { expect(subject.check_access(nil)).to eq(false) } + end + + context 'when access_level is NO_ACCESS' do + let(:access_level) { ::Gitlab::Access::NO_ACCESS } + + it { expect(subject.check_access(current_user)).to eq(false) } + end + + context 'when instance admin access is configured' do + let(:access_level) { Gitlab::Access::ADMIN } + + context 'when current_user is a maintainer' do + it { expect(subject.check_access(current_user)).to eq(false) } + end + + context 'when current_user is admin' do + before do + allow(current_user).to receive(:admin?).and_return(true) + end + + it { expect(subject.check_access(current_user)).to eq(true) } + end + end + + context 'when current_user can push_code to project' do + context 'and member access is high enough' do + it { expect(subject.check_access(current_user)).to eq(true) } + + context 'when external authorization denies access' do + before do + external_service_deny_access(current_user, project) + end + + it { expect(subject.check_access(current_user)).to be_falsey } + end + end + + context 'and member access is too low' do + let(:access_level) { ::Gitlab::Access::MAINTAINER } + + it { expect(subject.check_access(current_user)).to eq(false) } + end + end + + context 'when current_user cannot push_code to project' do + before do + allow(current_user).to receive(:can?).with(:push_code, project).and_return(false) + end + + it { expect(subject.check_access(current_user)).to eq(false) } + end + end +end diff --git a/spec/support/shared_examples/models/concerns/protected_tag_access_examples.rb b/spec/support/shared_examples/models/concerns/protected_tag_access_examples.rb new file mode 100644 index 00000000000..49f616d5a59 --- /dev/null +++ b/spec/support/shared_examples/models/concerns/protected_tag_access_examples.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'protected tag access' do + include_examples 'protected ref access', :protected_tag + + let_it_be(:protected_tag) { create(:protected_tag) } + + it { is_expected.to belong_to(:protected_tag) } + + describe '#project' do + before do + allow(protected_tag).to receive(:project) + end + + it 'delegates project to protected_tag association' do + described_class.new(protected_tag: protected_tag).project + + expect(protected_tag).to have_received(:project) + end + end +end diff --git a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb index e4958779957..b04ac40b309 100644 --- a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb @@ -84,9 +84,12 @@ RSpec.shared_examples 'a timebox' do |timebox_type| let(:max_date) { mid_point + 10.days } def box(from, to) - create(factory, *timebox_args, - start_date: from || open_on_left, - due_date: to || open_on_right) + create( + factory, + *timebox_args, + start_date: from || open_on_left, + due_date: to || open_on_right + ) end it 'can find overlapping timeboxes' do diff --git a/spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb b/spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb index 848840ee297..f98528ffedc 100644 --- a/spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb @@ -18,8 +18,12 @@ RSpec.shared_examples 'a hook that does not get automatically disabled on failur [3, nil], [3, 1.day.ago] ].map do |(recent_failures, disabled_until)| - create(hook_factory, **default_factory_arguments, recent_failures: recent_failures, -disabled_until: disabled_until) + create( + hook_factory, + **default_factory_arguments, + recent_failures: recent_failures, + disabled_until: disabled_until + ) end end @@ -110,7 +114,7 @@ disabled_until: disabled_until) context 'when we have exhausted the grace period' do before do - hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD) + hook.update!(recent_failures: WebHooks::AutoDisabling::FAILURE_THRESHOLD) end it 'does not disable the hook' do @@ -131,7 +135,7 @@ disabled_until: disabled_until) expect(hook).not_to be_temporarily_disabled # Backing off - WebHook::FAILURE_THRESHOLD.times do + WebHooks::AutoDisabling::FAILURE_THRESHOLD.times do hook.backoff! expect(hook).not_to be_temporarily_disabled end @@ -167,7 +171,7 @@ disabled_until: disabled_until) context 'when hook has been backed off' do before do - hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1) + hook.update!(recent_failures: WebHooks::AutoDisabling::FAILURE_THRESHOLD + 1) hook.disabled_until = 1.hour.from_now end diff --git a/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb b/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb index cd6eb8c77fa..113dcc266fc 100644 --- a/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb @@ -19,7 +19,7 @@ RSpec.shared_examples 'something that has web-hooks' do context 'when there is a failed hook' do before do hook = create_hook - hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1) + hook.update!(recent_failures: WebHooks::AutoDisabling::FAILURE_THRESHOLD + 1) end it { is_expected.to eq(true) } @@ -83,7 +83,7 @@ RSpec.shared_examples 'something that has web-hooks' do describe '#fetch_web_hook_failure', :clean_gitlab_redis_shared_state do context 'when a value has not been stored' do - it 'does not call #any_hook_failed?' do + it 'calls #any_hook_failed?' do expect(object.get_web_hook_failure).to be_nil expect(object).to receive(:any_hook_failed?).and_return(true) diff --git a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb index 5eeefacdeb9..3f532629961 100644 --- a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb +++ b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb @@ -290,6 +290,7 @@ RSpec.shared_examples 'value stream analytics label based stage' do context 'when `ProjectLabel is given' do let_it_be(:label) { create(:label) } + let(:expected_error) { s_('CycleAnalyticsStage|is not available for the selected group') } it 'raises error when `ProjectLabel` is given for `start_event_label`' do params = { @@ -300,7 +301,9 @@ RSpec.shared_examples 'value stream analytics label based stage' do end_event_identifier: :issue_closed } - expect { described_class.new(params) }.to raise_error(ActiveRecord::AssociationTypeMismatch) + stage = described_class.new(params) + expect(stage).to be_invalid + expect(stage.errors.messages_for(:start_event_label_id)).to eq([expected_error]) end it 'raises error when `ProjectLabel` is given for `end_event_label`' do @@ -312,7 +315,9 @@ RSpec.shared_examples 'value stream analytics label based stage' do end_event_label: label } - expect { described_class.new(params) }.to raise_error(ActiveRecord::AssociationTypeMismatch) + stage = described_class.new(params) + expect(stage).to be_invalid + expect(stage.errors.messages_for(:end_event_label_id)).to eq([expected_error]) end end end diff --git a/spec/support/shared_examples/models/database_event_tracking_shared_examples.rb b/spec/support/shared_examples/models/database_event_tracking_shared_examples.rb new file mode 100644 index 00000000000..3d98d9136e2 --- /dev/null +++ b/spec/support/shared_examples/models/database_event_tracking_shared_examples.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'database events tracking' do + describe 'events tracking' do + # required definitions: + # :record, :update_params + # + # other available attributes: + # :project, :namespace + + let(:user) { nil } + let(:category) { described_class.to_s } + let(:label) { described_class.table_name } + let(:action) { "database_event_#{property}" } + let(:feature_flag_name) { :product_intelligence_database_event_tracking } + let(:record_tracked_attributes) { record.attributes.slice(*described_class::SNOWPLOW_ATTRIBUTES.map(&:to_s)) } + let(:base_extra) { record_tracked_attributes.merge(project: try(:project), namespace: try(:namespace)) } + + before do + allow(Gitlab::Tracking).to receive(:database_event).and_call_original + end + + describe '#create' do + it_behaves_like 'Snowplow event tracking', overrides: { tracking_method: :database_event } do + subject(:create_record) { record } + + let(:extra) { base_extra } + let(:property) { 'create' } + end + end + + describe '#update', :freeze_time do + it_behaves_like 'Snowplow event tracking', overrides: { tracking_method: :database_event } do + subject(:update_record) { record.update!(update_params) } + + let(:extra) { base_extra.merge(update_params.stringify_keys) } + let(:property) { 'update' } + end + end + + describe '#destroy' do + it_behaves_like 'Snowplow event tracking', overrides: { tracking_method: :database_event } do + subject(:delete_record) { record.destroy! } + + let(:extra) { base_extra } + let(:property) { 'destroy' } + end + end + end +end + +RSpec.shared_examples 'database events tracking batch 2' do + it_behaves_like 'database events tracking' do + let(:feature_flag_name) { :product_intelligence_database_event_tracking_batch2 } + end +end diff --git a/spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb b/spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb index 64390ccdc25..f1f6d799cf3 100644 --- a/spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb +++ b/spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb @@ -10,10 +10,12 @@ RSpec.shared_examples 'a valid diff note with after commit callback' do it 'raises an error' do allow(diff_file_from_repository).to receive(:line_for_position).with(position).and_return(nil) - expect { subject.save! }.to raise_error(::DiffNote::NoteDiffFileCreationError, - "Failed to find diff line for: #{diff_file_from_repository.file_path}, "\ - "old_line: #{position.old_line}"\ - ", new_line: #{position.new_line}") + expect { subject.save! }.to raise_error( + ::DiffNote::NoteDiffFileCreationError, + "Failed to find diff line for: #{diff_file_from_repository.file_path}, "\ + "old_line: #{position.old_line}"\ + ", new_line: #{position.new_line}" + ) end end diff --git a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb index 7dfdd24177e..0cf109ce5c5 100644 --- a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb +++ b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb @@ -3,7 +3,6 @@ RSpec.shared_examples Integrations::BaseSlashCommands do describe "Associations" do it { is_expected.to respond_to :token } - it { is_expected.to have_many :chat_names } end describe 'default values' do @@ -85,7 +84,7 @@ RSpec.shared_examples Integrations::BaseSlashCommands do end context 'when the user is authenticated' do - let!(:chat_name) { create(:chat_name, integration: subject) } + let!(:chat_name) { create(:chat_name) } let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } } subject do diff --git a/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb b/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb index 6d519e561ee..d438918eb60 100644 --- a/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb +++ b/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb @@ -10,19 +10,19 @@ end RSpec.shared_examples 'allows project key on reference pattern' do |url_attr| it 'allows underscores in the project name' do - expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' + expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' end it 'allows numbers in the project name' do - expect(described_class.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234' + expect(subject.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234' end it 'requires the project name to begin with A-Z' do - expect(described_class.reference_pattern.match('3EXT_EXT-1234')).to eq nil - expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' + expect(subject.reference_pattern.match('3EXT_EXT-1234')).to eq nil + expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' end it 'does not allow issue number to finish with a letter' do - expect(described_class.reference_pattern.match('EXT-123A')).to eq(nil) + expect(subject.reference_pattern.match('EXT-123A')).to eq(nil) end end diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb index 7159c55e303..e9e25dee746 100644 --- a/spec/support/shared_examples/models/member_shared_examples.rb +++ b/spec/support/shared_examples/models/member_shared_examples.rb @@ -392,6 +392,30 @@ RSpec.shared_examples_for "bulk member creation" do expect(members.first).to be_invite end + context 'with different source types' do + shared_examples 'supports multiple sources' do + specify do + members = described_class.add_members(sources, [user1, user2], :maintainer) + + expect(members.map(&:user)).to contain_exactly(user1, user2, user1, user2) + expect(members).to all(be_a(member_type)) + expect(members).to all(be_persisted) + end + end + + context 'with an array of sources' do + let_it_be(:sources) { [source, source2] } + + it_behaves_like 'supports multiple sources' + end + + context 'with a query producing sources' do + let_it_be(:sources) { source_type.id_in([source, source2]) } + + it_behaves_like 'supports multiple sources' + end + end + context 'with de-duplication' do it 'has the same user by id and user' do members = described_class.add_members(source, [user1.id, user1, user1.id, user2, user2.id, user2], :maintainer) @@ -484,11 +508,13 @@ RSpec.shared_examples_for "bulk member creation" do create(:member_task, member: member, project: task_project, tasks_to_be_done: %w(code ci)) expect do - described_class.add_members(source, - [user1.id], - :developer, - tasks_to_be_done: %w(issues), - tasks_project_id: task_project.id) + described_class.add_members( + source, + [user1.id], + :developer, + tasks_to_be_done: %w(issues), + tasks_project_id: task_project.id + ) end.not_to change { MemberTask.count } member.reset @@ -498,11 +524,13 @@ RSpec.shared_examples_for "bulk member creation" do it 'adds tasks to be done if they do not exist', :aggregate_failures do expect do - described_class.add_members(source, - [user1.id], - :developer, - tasks_to_be_done: %w(issues), - tasks_project_id: task_project.id) + described_class.add_members( + source, + [user1.id], + :developer, + tasks_to_be_done: %w(issues), + tasks_project_id: task_project.id + ) end.to change { MemberTask.count }.by(1) member = source.members.find_by(user_id: user1.id) diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb index e28220334ac..329cb812a08 100644 --- a/spec/support/shared_examples/models/members_notifications_shared_example.rb +++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb @@ -69,7 +69,7 @@ RSpec.shared_examples 'members notifications' do |entity_type| let(:member) { create(:"#{entity_type}_member", :invited) } it "calls NotificationService.decline_#{entity_type}_invite" do - expect(notification_service).to receive(:"decline_#{entity_type}_invite").with(member) + expect(notification_service).to receive(:decline_invite).with(member) member.decline_invite! end diff --git a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb index 5be0f6349ea..c2c123277ee 100644 --- a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb +++ b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb @@ -20,7 +20,6 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| let_it_be(:component_file_other_architecture, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_2) } let_it_be(:component_file_other_component, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_2, architecture: architecture1_1) } let_it_be(:component_file_other_compression_type, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, compression_type: :xz) } - let_it_be(:component_file_other_file_md5, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, file_md5: 'other_md5') } let_it_be(:component_file_other_file_sha256, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, file_sha256: 'other_sha256') } let_it_be(:component_file_other_container, freeze: can_freeze) { create("debian_#{container_type}_component_file", component: component2_1, architecture: architecture2_1) } let_it_be_with_refind(:component_file_with_file_type_sources) { create("debian_#{container_type}_component_file", :sources, component: component1_1) } @@ -100,10 +99,6 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| it { is_expected.to validate_presence_of(:file_store) } end - describe '#file_md5' do - it { is_expected.to validate_presence_of(:file_md5) } - end - describe '#file_sha256' do it { is_expected.to validate_presence_of(:file_sha256) } end @@ -231,4 +226,20 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| it { is_expected.to eq("#{component1_1.name}/binary-#{architecture1_1.name}/Packages.xz") } end end + + describe '#empty?' do + subject { component_file_with_architecture.empty? } + + context 'with a non-empty component' do + it { is_expected.to be_falsey } + end + + context 'with an empty component' do + before do + component_file_with_architecture.update! size: 0 + end + + it { is_expected.to be_truthy } + end + end end diff --git a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb index ac4ad4525aa..3ea2ff4d8f0 100644 --- a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb +++ b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb @@ -1,32 +1,13 @@ # frozen_string_literal: true -require 'spec_helper' - -RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| - let_it_be(:distribution_with_suite, freeze: can_freeze) { create(factory, :with_suite) } - let_it_be(:distribution_with_same_container, freeze: can_freeze) { create(factory, container: distribution_with_suite.container ) } - let_it_be(:distribution_with_same_codename, freeze: can_freeze) { create(factory, codename: distribution_with_suite.codename ) } - let_it_be(:distribution_with_same_suite, freeze: can_freeze) { create(factory, suite: distribution_with_suite.suite ) } - let_it_be(:distribution_with_codename_and_suite_flipped, freeze: can_freeze) { create(factory, codename: distribution_with_suite.suite, suite: distribution_with_suite.codename) } - - let_it_be_with_refind(:distribution) { create(factory, container: distribution_with_suite.container ) } - +RSpec.shared_examples 'Debian Distribution for common behavior' do subject { distribution } describe 'relationships' do - it { is_expected.to belong_to(container) } it { is_expected.to belong_to(:creator).class_name('User') } - - it { is_expected.to have_one(:key).class_name("Packages::Debian::#{container.capitalize}DistributionKey").with_foreign_key(:distribution_id).inverse_of(:distribution) } - it { is_expected.to have_many(:components).class_name("Packages::Debian::#{container.capitalize}Component").inverse_of(:distribution) } - it { is_expected.to have_many(:architectures).class_name("Packages::Debian::#{container.capitalize}Architecture").inverse_of(:distribution) } end describe 'validations' do - describe "##{container}" do - it { is_expected.to validate_presence_of(container) } - end - describe "#creator" do it { is_expected.not_to validate_presence_of(:creator) } end @@ -47,57 +28,6 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| it { is_expected.not_to allow_value('hé').for(:suite) } end - describe '#unique_debian_suite_and_codename' do - using RSpec::Parameterized::TableSyntax - - where(:with_existing_suite, :suite, :codename, :errors) do - false | nil | :keep | nil - false | 'testing' | :keep | nil - false | nil | :codename | ["Codename has already been taken"] - false | :codename | :keep | ["Suite has already been taken as Codename"] - false | :codename | :codename | ["Codename has already been taken", "Suite has already been taken as Codename"] - true | nil | :keep | nil - true | 'testing' | :keep | nil - true | nil | :codename | ["Codename has already been taken"] - true | :codename | :keep | ["Suite has already been taken as Codename"] - true | :codename | :codename | ["Codename has already been taken", "Suite has already been taken as Codename"] - true | nil | :suite | ["Codename has already been taken as Suite"] - true | :suite | :keep | ["Suite has already been taken"] - true | :suite | :suite | ["Suite has already been taken", "Codename has already been taken as Suite"] - end - - with_them do - context factory do - let(:new_distribution) { build(factory, container: distribution.container) } - - before do - distribution.update_column(:suite, 'suite-' + distribution.codename) if with_existing_suite - - if suite.is_a?(Symbol) - new_distribution.suite = distribution.send suite unless suite == :keep - else - new_distribution.suite = suite - end - - if codename.is_a?(Symbol) - new_distribution.codename = distribution.send codename unless codename == :keep - else - new_distribution.codename = codename - end - end - - it do - if errors - expect(new_distribution).not_to be_valid - expect(new_distribution.errors.to_a).to eq(errors) - else - expect(new_distribution).to be_valid - end - end - end - end - end - describe '#origin' do it { is_expected.to allow_value(nil).for(:origin) } it { is_expected.to allow_value('Debian').for(:origin) } @@ -179,7 +109,11 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| subject { described_class.with_codename_or_suite(distribution_with_suite.codename) } it 'does not return other distributions' do - expect(subject.to_a).to contain_exactly(distribution_with_suite, distribution_with_same_codename, distribution_with_codename_and_suite_flipped) + expect(subject.to_a) + .to contain_exactly( + distribution_with_suite, + distribution_with_same_codename, + distribution_with_codename_and_suite_flipped) end end @@ -187,54 +121,169 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| subject { described_class.with_codename_or_suite(distribution_with_suite.suite) } it 'does not return other distributions' do - expect(subject.to_a).to contain_exactly(distribution_with_suite, distribution_with_same_suite, distribution_with_codename_and_suite_flipped) + expect(subject.to_a) + .to contain_exactly( + distribution_with_suite, + distribution_with_same_suite, + distribution_with_codename_and_suite_flipped) end end end end +end - if container == :project - describe 'project distribution specifics' do - describe 'relationships' do - it { is_expected.to have_many(:publications).class_name('Packages::Debian::Publication').inverse_of(:distribution).with_foreign_key(:distribution_id) } - it { is_expected.to have_many(:packages).class_name('Packages::Package').through(:publications) } - end +RSpec.shared_examples 'Debian Distribution for specific behavior' do |factory| + describe '#unique_debian_suite_and_codename' do + using RSpec::Parameterized::TableSyntax + + where(:with_existing_suite, :suite, :codename, :errors) do + false | nil | :keep | nil + false | 'testing' | :keep | nil + false | nil | :codename | ["Codename has already been taken"] + false | :codename | :keep | ["Suite has already been taken as Codename"] + false | :codename | :codename | ["Codename has already been taken", "Suite has already been taken as Codename"] + true | nil | :keep | nil + true | 'testing' | :keep | nil + true | nil | :codename | ["Codename has already been taken"] + true | :codename | :keep | ["Suite has already been taken as Codename"] + true | :codename | :codename | ["Codename has already been taken", "Suite has already been taken as Codename"] + true | nil | :suite | ["Codename has already been taken as Suite"] + true | :suite | :keep | ["Suite has already been taken"] + true | :suite | :suite | ["Suite has already been taken", "Codename has already been taken as Suite"] end - else - describe 'group distribution specifics' do - let_it_be(:public_project) { create(:project, :public, group: distribution_with_suite.container) } - let_it_be(:public_distribution_with_same_codename) { create(:debian_project_distribution, container: public_project, codename: distribution_with_suite.codename) } - let_it_be(:public_package_with_same_codename) { create(:debian_package, project: public_project, published_in: public_distribution_with_same_codename) } - let_it_be(:public_distribution_with_same_suite) { create(:debian_project_distribution, container: public_project, suite: distribution_with_suite.suite) } - let_it_be(:public_package_with_same_suite) { create(:debian_package, project: public_project, published_in: public_distribution_with_same_suite) } - - let_it_be(:private_project) { create(:project, :private, group: distribution_with_suite.container) } - let_it_be(:private_distribution_with_same_codename) { create(:debian_project_distribution, container: private_project, codename: distribution_with_suite.codename) } - let_it_be(:private_package_with_same_codename) { create(:debian_package, project: private_project, published_in: private_distribution_with_same_codename) } - let_it_be(:private_distribution_with_same_suite) { create(:debian_project_distribution, container: private_project, suite: distribution_with_suite.suite) } - let_it_be(:private_package_with_same_suite) { create(:debian_package, project: private_project, published_in: private_distribution_with_same_codename) } - - describe '#packages' do - subject { distribution_with_suite.packages } - - it 'returns only public packages with same codename' do - expect(subject.to_a).to contain_exactly(public_package_with_same_codename) + + with_them do + context factory do + let(:new_distribution) { build(factory, container: distribution.container) } + + before do + distribution.update_column(:suite, "suite-#{distribution.codename}") if with_existing_suite + + if suite.is_a?(Symbol) + new_distribution.suite = distribution.send suite unless suite == :keep + else + new_distribution.suite = suite + end + + if codename.is_a?(Symbol) + new_distribution.codename = distribution.send codename unless codename == :keep + else + new_distribution.codename = codename + end + end + + it do + if errors + expect(new_distribution).not_to be_valid + expect(new_distribution.errors.to_a).to eq(errors) + else + expect(new_distribution).to be_valid + end end end + end + end +end - describe '#package_files' do - subject { distribution_with_suite.package_files } +RSpec.shared_examples 'Debian Distribution with project container' do + it_behaves_like 'Debian Distribution for specific behavior', :debian_project_distribution - it 'returns only files from public packages with same codename' do - expect(subject.to_a).to contain_exactly(*public_package_with_same_codename.package_files) - end + describe 'relationships' do + it { is_expected.to belong_to(:project) } - context 'with pending destruction package files' do - let_it_be(:package_file_pending_destruction) { create(:package_file, :pending_destruction, package: public_package_with_same_codename) } + it { is_expected.to have_one(:key).class_name("Packages::Debian::ProjectDistributionKey").with_foreign_key(:distribution_id).inverse_of(:distribution) } + it { is_expected.to have_many(:components).class_name("Packages::Debian::ProjectComponent").inverse_of(:distribution) } + it { is_expected.to have_many(:architectures).class_name("Packages::Debian::ProjectArchitecture").inverse_of(:distribution) } + end - it 'does not return them' do - expect(subject.to_a).not_to include(package_file_pending_destruction) - end + describe "#project" do + it { is_expected.to validate_presence_of(:project) } + end + + describe 'project distribution specifics' do + describe 'relationships' do + it do + is_expected.to have_many(:publications).class_name('Packages::Debian::Publication').inverse_of(:distribution) + .with_foreign_key(:distribution_id) + end + + it { is_expected.to have_many(:packages).class_name('Packages::Package').through(:publications) } + end + end +end + +RSpec.shared_examples 'Debian Distribution with group container' do + it_behaves_like 'Debian Distribution for specific behavior', :debian_group_distribution + + describe 'relationships' do + it { is_expected.to belong_to(:group) } + + it { is_expected.to have_one(:key).class_name("Packages::Debian::GroupDistributionKey").with_foreign_key(:distribution_id).inverse_of(:distribution) } + it { is_expected.to have_many(:components).class_name("Packages::Debian::GroupComponent").inverse_of(:distribution) } + it { is_expected.to have_many(:architectures).class_name("Packages::Debian::GroupArchitecture").inverse_of(:distribution) } + end + + describe "#group" do + it { is_expected.to validate_presence_of(:group) } + end + + describe 'group distribution specifics' do + let_it_be(:public_project) { create(:project, :public, group: distribution_with_suite.container) } + let_it_be(:public_distribution_with_same_codename) do + create(:debian_project_distribution, container: public_project, codename: distribution_with_suite.codename) + end + + let_it_be(:public_package_with_same_codename) do + create(:debian_package, project: public_project, published_in: public_distribution_with_same_codename) + end + + let_it_be(:public_distribution_with_same_suite) do + create(:debian_project_distribution, container: public_project, suite: distribution_with_suite.suite) + end + + let_it_be(:public_package_with_same_suite) do + create(:debian_package, project: public_project, published_in: public_distribution_with_same_suite) + end + + let_it_be(:private_project) { create(:project, :private, group: distribution_with_suite.container) } + let_it_be(:private_distribution_with_same_codename) do + create(:debian_project_distribution, container: private_project, codename: distribution_with_suite.codename) + end + + let_it_be(:private_package_with_same_codename) do + create(:debian_package, project: private_project, published_in: private_distribution_with_same_codename) + end + + let_it_be(:private_distribution_with_same_suite) do + create(:debian_project_distribution, container: private_project, suite: distribution_with_suite.suite) + end + + let_it_be(:private_package_with_same_suite) do + create(:debian_package, project: private_project, published_in: private_distribution_with_same_codename) + end + + describe '#packages' do + subject { distribution_with_suite.packages } + + it 'returns only public packages with same codename' do + expect(subject.to_a).to contain_exactly(public_package_with_same_codename) + end + end + + describe '#package_files' do + subject { distribution_with_suite.package_files } + + it 'returns only files from public packages with same codename' do + expect(subject.to_a).to contain_exactly(*public_package_with_same_codename.package_files) + end + + context 'with pending destruction package files' do + let_it_be(:package_file_pending_destruction) do + create(:package_file, :pending_destruction, package: public_package_with_same_codename) + end + + it 'does not return them' do + expect(subject.to_a).not_to include(package_file_pending_destruction) end end end diff --git a/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb b/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb index 3caf58da4d2..f1af1760e8d 100644 --- a/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb +++ b/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb @@ -19,7 +19,7 @@ RSpec.shared_examples 'ci_cd_settings delegation' do end end -RSpec.shared_examples 'a ci_cd_settings predicate method' do |prefix: ''| +RSpec.shared_examples 'a ci_cd_settings predicate method' do |prefix: '', default: false| using RSpec::Parameterized::TableSyntax context 'when ci_cd_settings is nil' do @@ -28,7 +28,7 @@ RSpec.shared_examples 'a ci_cd_settings predicate method' do |prefix: ''| end it 'returns false' do - expect(project.send("#{prefix}#{delegated_method}")).to be(false) + expect(project.send("#{prefix}#{delegated_method}")).to be(default) end end diff --git a/spec/support/shared_examples/models/resource_event_shared_examples.rb b/spec/support/shared_examples/models/resource_event_shared_examples.rb index 038ff33c68a..1409f7caea8 100644 --- a/spec/support/shared_examples/models/resource_event_shared_examples.rb +++ b/spec/support/shared_examples/models/resource_event_shared_examples.rb @@ -10,6 +10,8 @@ RSpec.shared_examples 'a resource event' do let_it_be(:issue2) { create(:issue, author: user1) } let_it_be(:issue3) { create(:issue, author: user2) } + let(:resource_event) { described_class.name.demodulize.underscore.to_sym } + describe 'importable' do it { is_expected.to respond_to(:importing?) } it { is_expected.to respond_to(:imported?) } @@ -36,9 +38,9 @@ RSpec.shared_examples 'a resource event' do let!(:created_at2) { 2.days.ago } let!(:created_at3) { 3.days.ago } - let!(:event1) { create(described_class.name.underscore.to_sym, issue: issue1, created_at: created_at1) } - let!(:event2) { create(described_class.name.underscore.to_sym, issue: issue2, created_at: created_at2) } - let!(:event3) { create(described_class.name.underscore.to_sym, issue: issue2, created_at: created_at3) } + let!(:event1) { create(resource_event, issue: issue1, created_at: created_at1) } + let!(:event2) { create(resource_event, issue: issue2, created_at: created_at2) } + let!(:event3) { create(resource_event, issue: issue2, created_at: created_at3) } it 'returns the expected events' do events = described_class.created_after(created_at3) @@ -62,9 +64,10 @@ RSpec.shared_examples 'a resource event for issues' do let_it_be(:issue2) { create(:issue, author: user1) } let_it_be(:issue3) { create(:issue, author: user2) } - let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue1) } - let_it_be(:event2) { create(described_class.name.underscore.to_sym, issue: issue2) } - let_it_be(:event3) { create(described_class.name.underscore.to_sym, issue: issue1) } + let_it_be(:resource_event) { described_class.name.demodulize.underscore.to_sym } + let_it_be(:event1) { create(resource_event, issue: issue1) } + let_it_be(:event2) { create(resource_event, issue: issue2) } + let_it_be(:event3) { create(resource_event, issue: issue1) } describe 'associations' do it { is_expected.to belong_to(:issue) } @@ -93,9 +96,9 @@ RSpec.shared_examples 'a resource event for issues' do end describe '.by_created_at_earlier_or_equal_to' do - let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue1, created_at: '2020-03-10') } - let_it_be(:event2) { create(described_class.name.underscore.to_sym, issue: issue2, created_at: '2020-03-10') } - let_it_be(:event3) { create(described_class.name.underscore.to_sym, issue: issue1, created_at: '2020-03-12') } + let_it_be(:event1) { create(resource_event, issue: issue1, created_at: '2020-03-10') } + let_it_be(:event2) { create(resource_event, issue: issue2, created_at: '2020-03-10') } + let_it_be(:event3) { create(resource_event, issue: issue1, created_at: '2020-03-12') } it 'returns the expected events' do events = described_class.by_created_at_earlier_or_equal_to('2020-03-11 23:59:59') @@ -112,7 +115,7 @@ RSpec.shared_examples 'a resource event for issues' do if described_class.method_defined?(:issuable) describe '#issuable' do - let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue2) } + let_it_be(:event1) { create(resource_event, issue: issue2) } it 'returns the expected issuable' do expect(event1.issuable).to eq(issue2) @@ -125,6 +128,7 @@ RSpec.shared_examples 'a resource event for merge requests' do let_it_be(:user1) { create(:user) } let_it_be(:user2) { create(:user) } + let_it_be(:resource_event) { described_class.name.demodulize.underscore.to_sym } let_it_be(:merge_request1) { create(:merge_request, author: user1) } let_it_be(:merge_request2) { create(:merge_request, author: user1) } let_it_be(:merge_request3) { create(:merge_request, author: user2) } @@ -134,9 +138,9 @@ RSpec.shared_examples 'a resource event for merge requests' do end describe '.by_merge_request' do - let_it_be(:event1) { create(described_class.name.underscore.to_sym, merge_request: merge_request1) } - let_it_be(:event2) { create(described_class.name.underscore.to_sym, merge_request: merge_request2) } - let_it_be(:event3) { create(described_class.name.underscore.to_sym, merge_request: merge_request1) } + let_it_be(:event1) { create(resource_event, merge_request: merge_request1) } + let_it_be(:event2) { create(resource_event, merge_request: merge_request2) } + let_it_be(:event3) { create(resource_event, merge_request: merge_request1) } it 'returns the expected records for an issue with events' do events = described_class.by_merge_request(merge_request1) @@ -153,7 +157,7 @@ RSpec.shared_examples 'a resource event for merge requests' do if described_class.method_defined?(:issuable) describe '#issuable' do - let_it_be(:event1) { create(described_class.name.underscore.to_sym, merge_request: merge_request2) } + let_it_be(:event1) { create(resource_event, merge_request: merge_request2) } it 'returns the expected issuable' do expect(event1.issuable).to eq(merge_request2) @@ -163,7 +167,7 @@ RSpec.shared_examples 'a resource event for merge requests' do context 'on callbacks' do it 'does not trigger note created subscription' do - event = build(described_class.name.underscore.to_sym, merge_request: merge_request1) + event = build(resource_event, merge_request: merge_request1) expect(GraphqlTriggers).not_to receive(:work_item_note_created) expect(event).not_to receive(:trigger_note_subscription_create) @@ -177,15 +181,17 @@ RSpec.shared_examples 'a note for work item resource event' do let_it_be(:project) { create(:project) } let_it_be(:work_item) { create(:work_item, :task, project: project, author: user) } + let(:resource_event) { described_class.name.demodulize.underscore.to_sym } + it 'builds synthetic note with correct synthetic_note_class' do - event = build(described_class.name.underscore.to_sym, issue: work_item) + event = build(resource_event, issue: work_item) expect(event.work_item_synthetic_system_note.class.name).to eq(event.synthetic_note_class.name) end context 'on callbacks' do it 'triggers note created subscription' do - event = build(described_class.name.underscore.to_sym, issue: work_item) + event = build(resource_event, issue: work_item) expect(GraphqlTriggers).to receive(:work_item_note_created) expect(event).to receive(:trigger_note_subscription_create).and_call_original diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb index 7e69a6663d5..017e51ecd24 100644 --- a/spec/support/shared_examples/models/wiki_shared_examples.rb +++ b/spec/support/shared_examples/models/wiki_shared_examples.rb @@ -94,6 +94,40 @@ RSpec.shared_examples 'wiki model' do end end + describe '#has_home_page?' do + context 'when home page exists' do + before do + wiki.repository.create_file( + user, + 'home.md', + 'home file', + branch_name: wiki.default_branch, + message: "created home page", + author_email: user.email, + author_name: user.name + ) + end + + it 'returns true' do + expect(wiki.has_home_page?).to eq(true) + end + + it 'returns false when #find_page raise an error' do + allow(wiki) + .to receive(:find_page) + .and_raise(StandardError) + + expect(wiki.has_home_page?).to eq(false) + end + end + + context 'when home page does not exist' do + it 'returns false' do + expect(wiki.has_home_page?).to eq(false) + end + end + end + describe '#to_global_id' do it 'returns a global ID' do expect(wiki.to_global_id.to_s).to eq("gid://gitlab/#{wiki.class.name}/#{wiki.id}") @@ -791,6 +825,21 @@ RSpec.shared_examples 'wiki model' do end end + context 'when the repository fails to update' do + let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') } + + it 'returns false and sets error message', :aggregate_failures do + expect(subject.repository) + .to receive(:update_file) + .and_raise(Gitlab::Git::Index::IndexError.new) + + expect(subject.update_page(page.page, content: 'new content', format: :markdown)) + .to eq(false) + expect(subject.error_message) + .to match("Duplicate page: A page with that title already exists") + end + end + context 'when page path does not have a default extension' do let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') } diff --git a/spec/support/shared_examples/observability/csp_shared_examples.rb b/spec/support/shared_examples/observability/csp_shared_examples.rb index 0cd211f69eb..9d6e7e75f4d 100644 --- a/spec/support/shared_examples/observability/csp_shared_examples.rb +++ b/spec/support/shared_examples/observability/csp_shared_examples.rb @@ -31,19 +31,19 @@ RSpec.shared_examples 'observability csp policy' do |controller_class = describe let(:observability_url) { Gitlab::Observability.observability_url } let(:signin_url) do Gitlab::Utils.append_path(Gitlab.config.gitlab.url, - '/users/sign_in') + '/users/sign_in') end let(:oauth_url) do Gitlab::Utils.append_path(Gitlab.config.gitlab.url, - '/oauth/authorize') + '/oauth/authorize') end before do setup_csp_for_controller(controller_class, csp, any_time: true) group.add_developer(user) login_as(user) - allow(Gitlab::Observability).to receive(:observability_enabled?).and_return(true) + stub_feature_flags(observability_group_tab: true) end subject do @@ -67,7 +67,7 @@ RSpec.shared_examples 'observability csp policy' do |controller_class = describe end before do - allow(Gitlab::Observability).to receive(:observability_enabled?).and_return(false) + stub_feature_flags(observability_group_tab: false) end it 'does not add observability urls to the csp header' do @@ -76,23 +76,6 @@ RSpec.shared_examples 'observability csp policy' do |controller_class = describe end end - context 'when checking if observability is enabled' do - let(:csp) do - ActionDispatch::ContentSecurityPolicy.new do |p| - p.frame_src 'https://something.test' - end - end - - it 'check access for a given user and group' do - allow(Gitlab::Observability).to receive(:observability_enabled?) - - get tested_path - - expect(Gitlab::Observability).to have_received(:observability_enabled?) - .with(user, group).at_least(:once) - end - end - context 'when frame-src exists in the CSP config' do let(:csp) do ActionDispatch::ContentSecurityPolicy.new do |p| diff --git a/spec/support/shared_examples/observability/embed_observabilities_examples.rb b/spec/support/shared_examples/observability/embed_observabilities_examples.rb new file mode 100644 index 00000000000..c8d4e9e0d7e --- /dev/null +++ b/spec/support/shared_examples/observability/embed_observabilities_examples.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'embeds observability' do + it 'renders iframe in description' do + page.within('.description') do + expect_observability_iframe(page.html) + end + end + + it 'renders iframe in comment' do + expect(page).not_to have_css('.note-text') + + page.within('.js-main-target-form') do + fill_in('note[note]', with: observable_url) + click_button('Comment') + end + + wait_for_requests + + page.within('.note-text') do + expect_observability_iframe(page.html) + end + end +end + +RSpec.shared_examples 'does not embed observability' do + it 'does not render iframe in description' do + page.within('.description') do + expect_observability_iframe(page.html, to_be_nil: true) + end + end + + it 'does not render iframe in comment' do + expect(page).not_to have_css('.note-text') + + page.within('.js-main-target-form') do + fill_in('note[note]', with: observable_url) + click_button('Comment') + end + + wait_for_requests + + page.within('.note-text') do + expect_observability_iframe(page.html, to_be_nil: true) + end + end +end + +def expect_observability_iframe(html, to_be_nil: false) + iframe = Nokogiri::HTML.parse(html).at_css('#observability-ui-iframe') + + expect(html).to include(observable_url) + + if to_be_nil + expect(iframe).to be_nil + else + expect(iframe).not_to be_nil + iframe_src = "#{expected_observable_url}&theme=light&username=#{user.username}&kiosk=inline-embed" + expect(iframe.attributes['src'].value).to eq(iframe_src) + end +end diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb index 9ec1b8b3f5a..d1f5a01b10c 100644 --- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -401,3 +401,24 @@ RSpec.shared_examples 'package access with repository disabled' do it { is_expected.to be_allowed(:read_package) } end + +RSpec.shared_examples 'equivalent project policy abilities' do + where(:project_visibility, :user_role_on_project) do + project_visibilities = [:public, :internal, :private] + user_role_on_project = [:anonymous, :non_member, :guest, :reporter, :developer, :maintainer, :owner, :admin] + project_visibilities.product(user_role_on_project) + end + + with_them do + it 'evaluates the same' do + project = public_send("#{project_visibility}_project") + current_user = public_send(user_role_on_project) + enable_admin_mode!(current_user) if user_role_on_project == :admin + policy = ProjectPolicy.new(current_user, project) + old_permissions = policy.allowed?(old_policy) + new_permissions = policy.allowed?(new_policy) + + expect(old_permissions).to eq new_permissions + end + end +end 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 f70621673d5..f9f8435c211 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 @@ -7,9 +7,9 @@ RSpec.shared_examples 'when regex matching everything is specified' do end it_behaves_like 'removing the expected tags', - service_response_extra: service_response_extra, - supports_caching: supports_caching, - delete_expectations: delete_expectations + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations context 'with deprecated name_regex param' do let(:params) do @@ -17,9 +17,9 @@ RSpec.shared_examples 'when regex matching everything is specified' do end it_behaves_like 'removing the expected tags', - service_response_extra: service_response_extra, - supports_caching: supports_caching, - delete_expectations: delete_expectations + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations end end @@ -31,9 +31,9 @@ RSpec.shared_examples 'when regex matching everything is specified and latest is end it_behaves_like 'removing the expected tags', - service_response_extra: service_response_extra, - supports_caching: supports_caching, - delete_expectations: delete_expectations + 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 @@ -43,9 +43,9 @@ RSpec.shared_examples 'when delete regex matching specific tags is used' do end it_behaves_like 'removing the expected tags', - service_response_extra: service_response_extra, - supports_caching: supports_caching, - delete_expectations: [%w[C D]] + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: [%w[C D]] end RSpec.shared_examples 'when delete regex matching specific tags is used with overriding allow regex' do @@ -58,9 +58,9 @@ RSpec.shared_examples 'when delete regex matching specific tags is used with ove end it_behaves_like 'removing the expected tags', - service_response_extra: service_response_extra, - supports_caching: supports_caching, - delete_expectations: [%w[D]] + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: [%w[D]] context 'with name_regex_delete overriding deprecated name_regex' do let(:params) do @@ -71,9 +71,9 @@ RSpec.shared_examples 'when delete regex matching specific tags is used with ove end it_behaves_like 'removing the expected tags', - service_response_extra: service_response_extra, - supports_caching: supports_caching, - delete_expectations: [%w[D]] + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: [%w[D]] end end @@ -87,9 +87,9 @@ RSpec.shared_examples 'with allow regex value' do end it_behaves_like 'removing the expected tags', - service_response_extra: service_response_extra, - supports_caching: supports_caching, - delete_expectations: delete_expectations + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations end RSpec.shared_examples 'when keeping only N tags' do @@ -135,9 +135,9 @@ RSpec.shared_examples 'when removing keeping only 3' do end it_behaves_like 'removing the expected tags', - service_response_extra: service_response_extra, - supports_caching: supports_caching, - delete_expectations: delete_expectations + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations end RSpec.shared_examples 'when removing older than 1 day' do @@ -150,9 +150,9 @@ RSpec.shared_examples 'when removing older than 1 day' do end it_behaves_like 'removing the expected tags', - service_response_extra: service_response_extra, - supports_caching: supports_caching, - delete_expectations: delete_expectations + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations end RSpec.shared_examples 'when combining all parameters' do @@ -166,9 +166,9 @@ RSpec.shared_examples 'when combining all parameters' do end it_behaves_like 'removing the expected tags', - service_response_extra: service_response_extra, - supports_caching: supports_caching, - delete_expectations: delete_expectations + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations end RSpec.shared_examples 'when running a container_expiration_policy' do diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/shared_examples/prometheus/additional_metrics_shared_examples.rb index e589baf0909..d196114b227 100644 --- a/spec/support/prometheus/additional_metrics_shared_examples.rb +++ b/spec/support/shared_examples/prometheus/additional_metrics_shared_examples.rb @@ -6,13 +6,13 @@ RSpec.shared_examples 'additional metrics query' do let(:metric_group_class) { Gitlab::Prometheus::MetricGroup } let(:metric_class) { Gitlab::Prometheus::Metric } - let(:metric_names) { %w{metric_a metric_b} } + let(:metric_names) { %w[metric_a metric_b] } let(:query_range_result) do - [{ 'metric': {}, 'values': [[1488758662.506, '0.00002996364761904785'], [1488758722.506, '0.00003090239047619091']] }] + [{ metric: {}, values: [[1488758662.506, '0.00002996364761904785'], [1488758722.506, '0.00003090239047619091']] }] end - let(:client) { double('prometheus_client') } + let(:client) { instance_double('Gitlab::PrometheusClient') } let(:query_result) { described_class.new(client).query(*query_params) } let(:project) { create(:project, :repository) } let(:environment) { create(:environment, slug: 'environment-slug', project: project) } @@ -22,12 +22,13 @@ RSpec.shared_examples 'additional metrics query' do allow(metric_group_class).to receive(:common_metrics).and_return([simple_metric_group(metrics: [simple_metric])]) end - context 'metrics query context' do + describe 'metrics query context' do subject! { described_class.new(client) } shared_examples 'query context containing environment slug and filter' do it 'contains ci_environment_slug' do - expect(subject).to receive(:query_metrics).with(project, environment, hash_including(ci_environment_slug: environment.slug)) + expect(subject) + .to receive(:query_metrics).with(project, environment, hash_including(ci_environment_slug: environment.slug)) subject.query(*query_params) end @@ -54,7 +55,8 @@ RSpec.shared_examples 'additional metrics query' do it_behaves_like 'query context containing environment slug and filter' it 'query context contains kube_namespace' do - expect(subject).to receive(:query_metrics).with(project, environment, hash_including(kube_namespace: kube_namespace)) + expect(subject) + .to receive(:query_metrics).with(project, environment, hash_including(kube_namespace: kube_namespace)) subject.query(*query_params) end @@ -77,7 +79,7 @@ RSpec.shared_examples 'additional metrics query' do allow(metric_group_class).to receive(:common_metrics).and_return([simple_metric_group]) end - context 'some queries return results' do + context 'when some queries return results' do before do allow(client).to receive(:query_range).with('query_range_a', any_args).and_return(query_range_result) allow(client).to receive(:query_range).with('query_range_b', any_args).and_return(query_range_result) @@ -118,7 +120,7 @@ RSpec.shared_examples 'additional metrics query' do allow(client).to receive(:label_values).and_return(metric_names) end - context 'both queries return results' do + context 'when both queries return results' do before do allow(client).to receive(:query_range).with('query_range_a', any_args).and_return(query_range_result) allow(client).to receive(:query_range).with('query_range_b', any_args).and_return(query_range_result) @@ -138,7 +140,7 @@ RSpec.shared_examples 'additional metrics query' do end end - context 'one query returns result' do + context 'when one query returns result' do before do allow(client).to receive(:query_range).with('query_range_a', any_args).and_return(query_range_result) allow(client).to receive(:query_range).with('query_range_b', any_args).and_return([]) diff --git a/spec/support/shared_examples/protected_tags/access_control_ce_shared_examples.rb b/spec/support/shared_examples/protected_tags/access_control_ce_shared_examples.rb new file mode 100644 index 00000000000..f308b4ad372 --- /dev/null +++ b/spec/support/shared_examples/protected_tags/access_control_ce_shared_examples.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +RSpec.shared_examples "protected tags > access control > CE" do + ProtectedRef::AccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| + it "allows creating protected tags that #{access_type_name} can create" do + visit project_protected_tags_path(project) + + set_protected_tag_name('master') + set_allowed_to('create', access_type_name) + click_on_protect + + expect(ProtectedTag.count).to eq(1) + expect(ProtectedTag.last.create_access_levels.map(&:access_level)).to eq([access_type_id]) + end + + it "allows updating protected tags so that #{access_type_name} can create them" do + visit project_protected_tags_path(project) + + set_protected_tag_name('master') + set_allowed_to('create', 'No one') + click_on_protect + + expect(ProtectedTag.count).to eq(1) + + set_allowed_to('create', access_type_name, form: '.protected-tags-list') + + wait_for_requests + + expect(ProtectedTag.last.create_access_levels.map(&:access_level)).to include(access_type_id) + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb index d8690356f81..7cbaf40721a 100644 --- a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'close quick action' do |issuable_type| - include Spec::Support::Helpers::Features::NotesHelpers + include Features::NotesHelpers before do project.add_maintainer(maintainer) diff --git a/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb b/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb index b5704ad8f17..9b03cdbb3bf 100644 --- a/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb @@ -23,11 +23,11 @@ RSpec.shared_examples 'does not exceed the issuable size limit' do end note = described_class.new(project, user, opts.merge( - note: note_text, - noteable_type: noteable_type, - noteable_id: issuable.id, - confidential: false - )).execute + note: note_text, + noteable_type: noteable_type, + noteable_id: issuable.id, + confidential: false + )).execute expect(note.errors[:validation]).to match_array([validation_message]) end @@ -44,11 +44,11 @@ RSpec.shared_examples 'does not exceed the issuable size limit' do end note = described_class.new(project, user, opts.merge( - note: note_text, - noteable_type: noteable_type, - noteable_id: issuable.id, - confidential: false - )).execute + note: note_text, + noteable_type: noteable_type, + noteable_id: issuable.id, + confidential: false + )).execute expect(note.errors[:validation]).to be_empty end diff --git a/spec/support/shared_examples/quick_actions/issue/issue_links_quick_actions_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/issue_links_quick_actions_shared_examples.rb new file mode 100644 index 00000000000..811b5ee4de2 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/issue_links_quick_actions_shared_examples.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'issues link quick action' do |command| + let_it_be_with_refind(:group) { create(:group) } + let_it_be_with_reload(:other_issue) { create(:issue, project: project) } + let_it_be_with_reload(:second_issue) { create(:issue, project: project) } + let_it_be_with_reload(:third_issue) { create(:issue, project: project) } + + let(:link_type) { command == :relate ? 'relates_to' : 'blocks' } + let(:links_query) do + if command == :blocked_by + IssueLink.where(target: issue, link_type: link_type).map(&:source) + else + IssueLink.where(source: issue, link_type: link_type).map(&:target) + end + end + + shared_examples 'link command' do + it 'links issues' do + service.execute(content, issue) + + expect(links_query).to match_array(issues_linked) + end + end + + context 'when user is member of group' do + before do + group.add_developer(user) + end + + context 'when linking a single issue' do + let(:issues_linked) { [other_issue] } + let(:content) { "/#{command} #{other_issue.to_reference}" } + + it_behaves_like 'link command' + end + + context 'when linking multiple issues at once' do + let(:issues_linked) { [second_issue, third_issue] } + let(:content) { "/#{command} #{second_issue.to_reference} #{third_issue.to_reference}" } + + it_behaves_like 'link command' + end + + context 'when quick action target is unpersisted' do + let(:issue) { build(:issue, project: project) } + let(:issues_linked) { [other_issue] } + let(:content) { "/#{command} #{other_issue.to_reference}" } + + it 'links the issues after the issue is persisted' do + service.execute(content, issue) + + issue.save! + + expect(links_query).to match_array(issues_linked) + end + end + + context 'with empty link command' do + let(:issues_linked) { [] } + let(:content) { "/#{command}" } + + it_behaves_like 'link command' + end + + context 'with already having linked issues' do + let(:issues_linked) { [second_issue, third_issue] } + let(:content) { "/#{command} #{third_issue.to_reference(project)}" } + + before do + create_existing_link(command) + end + + it_behaves_like 'link command' + end + + context 'with cross project' do + let_it_be_with_reload(:another_group) { create(:group, :public) } + let_it_be_with_reload(:other_project) { create(:project, group: another_group) } + + before do + another_group.add_developer(user) + [other_issue, second_issue, third_issue].map { |i| i.update!(project: other_project) } + end + + context 'when linking a cross project issue' do + let(:issues_linked) { [other_issue] } + let(:content) { "/#{command} #{other_issue.to_reference(project)}" } + + it_behaves_like 'link command' + end + + context 'when linking multiple cross projects issues at once' do + let(:issues_linked) { [second_issue, third_issue] } + let(:content) { "/#{command} #{second_issue.to_reference(project)} #{third_issue.to_reference(project)}" } + + it_behaves_like 'link command' + end + + context 'when linking a non-existing issue' do + let(:issues_linked) { [] } + let(:content) { "/#{command} imaginary##{non_existing_record_iid}" } + + it_behaves_like 'link command' + end + + context 'when linking a private issue' do + let_it_be(:private_issue) { create(:issue, project: create(:project, :private)) } + let(:issues_linked) { [] } + let(:content) { "/#{command} #{private_issue.to_reference(project)}" } + + it_behaves_like 'link command' + end + end + end + + def create_existing_link(command) + issues = [issue, second_issue] + source, target = command == :blocked_by ? issues.reverse : issues + + create(:issue_link, source: source, target: target, link_type: link_type) + end +end diff --git a/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb index 3f1a98ca08e..7bd7500d546 100644 --- a/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true RSpec.shared_examples 'promote_to_incident quick action' do + include ListboxHelpers + describe '/promote_to_incident' do context 'when issue can be promoted' do it 'promotes issue to incident' do @@ -52,9 +54,11 @@ RSpec.shared_examples 'promote_to_incident quick action' do context 'when incident is selected for issue type' do it 'promotes issue to incident' do visit new_project_issue_path(project) + wait_for_requests + fill_in('Title', with: 'Title') find('.js-issuable-type-filter-dropdown-wrap').click - click_link('Incident') + select_listbox_item(_('Incident')) fill_in('Description', with: '/promote_to_incident') click_button('Create issue') diff --git a/spec/support/redis/redis_new_instance_shared_examples.rb b/spec/support/shared_examples/redis/redis_new_instance_shared_examples.rb index 435d342fcca..4a3732efe13 100644 --- a/spec/support/redis/redis_new_instance_shared_examples.rb +++ b/spec/support/shared_examples/redis/redis_new_instance_shared_examples.rb @@ -6,7 +6,6 @@ RSpec.shared_examples "redis_new_instance_shared_examples" do |name, fallback_cl include TmpdirHelper let(:instance_specific_config_file) { "config/redis.#{name}.yml" } - let(:environment_config_file_name) { "GITLAB_REDIS_#{name.upcase}_CONFIG_FILE" } let(:fallback_config_file) { nil } let(:rails_root) { mktmpdir } @@ -16,32 +15,6 @@ RSpec.shared_examples "redis_new_instance_shared_examples" do |name, fallback_cl it_behaves_like "redis_shared_examples" - describe '.config_file_name' do - subject { described_class.config_file_name } - - before do - # Undo top-level stub of config_file_name because we are testing that method now. - allow(described_class).to receive(:config_file_name).and_call_original - - allow(described_class).to receive(:rails_root).and_return(rails_root) - FileUtils.mkdir_p(File.join(rails_root, 'config')) - end - - context 'and there is a global env override' do - before do - stub_env('GITLAB_REDIS_CONFIG_FILE', 'global override') - end - - it { expect(subject).to eq('global override') } - - context "and #{fallback_class.name.demodulize} has a different config file" do - let(:fallback_config_file) { 'fallback config file' } - - it { expect(subject).to eq('fallback config file') } - end - end - end - describe '#fetch_config' do subject { described_class.new('test').send(:fetch_config) } @@ -98,7 +71,7 @@ RSpec.shared_examples "redis_new_instance_shared_examples" do |name, fallback_cl context 'when resque.yml exists' do before do File.write(File.join(rails_root, 'config/resque.yml'), { - 'test' => { 'foobar' => 123 } + 'test' => { 'foobar' => 123 } }.to_json) end diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/shared_examples/redis/redis_shared_examples.rb index 8c195a9dbeb..9224e01b1fe 100644 --- a/spec/support/redis/redis_shared_examples.rb +++ b/spec/support/shared_examples/redis/redis_shared_examples.rb @@ -39,34 +39,6 @@ RSpec.shared_examples "redis_shared_examples" do context 'when there is no config file anywhere' do it { expect(subject).to be_nil } - - context 'and there is a global env override' do - before do - stub_env('GITLAB_REDIS_CONFIG_FILE', 'global override') - end - - it { expect(subject).to eq('global override') } - - context 'and there is an instance specific config file' do - before do - FileUtils.touch(File.join(rails_root, instance_specific_config_file)) - end - - it { expect(subject).to eq("#{rails_root}/#{instance_specific_config_file}") } - - it 'returns a path that exists' do - expect(File.file?(subject)).to eq(true) - end - - context 'and there is a specific env override' do - before do - stub_env(environment_config_file_name, 'instance specific override') - end - - it { expect(subject).to eq('instance specific override') } - end - end - end end end @@ -87,7 +59,9 @@ RSpec.shared_examples "redis_shared_examples" do context 'with the namespace' do let(:namespace) { 'namespace_name' } - let(:redis_store_to_s) { "Redis Client connected to #{host} against DB #{redis_database} with namespace #{namespace}" } + let(:redis_store_to_s) do + "Redis Client connected to #{host} against DB #{redis_database} with namespace #{namespace}" + end subject { described_class.new(rails_env).store(namespace: namespace) } @@ -188,12 +162,13 @@ RSpec.shared_examples "redis_shared_examples" do 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.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 @@ -237,6 +212,7 @@ RSpec.shared_examples "redis_shared_examples" do before do clear_pool end + after do clear_pool end @@ -407,12 +383,6 @@ RSpec.shared_examples "redis_shared_examples" do end end - it 'has a value for the legacy default URL' do - allow(subject).to receive(:fetch_config) { nil } - - expect(subject.send(:raw_config_hash)).to include(url: a_string_matching(%r{\Aredis://localhost:638[012]\Z})) - end - context 'when redis.yml exists' do subject { described_class.new('test').send(:fetch_config) } @@ -436,11 +406,11 @@ RSpec.shared_examples "redis_shared_examples" do expect(subject).to eq(nil) end - context 'but resque.yml exists' do + context 'when resque.yml exists' do before do FileUtils.mkdir_p(File.join(rails_root, 'config')) File.write(File.join(rails_root, 'config/resque.yml'), { - 'test' => { 'foobar' => 123 } + 'test' => { 'foobar' => 123 } }.to_json) end diff --git a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb index 2170025824f..74dbec063e0 100644 --- a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb +++ b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb @@ -14,7 +14,7 @@ RSpec.shared_examples 'GET resource access tokens available' do it 'lists all available scopes' do get_access_tokens - expect(assigns(:scopes)).to eq(Gitlab::Auth.resource_bot_scopes) + expect(assigns(:scopes)).to eq(Gitlab::Auth.available_scopes_for(resource)) end it 'returns for json response' do diff --git a/spec/support/shared_examples/requests/admin_mode_shared_examples.rb b/spec/support/shared_examples/requests/admin_mode_shared_examples.rb index 07fde7d3f35..4f198dfb740 100644 --- a/spec/support/shared_examples/requests/admin_mode_shared_examples.rb +++ b/spec/support/shared_examples/requests/admin_mode_shared_examples.rb @@ -1,98 +1,79 @@ # frozen_string_literal: true -RSpec.shared_examples 'GET request permissions for admin mode' do - it_behaves_like 'GET request permissions for admin mode when user' - it_behaves_like 'GET request permissions for admin mode when admin' -end - -RSpec.shared_examples 'PUT request permissions for admin mode' do |params| - it_behaves_like 'PUT request permissions for admin mode when user', params - it_behaves_like 'PUT request permissions for admin mode when admin', params -end - -RSpec.shared_examples 'POST request permissions for admin mode' do |params| - it_behaves_like 'POST request permissions for admin mode when user', params - it_behaves_like 'POST request permissions for admin mode when admin', params -end RSpec.shared_examples 'DELETE request permissions for admin mode' do - it_behaves_like 'DELETE request permissions for admin mode when user' - it_behaves_like 'DELETE request permissions for admin mode when admin' -end - -RSpec.shared_examples 'GET request permissions for admin mode when user' do - subject { get api(path, current_user, admin_mode: admin_mode) } + subject { delete api(path, current_user, admin_mode: admin_mode) } - let_it_be(:current_user) { create(:user) } + let_it_be(:success_status_code) { :no_content } + let_it_be(:failed_status_code) { :forbidden } - it_behaves_like 'admin mode on', true, :forbidden - it_behaves_like 'admin mode on', false, :forbidden + it_behaves_like 'when admin' + it_behaves_like 'when user' end -RSpec.shared_examples 'GET request permissions for admin mode when admin' do +RSpec.shared_examples 'GET request permissions for admin mode' do subject { get api(path, current_user, admin_mode: admin_mode) } - let_it_be(:current_user) { create(:admin) } - - it_behaves_like 'admin mode on', true, :ok - it_behaves_like 'admin mode on', false, :forbidden -end - -RSpec.shared_examples 'PUT request permissions for admin mode when user' do |params| - subject { put api(path, current_user, admin_mode: admin_mode), params: params } - - let_it_be(:current_user) { create(:user) } + let_it_be(:success_status_code) { :ok } + let_it_be(:failed_status_code) { :forbidden } - it_behaves_like 'admin mode on', true, :forbidden - it_behaves_like 'admin mode on', false, :forbidden + it_behaves_like 'when admin' + it_behaves_like 'when user' end -RSpec.shared_examples 'PUT request permissions for admin mode when admin' do |params| +RSpec.shared_examples 'PUT request permissions for admin mode' do subject { put api(path, current_user, admin_mode: admin_mode), params: params } - let_it_be(:current_user) { create(:admin) } + let_it_be(:success_status_code) { :ok } + let_it_be(:failed_status_code) { :forbidden } - it_behaves_like 'admin mode on', true, :ok - it_behaves_like 'admin mode on', false, :forbidden + it_behaves_like 'when admin' + it_behaves_like 'when user' end -RSpec.shared_examples 'POST request permissions for admin mode when user' do |params| +RSpec.shared_examples 'POST request permissions for admin mode' do subject { post api(path, current_user, admin_mode: admin_mode), params: params } - let_it_be(:current_user) { create(:user) } + let_it_be(:success_status_code) { :created } + let_it_be(:failed_status_code) { :forbidden } - it_behaves_like 'admin mode on', true, :forbidden - it_behaves_like 'admin mode on', false, :forbidden + it_behaves_like 'when admin' + it_behaves_like 'when user' end -RSpec.shared_examples 'POST request permissions for admin mode when admin' do |params| - subject { post api(path, current_user, admin_mode: admin_mode), params: params } +RSpec.shared_examples 'when user' do + let_it_be(:current_user) { create(:user) } - let_it_be(:current_user) { create(:admin) } + include_examples 'makes request' do + let(:status) { failed_status_code } + let(:admin_mode) { true } + end - it_behaves_like 'admin mode on', true, :created - it_behaves_like 'admin mode on', false, :forbidden + it_behaves_like 'makes request' do + let(:status) { failed_status_code } + let(:admin_mode) { false } + end end -RSpec.shared_examples 'DELETE request permissions for admin mode when user' do - subject { delete api(path, current_user, admin_mode: admin_mode) } +RSpec.shared_examples 'when admin' do + let_it_be(:current_user) { create(:admin) } - let_it_be(:current_user) { create(:user) } + it_behaves_like 'makes request' do + let(:status) { success_status_code } + let(:admin_mode) { true } + end - it_behaves_like 'admin mode on', true, :forbidden - it_behaves_like 'admin mode on', false, :forbidden + it_behaves_like 'makes request' do + let(:status) { failed_status_code } + let(:admin_mode) { false } + end end -RSpec.shared_examples 'DELETE request permissions for admin mode when admin' do - subject { delete api(path, current_user, admin_mode: admin_mode) } - - let_it_be(:current_user) { create(:admin) } - - it_behaves_like 'admin mode on', true, :no_content - it_behaves_like 'admin mode on', false, :forbidden -end +RSpec.shared_examples "makes request" do + let_it_be(:status) { nil } -RSpec.shared_examples "admin mode on" do |admin_mode, status| - let_it_be(:admin_mode) { admin_mode } + it "returns" do + subject - it_behaves_like 'returning response status', status + expect(response).to have_gitlab_http_status(status) + end end diff --git a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb index f5c41416763..3ff52166990 100644 --- a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb @@ -18,7 +18,7 @@ RSpec.shared_examples 'returns repositories for allowed users' do |user_type, sc subject expect(json_response.length).to eq(2) - expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + expect(json_response.pluck('id')).to contain_exactly( root_repository.id, test_repository.id) expect(response.body).not_to include('tags') expect(response.body).not_to include('tags_count') @@ -47,7 +47,7 @@ RSpec.shared_examples 'returns tags for allowed users' do |user_type, scope| subject expect(json_response.length).to eq(2) - expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + expect(json_response.pluck('id')).to contain_exactly( root_repository.id, test_repository.id) expect(response.body).to include('tags') end diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb index f31cbcfdec1..804221b7416 100644 --- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb @@ -4,7 +4,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| let!(:custom_attribute1) { attributable.custom_attributes.create! key: 'foo', value: 'foo' } let!(:custom_attribute2) { attributable.custom_attributes.create! key: 'bar', value: 'bar' } - describe "GET /#{attributable_name} with custom attributes filter" do + describe "GET /#{attributable_name} with custom attributes filter", :aggregate_failures do before do other_attributable end @@ -14,13 +14,13 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| get api("/#{attributable_name}", user), params: { custom_attributes: { foo: 'foo', bar: 'bar' } } expect(response).to have_gitlab_http_status(:ok) - expect(json_response.map { |r| r['id'] }).to include(attributable.id, other_attributable.id) + expect(json_response.pluck('id')).to include(attributable.id, other_attributable.id) end end context 'with an authorized user' do it 'filters by custom attributes' do - get api("/#{attributable_name}", admin), params: { custom_attributes: { foo: 'foo', bar: 'bar' } } + get api("/#{attributable_name}", admin, admin_mode: true), params: { custom_attributes: { foo: 'foo', bar: 'bar' } } expect(response).to have_gitlab_http_status(:ok) expect(json_response.size).to be 1 @@ -29,7 +29,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| end end - describe "GET /#{attributable_name} with custom attributes" do + describe "GET /#{attributable_name} with custom attributes", :aggregate_failures do before do other_attributable end @@ -46,7 +46,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| context 'with an authorized user' do it 'does not include custom attributes by default' do - get api("/#{attributable_name}", admin) + get api("/#{attributable_name}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response).not_to be_empty @@ -54,7 +54,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| end it 'includes custom attributes if requested' do - get api("/#{attributable_name}", admin), params: { with_custom_attributes: true } + get api("/#{attributable_name}", admin, admin_mode: true), params: { with_custom_attributes: true } expect(response).to have_gitlab_http_status(:ok) expect(json_response).not_to be_empty @@ -72,7 +72,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| end end - describe "GET /#{attributable_name}/:id with custom attributes" do + describe "GET /#{attributable_name}/:id with custom attributes", :aggregate_failures do context 'with an unauthorized user' do it 'does not include custom attributes' do get api("/#{attributable_name}/#{attributable.id}", user), params: { with_custom_attributes: true } @@ -84,14 +84,14 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| context 'with an authorized user' do it 'does not include custom attributes by default' do - get api("/#{attributable_name}/#{attributable.id}", admin) + get api("/#{attributable_name}/#{attributable.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response).not_to include 'custom_attributes' end it 'includes custom attributes if requested' do - get api("/#{attributable_name}/#{attributable.id}", admin), params: { with_custom_attributes: true } + get api("/#{attributable_name}/#{attributable.id}", admin, admin_mode: true), params: { with_custom_attributes: true } expect(response).to have_gitlab_http_status(:ok) expect(json_response['custom_attributes']).to contain_exactly( @@ -102,7 +102,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| end end - describe "GET /#{attributable_name}/:id/custom_attributes" do + describe "GET /#{attributable_name}/:id/custom_attributes", :aggregate_failures do context 'with an unauthorized user' do subject { get api("/#{attributable_name}/#{attributable.id}/custom_attributes", user) } @@ -111,7 +111,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| context 'with an authorized user' do it 'returns all custom attributes' do - get api("/#{attributable_name}/#{attributable.id}/custom_attributes", admin) + get api("/#{attributable_name}/#{attributable.id}/custom_attributes", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response).to contain_exactly( @@ -122,7 +122,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| end end - describe "GET /#{attributable_name}/:id/custom_attributes/:key" do + describe "GET /#{attributable_name}/:id/custom_attributes/:key", :aggregate_failures do context 'with an unauthorized user' do subject { get api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", user) } @@ -131,7 +131,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| context 'with an authorized user' do it 'returns a single custom attribute' do - get api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin) + get api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response).to eq({ 'key' => 'foo', 'value' => 'foo' }) @@ -139,7 +139,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| end end - describe "PUT /#{attributable_name}/:id/custom_attributes/:key" do + describe "PUT /#{attributable_name}/:id/custom_attributes/:key", :aggregate_failures do context 'with an unauthorized user' do subject { put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", user), params: { value: 'new' } } @@ -149,7 +149,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| context 'with an authorized user' do it 'creates a new custom attribute' do expect do - put api("/#{attributable_name}/#{attributable.id}/custom_attributes/new", admin), params: { value: 'new' } + put api("/#{attributable_name}/#{attributable.id}/custom_attributes/new", admin, admin_mode: true), params: { value: 'new' } end.to change { attributable.custom_attributes.count }.by(1) expect(response).to have_gitlab_http_status(:ok) @@ -159,7 +159,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| it 'updates an existing custom attribute' do expect do - put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin), params: { value: 'new' } + put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin, admin_mode: true), params: { value: 'new' } end.not_to change { attributable.custom_attributes.count } expect(response).to have_gitlab_http_status(:ok) @@ -169,7 +169,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| end end - describe "DELETE /#{attributable_name}/:id/custom_attributes/:key" do + describe "DELETE /#{attributable_name}/:id/custom_attributes/:key", :aggregate_failures do context 'with an unauthorized user' do subject { delete api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", user) } @@ -179,7 +179,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| context 'with an authorized user' do it 'deletes an existing custom attribute' do expect do - delete api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin) + delete api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin, admin_mode: true) end.to change { attributable.custom_attributes.count }.by(-1) expect(response).to have_gitlab_http_status(:no_content) diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb index 6d29076da0f..bc7ad570441 100644 --- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb @@ -165,3 +165,41 @@ RSpec.shared_examples 'Debian packages write endpoint' do |desired_behavior, suc it_behaves_like 'rejects Debian access with unknown container id', :unauthorized, :basic end + +RSpec.shared_examples 'Debian packages endpoint catching ObjectStorage::RemoteStoreError' do + include_context 'Debian repository access', :public, :developer, :basic do + it "returns forbidden" do + expect(::Packages::Debian::CreatePackageFileService).to receive(:new).and_raise ObjectStorage::RemoteStoreError + + subject + + expect(response).to have_gitlab_http_status(:forbidden) + end + end +end + +RSpec.shared_examples 'Debian packages index endpoint' do |success_body| + it_behaves_like 'Debian packages read endpoint', 'GET', :success, success_body + + context 'when no ComponentFile is found' do + let(:target_component_name) { component.name + FFaker::Lorem.word } + + it_behaves_like 'Debian packages read endpoint', 'GET', :no_content, /^$/ + end +end + +RSpec.shared_examples 'Debian packages index sha256 endpoint' do |success_body| + it_behaves_like 'Debian packages read endpoint', 'GET', :success, success_body + + context 'with empty checksum' do + let(:target_sha256) { 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' } + + it_behaves_like 'Debian packages read endpoint', 'GET', :no_content, /^$/ + end + + context 'when ComponentFile is not found' do + let(:target_component_name) { component.name + FFaker::Lorem.word } + + it_behaves_like 'Debian packages read endpoint', 'GET', :not_found, /^{"message":"404 Not Found"}$/ + end +end diff --git a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb index f577e2ad323..2996c794e52 100644 --- a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb @@ -123,18 +123,6 @@ RSpec.shared_examples 'discussions API' do |parent_type, noteable_type, id_name, expect_snowplow_event(category: 'Notes::CreateService', action: 'execute', label: 'note', value: anything) end - context 'with notes_create_service_tracking feature flag disabled' do - before do - stub_feature_flags(notes_create_service_tracking: false) - end - - it 'does not track Notes::CreateService events', :snowplow do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), params: { body: 'hi!' } - - expect_no_snowplow_event(category: 'Notes::CreateService', action: 'execute') - end - end - context 'when an admin or owner makes the request' do it 'accepts the creation date to be set' do creation_time = 2.weeks.ago @@ -243,8 +231,7 @@ RSpec.shared_examples 'discussions API' do |parent_type, noteable_type, id_name, it 'returns a 404 error when note id not found' do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}/notes/#{non_existing_record_id}", user), - params: { body: 'Hello!' } + "discussions/#{note.discussion_id}/notes/#{non_existing_record_id}", user), params: { body: 'Hello!' } expect(response).to have_gitlab_http_status(:not_found) 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 6c8b792bf92..930c47dac52 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 @@ -480,6 +480,7 @@ RSpec.shared_examples 'graphql issue list request spec' do context 'when fetching escalation status' do let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue_a) } + let_it_be(:incident_type) { WorkItems::Type.default_by_type(:incident) } let(:fields) do <<~QUERY @@ -491,7 +492,7 @@ RSpec.shared_examples 'graphql issue list request spec' do end before do - issue_a.update_columns(issue_type: Issue.issue_types[:incident]) + issue_a.update_columns(issue_type: WorkItems::Type.base_types[:incident], work_item_type_id: incident_type.id) end it 'returns the escalation status values' do diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb index b459e479c91..53329c5caec 100644 --- a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb @@ -6,7 +6,7 @@ RSpec.shared_examples 'when the snippet is not found' do end it_behaves_like 'a mutation that returns top-level errors', - errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] end RSpec.shared_examples 'snippet edit usage data counters' do diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/subscription_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/subscription_shared_examples.rb index 40b88ef370f..4dc0264172f 100644 --- a/spec/support/shared_examples/requests/api/graphql/mutations/subscription_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/mutations/subscription_shared_examples.rb @@ -36,9 +36,9 @@ RSpec.shared_examples 'a subscribable resource api' do context 'when the user is not authorized' do 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 context 'when user is authorized' do 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 f5835460a77..5e9dfc826d4 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 @@ -279,11 +279,11 @@ RSpec.shared_examples 'group and project packages query' do end def npm_pipeline_ids - graphql_data_npm_package.dig('pipelines', 'nodes').map { |pipeline| pipeline['id'] } + graphql_data_npm_package.dig('pipelines', 'nodes').pluck('id') end def composer_pipeline_ids - graphql_data_composer_package.dig('pipelines', 'nodes').map { |pipeline| pipeline['id'] } + graphql_data_composer_package.dig('pipelines', 'nodes').pluck('id') end def graphql_data_npm_package diff --git a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb index b4019d7c232..161f4a02b8c 100644 --- a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb @@ -38,7 +38,7 @@ RSpec.shared_examples 'a package with files' do context 'with package files pending destruction' do let_it_be(:package_file_pending_destruction) { create(:package_file, :pending_destruction, package: package) } - let(:response_package_file_ids) { package_files_response.map { |pf| pf['id'] } } + let(:response_package_file_ids) { package_files_response.pluck('id') } it 'does not return them' do expect(package.reload.package_files).to include(package_file_pending_destruction) 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 6b4d8cae2ce..6648c18fb70 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 @@ -13,13 +13,15 @@ RSpec.shared_examples 'a GraphQL query for access levels' do |access_level_kind| let(:maintainer_access_level) { access_levels.for_role.first } let(:maintainer_access_level_data) { access_levels_data.first } let(:access_levels_data) do - graphql_data_at('project', - 'branchRules', - 'nodes', - 0, - 'branchProtection', - "#{access_level_kind.to_s.camelize(:lower)}AccessLevels", - 'nodes') + graphql_data_at( + 'project', + 'branchRules', + 'nodes', + 0, + 'branchProtection', + "#{access_level_kind.to_s.camelize(:lower)}AccessLevels", + 'nodes' + ) end let(:query) do diff --git a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb index f2002de4b55..a2c34aa6a54 100644 --- a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true RSpec.shared_examples 'web-hook API endpoints test hook' do |prefix| - describe "POST #{prefix}/:hook_id" do + describe "POST #{prefix}/:hook_id", :aggregate_failures do it 'tests the hook' do expect(WebHookService) .to receive(:new).with(hook, anything, String, force: false) .and_return(instance_double(WebHookService, execute: nil)) - post api(hook_uri, user) + post api(hook_uri, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:created) end @@ -17,7 +17,7 @@ end RSpec.shared_examples 'web-hook API endpoints with branch-filter' do |prefix| describe "POST #{prefix}/hooks" do it "returns a 422 error if branch filter is not valid" do - post api(collection_uri, user), + post api(collection_uri, user, admin_mode: user.admin?), params: { url: "http://example.com", push_events_branch_filter: '~badbranchname/' } expect(response).to have_gitlab_http_status(:unprocessable_entity) @@ -58,10 +58,10 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| let(:default_values) { {} } - describe "GET #{prefix}/hooks" do + describe "GET #{prefix}/hooks", :aggregate_failures do context "authorized user" do it "returns all hooks" do - get api(collection_uri, user) + get api(collection_uri, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:ok) expect(response).to match_collection_schema @@ -70,7 +70,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| context "when user is forbidden" do it "prevents access to hooks" do - get api(collection_uri, unauthorized_user) + get api(collection_uri, unauthorized_user, admin_mode: true) expect(response).to have_gitlab_http_status(:forbidden) end @@ -90,7 +90,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| end it 'returns the names of the url variables' do - get api(collection_uri, user) + get api(collection_uri, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:ok) expect(json_response).to contain_exactly( @@ -102,10 +102,10 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| end end - describe "GET #{prefix}/hooks/:hook_id" do + describe "GET #{prefix}/hooks/:hook_id", :aggregate_failures do context "authorized user" do it "returns a project hook" do - get api(hook_uri, user) + get api(hook_uri, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:ok) expect(response).to match_hook_schema @@ -114,7 +114,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| end it "returns a 404 error if hook id is not available" do - get api(hook_uri(non_existing_record_id), user) + get api(hook_uri(non_existing_record_id), user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end @@ -125,7 +125,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| end it "has the correct alert status", :aggregate_failures do - get api(hook_uri, user) + get api(hook_uri, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:ok) @@ -135,12 +135,12 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| context 'the hook is backed-off' do before do - WebHook::FAILURE_THRESHOLD.times { hook.backoff! } + WebHooks::AutoDisabling::FAILURE_THRESHOLD.times { hook.backoff! } hook.backoff! end it "has the correct alert status", :aggregate_failures do - get api(hook_uri, user) + get api(hook_uri, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:ok) @@ -156,7 +156,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| context "when user is forbidden" do it "does not access an existing hook" do - get api(hook_uri, unauthorized_user) + get api(hook_uri, unauthorized_user, admin_mode: true) expect(response).to have_gitlab_http_status(:forbidden) end @@ -171,13 +171,12 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| end end - describe "POST #{prefix}/hooks" do + describe "POST #{prefix}/hooks", :aggregate_failures do let(:hook_creation_params) { hook_params } it "adds hook", :aggregate_failures do expect do - post api(collection_uri, user), - params: hook_creation_params + post api(collection_uri, user, admin_mode: user.admin?), params: hook_creation_params end.to change { hooks_count }.by(1) expect(response).to have_gitlab_http_status(:created) @@ -201,8 +200,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| token = "secret token" expect do - post api(collection_uri, user), - params: { url: "http://example.com", token: token } + post api(collection_uri, user, admin_mode: user.admin?), params: { url: "http://example.com", token: token } end.to change { hooks_count }.by(1) expect(response).to have_gitlab_http_status(:created) @@ -216,19 +214,19 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| end it "returns a 400 error if url not given" do - post api(collection_uri, user), params: { event_names.first => true } + post api(collection_uri, user, admin_mode: user.admin?), params: { event_names.first => true } expect(response).to have_gitlab_http_status(:bad_request) end it "returns a 400 error if no parameters are provided" do - post api(collection_uri, user) + post api(collection_uri, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:bad_request) end it 'sets default values for events', :aggregate_failures do - post api(collection_uri, user), params: { url: 'http://mep.mep' } + post api(collection_uri, user, admin_mode: user.admin?), params: { url: 'http://mep.mep' } expect(response).to have_gitlab_http_status(:created) expect(response).to match_hook_schema @@ -239,22 +237,22 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| end it "returns a 422 error if token not valid" do - post api(collection_uri, user), + post api(collection_uri, user, admin_mode: user.admin?), params: { url: "http://example.com", token: "foo\nbar" } expect(response).to have_gitlab_http_status(:unprocessable_entity) end it "returns a 422 error if url not valid" do - post api(collection_uri, user), params: { url: "ftp://example.com" } + post api(collection_uri, user, admin_mode: user.admin?), params: { url: "ftp://example.com" } expect(response).to have_gitlab_http_status(:unprocessable_entity) end end - describe "PUT #{prefix}/hooks/:hook_id" do + describe "PUT #{prefix}/hooks/:hook_id", :aggregate_failures do it "updates an existing hook" do - put api(hook_uri, user), params: update_params + put api(hook_uri, user, admin_mode: user.admin?), params: update_params expect(response).to have_gitlab_http_status(:ok) expect(response).to match_hook_schema @@ -267,7 +265,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| it 'updates the URL variables' do hook.update!(url_variables: { 'abc' => 'some value' }) - put api(hook_uri, user), + put api(hook_uri, user, admin_mode: user.admin?), params: { url_variables: [{ key: 'def', value: 'other value' }] } expect(response).to have_gitlab_http_status(:ok) @@ -280,7 +278,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| it "adds the token without including it in the response" do token = "secret token" - put api(hook_uri, user), params: { url: "http://example.org", token: token } + put api(hook_uri, user, admin_mode: user.admin?), params: { url: "http://example.org", token: token } expect(response).to have_gitlab_http_status(:ok) expect(json_response["url"]).to eq("http://example.org") @@ -291,68 +289,68 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| end it "returns 404 error if hook id not found" do - put api(hook_uri(non_existing_record_id), user), params: { url: 'http://example.org' } + put api(hook_uri(non_existing_record_id), user, admin_mode: user.admin?), params: { url: 'http://example.org' } expect(response).to have_gitlab_http_status(:not_found) end it "returns 400 error if no parameters are provided" do - put api(hook_uri, user) + put api(hook_uri, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:bad_request) end it "returns a 422 error if url is not valid" do - put api(hook_uri, user), params: { url: 'ftp://example.com' } + put api(hook_uri, user, admin_mode: user.admin?), params: { url: 'ftp://example.com' } expect(response).to have_gitlab_http_status(:unprocessable_entity) end it "returns a 422 error if token is not valid" do - put api(hook_uri, user), params: { token: %w[foo bar].join("\n") } + put api(hook_uri, user, admin_mode: user.admin?), params: { token: %w[foo bar].join("\n") } expect(response).to have_gitlab_http_status(:unprocessable_entity) end end - describe "DELETE /projects/:id/hooks/:hook_id" do + describe "DELETE /projects/:id/hooks/:hook_id", :aggregate_failures do it "deletes hook from project" do expect do - delete api(hook_uri, user) + delete api(hook_uri, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:no_content) end.to change { hooks_count }.by(-1) end it "returns a 404 error when deleting non existent hook" do - delete api(hook_uri(non_existing_record_id), user) + delete api(hook_uri(non_existing_record_id), user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end it "returns a 404 error if hook id not given" do - delete api(collection_uri, user) + delete api(collection_uri, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end it "returns forbidden if a user attempts to delete hooks they do not own" do - delete api(hook_uri, unauthorized_user) + delete api(hook_uri, unauthorized_user, admin_mode: true) expect(response).to have_gitlab_http_status(:forbidden) expect(WebHook.exists?(hook.id)).to be_truthy end it_behaves_like '412 response' do - let(:request) { api(hook_uri, user) } + let(:request) { api(hook_uri, user, admin_mode: user.admin?) } end end describe "PUT #{prefix}/hooks/:hook_id/url_variables/:key", :aggregate_failures do it 'sets the variable' do expect do - put api("#{hook_uri}/url_variables/abc", user), - params: { value: 'some secret value' } + put api("#{hook_uri}/url_variables/abc", user, admin_mode: user.admin?), + params: { value: 'some secret value' } end.to change { hook.reload.url_variables }.to(eq('abc' => 'some secret value')) expect(response).to have_gitlab_http_status(:no_content) @@ -361,30 +359,30 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| it 'overwrites existing values' do hook.update!(url_variables: { 'abc' => 'xyz', 'def' => 'other value' }) - put api("#{hook_uri}/url_variables/abc", user), - params: { value: 'some secret value' } + put api("#{hook_uri}/url_variables/abc", user, admin_mode: user.admin?), + params: { value: 'some secret value' } expect(response).to have_gitlab_http_status(:no_content) expect(hook.reload.url_variables).to eq('abc' => 'some secret value', 'def' => 'other value') end it "returns a 404 error when editing non existent hook" do - put api("#{hook_uri(non_existing_record_id)}/url_variables/abc", user), - params: { value: 'xyz' } + put api("#{hook_uri(non_existing_record_id)}/url_variables/abc", user, admin_mode: user.admin?), + params: { value: 'xyz' } expect(response).to have_gitlab_http_status(:not_found) end it "returns a 422 error when the key is illegal" do - put api("#{hook_uri}/url_variables/abc%20def", user), - params: { value: 'xyz' } + put api("#{hook_uri}/url_variables/abc%20def", user, admin_mode: user.admin?), + params: { value: 'xyz' } expect(response).to have_gitlab_http_status(:unprocessable_entity) end it "returns a 422 error when the value is illegal" do - put api("#{hook_uri}/url_variables/abc", user), - params: { value: '' } + put api("#{hook_uri}/url_variables/abc", user, admin_mode: user.admin?), + params: { value: '' } expect(response).to have_gitlab_http_status(:unprocessable_entity) end @@ -397,7 +395,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| it 'unsets the variable' do expect do - delete api("#{hook_uri}/url_variables/abc", user) + delete api("#{hook_uri}/url_variables/abc", user, admin_mode: user.admin?) end.to change { hook.reload.url_variables }.to(eq({ 'def' => 'other value' })) expect(response).to have_gitlab_http_status(:no_content) @@ -406,13 +404,13 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| it 'returns 404 for keys that do not exist' do hook.update!(url_variables: { 'def' => 'other value' }) - delete api("#{hook_uri}/url_variables/abc", user) + delete api("#{hook_uri}/url_variables/abc", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end it "returns a 404 error when deleting a variable from a non existent hook" do - delete api(hook_uri(non_existing_record_id) + "/url_variables/abc", user) + delete api(hook_uri(non_existing_record_id) + "/url_variables/abc", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end diff --git a/spec/support/shared_examples/requests/api/integrations/github_enterprise_jira_dvcs_end_of_life_shared_examples.rb b/spec/support/shared_examples/requests/api/integrations/github_enterprise_jira_dvcs_end_of_life_shared_examples.rb new file mode 100644 index 00000000000..6799dec7b80 --- /dev/null +++ b/spec/support/shared_examples/requests/api/integrations/github_enterprise_jira_dvcs_end_of_life_shared_examples.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do + it 'is a reachable endpoint' do + subject + + expect(response).not_to have_gitlab_http_status(:not_found) + end + + context 'when the flag is disabled' do + before do + stub_feature_flags(jira_dvcs_end_of_life_amnesty: false) + end + + it 'presents as an endpoint that does not exist' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end +end diff --git a/spec/support/shared_examples/requests/api/integrations/slack/slack_request_verification_shared_examples.rb b/spec/support/shared_examples/requests/api/integrations/slack/slack_request_verification_shared_examples.rb new file mode 100644 index 00000000000..ddda9ca6bcc --- /dev/null +++ b/spec/support/shared_examples/requests/api/integrations/slack/slack_request_verification_shared_examples.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'Slack request verification' do + describe 'unauthorized request' do + shared_examples 'an unauthorized request' do + specify do + subject + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + shared_examples 'a successful request that generates a tracked error' do + specify do + expect(Gitlab::ErrorTracking).to receive(:track_exception).once + + subject + + expect(response).to have_gitlab_http_status(:no_content) + expect(response.body).to be_empty + end + end + + context 'when the slack_app_signing_secret setting is not set' do + before do + stub_application_setting(slack_app_signing_secret: nil) + end + + it_behaves_like 'an unauthorized request' + end + + context 'when the timestamp header has expired' do + before do + headers[::API::Integrations::Slack::Request::VERIFICATION_TIMESTAMP_HEADER] = 5.minutes.ago.to_i.to_s + end + + it_behaves_like 'an unauthorized request' + end + + context 'when the timestamp header is missing' do + before do + headers.delete(::API::Integrations::Slack::Request::VERIFICATION_TIMESTAMP_HEADER) + end + + it_behaves_like 'an unauthorized request' + end + + context 'when the signature header is missing' do + before do + headers.delete(::API::Integrations::Slack::Request::VERIFICATION_SIGNATURE_HEADER) + end + + it_behaves_like 'an unauthorized request' + end + + context 'when the signature is not verified' do + before do + headers[::API::Integrations::Slack::Request::VERIFICATION_SIGNATURE_HEADER] = 'unverified_signature' + end + + it_behaves_like 'an unauthorized request' + end + + context 'when type param is missing' do + it_behaves_like 'a successful request that generates a tracked error' + end + end +end diff --git a/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb b/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb index 1045a92f332..e2c9874e7fc 100644 --- a/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb @@ -34,5 +34,14 @@ RSpec.shared_examples 'issuable update endpoint' do expect(json_response['labels']).to include '&' expect(json_response['labels']).to include '?' end + + it 'clears milestone when milestone_id=0' do + entity.update!(milestone: milestone) + + put api(url, user), params: { milestone_id: 0 } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['milestone']).to be_nil + end end end diff --git a/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb b/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb index 41d21490343..fba0533251a 100644 --- a/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb @@ -9,6 +9,6 @@ RSpec.shared_examples 'fetches labels' do expect(json_response).to be_an Array expect(json_response).to all(match_schema('public_api/v4/labels/label')) expect(json_response.size).to eq(expected_labels.size) - expect(json_response.map { |r| r['name'] }).to match_array(expected_labels) + expect(json_response.pluck('name')).to match_array(expected_labels) end end diff --git a/spec/support/shared_examples/requests/api/milestones_shared_examples.rb b/spec/support/shared_examples/requests/api/milestones_shared_examples.rb index 1ea11ba3d7c..ee7d0e86771 100644 --- a/spec/support/shared_examples/requests/api/milestones_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/milestones_shared_examples.rb @@ -52,7 +52,7 @@ RSpec.shared_examples 'group and project milestones' do |route_definition| expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Array expect(json_response.length).to eq(2) - expect(json_response.map { |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id]) + expect(json_response.pluck('id')).to match_array([closed_milestone.id, other_milestone.id]) end it 'does not return any milestone if none found' do @@ -293,7 +293,7 @@ RSpec.shared_examples 'group and project milestones' do |route_definition| expect(json_response).to be_an Array # 2 for projects, 3 for group(which has another project with an issue) expect(json_response.size).to be_between(2, 3) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) + expect(json_response.pluck('id')).to include(issue.id, confidential_issue.id) end it 'does not return confidential issues to team members with guest role' do @@ -306,7 +306,7 @@ RSpec.shared_examples 'group and project milestones' do |route_definition| expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id) + expect(json_response.pluck('id')).to include(issue.id) end it 'does not return confidential issues to regular users' do @@ -316,7 +316,7 @@ RSpec.shared_examples 'group and project milestones' do |route_definition| expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id) + expect(json_response.pluck('id')).to include(issue.id) end it 'returns issues ordered by label priority' do diff --git a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb new file mode 100644 index 00000000000..2ca62698daf --- /dev/null +++ b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'MLflow|Not Found - Resource Does Not Exist' do + it "is Resource Does Not Exist", :aggregate_failures do + is_expected.to have_gitlab_http_status(:not_found) + + expect(json_response).to include({ "error_code" => 'RESOURCE_DOES_NOT_EXIST' }) + end +end + +RSpec.shared_examples 'MLflow|Requires api scope' do + context 'when user has access but token has wrong scope' do + let(:access_token) { tokens[:read] } + + it { is_expected.to have_gitlab_http_status(:forbidden) } + end +end + +RSpec.shared_examples 'MLflow|Requires read_api scope' do + context 'when user has access but token has wrong scope' do + let(:access_token) { tokens[:no_access] } + + it { is_expected.to have_gitlab_http_status(:forbidden) } + end +end + +RSpec.shared_examples 'MLflow|Bad Request' do + it "is Bad Request" do + is_expected.to have_gitlab_http_status(:bad_request) + end +end + +RSpec.shared_examples 'MLflow|shared error cases' do + context 'when not authenticated' do + let(:headers) { {} } + + it "is Unauthorized" do + is_expected.to have_gitlab_http_status(:unauthorized) + end + end + + context 'when user does not have access' do + let(:access_token) { tokens[:different_user] } + + it "is Not Found" do + is_expected.to have_gitlab_http_status(:not_found) + end + end + + context 'when ff is disabled' do + let(:ff_value) { false } + + it "is Not Found" do + is_expected.to have_gitlab_http_status(:not_found) + end + end +end + +RSpec.shared_examples 'MLflow|Bad Request on missing required' do |keys| + keys.each do |key| + context "when \"#{key}\" is missing" do + let(:params) { default_params.tap { |p| p.delete(key) } } + + it "is Bad Request" do + is_expected.to have_gitlab_http_status(:bad_request) + end + end + 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 efe5ed3bcf9..b44ff952cdf 100644 --- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| - describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes", :aggregate_failures do context 'sorting' do before do params = { noteable: noteable, author: user } @@ -12,9 +12,9 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| context 'without sort params' do it 'sorts by created_at in descending order by default' do - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?) - response_dates = json_response.map { |note| note['created_at'] } + response_dates = json_response.pluck('created_at') expect(json_response.length).to eq(4) expect(response_dates).to eq(response_dates.sort.reverse) @@ -23,7 +23,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it 'fetches notes using parent path as id paremeter' do parent_id = CGI.escape(parent.full_path) - get api("/#{parent_type}/#{parent_id}/#{noteable_type}/#{noteable[id_name]}/notes", user) + get api("/#{parent_type}/#{parent_id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:ok) end @@ -40,18 +40,18 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| end it 'page breaks first page correctly' do - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?per_page=4", user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?per_page=4", user, admin_mode: user.admin?) - response_ids = json_response.map { |note| note['id'] } + response_ids = json_response.pluck('id') expect(response_ids).to include(@note2.id) expect(response_ids).not_to include(@first_note.id) end it 'page breaks second page correctly' do - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?per_page=4&page=2", user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?per_page=4&page=2", user, admin_mode: user.admin?) - response_ids = json_response.map { |note| note['id'] } + response_ids = json_response.pluck('id') expect(response_ids).not_to include(@note2.id) expect(response_ids).to include(@first_note.id) @@ -60,27 +60,27 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| end it 'sorts by ascending order when requested' do - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?sort=asc", user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?sort=asc", user, admin_mode: user.admin?) - response_dates = json_response.map { |note| note['created_at'] } + response_dates = json_response.pluck('created_at') expect(json_response.length).to eq(4) expect(response_dates).to eq(response_dates.sort) end it 'sorts by updated_at in descending order when requested' do - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at", user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at", user, admin_mode: user.admin?) - response_dates = json_response.map { |note| note['updated_at'] } + response_dates = json_response.pluck('updated_at') expect(json_response.length).to eq(4) expect(response_dates).to eq(response_dates.sort.reverse) end it 'sorts by updated_at in ascending order when requested' do - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at&sort=asc", user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at&sort=asc", user, admin_mode: user.admin?) - response_dates = json_response.map { |note| note['updated_at'] } + response_dates = json_response.pluck('updated_at') expect(json_response.length).to eq(4) expect(response_dates).to eq(response_dates.sort) @@ -88,7 +88,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| end it "returns an array of notes" do - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -97,7 +97,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| end it "returns a 404 error when noteable id not found" do - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{non_existing_record_id}/notes", user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{non_existing_record_id}/notes", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end @@ -105,36 +105,36 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it "returns 404 when not authorized" do parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user, admin_mode: private_user.admin?) expect(response).to have_gitlab_http_status(:not_found) end end - describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id", :aggregate_failures do it "returns a note by id" do - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:ok) expect(json_response['body']).to eq(note.note) end it "returns a 404 error if note not found" do - get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{non_existing_record_id}", user) + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{non_existing_record_id}", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end end - describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes", :aggregate_failures do let(:params) { { body: 'hi!' } } subject do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?), params: params end it "creates a new note" do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!' } + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?), params: { body: 'hi!' } expect(response).to have_gitlab_http_status(:created) expect(json_response['body']).to eq('hi!') @@ -143,7 +143,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| end it "returns a 400 bad request error if body not given" do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user) + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:bad_request) end @@ -158,7 +158,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| uri = "/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes" expect do - post api(uri, user), params: { body: 'hi!' } + post api(uri, user, admin_mode: user.admin?), params: { body: 'hi!' } end.to change { Event.count }.by(1) end @@ -169,7 +169,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| context 'by an admin' do it 'sets the creation time on the new note' do admin = create(:admin) - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", admin), params: params + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:created) expect(json_response['body']).to eq('hi!') @@ -185,7 +185,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| let(:user) { project.first_owner } it 'sets the creation time on the new note' do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?), params: params expect(response).to have_gitlab_http_status(:created) expect(json_response['body']).to eq('hi!') @@ -215,7 +215,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| when 'groups' context 'by a group owner' do it 'sets the creation time on the new note' do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?), params: params expect(response).to have_gitlab_http_status(:created) expect(json_response['body']).to eq('hi!') @@ -253,7 +253,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| context 'when the user is posting an award emoji on their own noteable' do it 'creates a new note' do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: ':+1:' } + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?), params: { body: ':+1:' } expect(response).to have_gitlab_http_status(:created) expect(json_response['body']).to eq(':+1:') @@ -266,7 +266,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| end it 'responds with 404' do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user, admin_mode: private_user.admin?), params: { body: 'Foo' } expect(response).to have_gitlab_http_status(:not_found) @@ -299,11 +299,11 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| end end - describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do + describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id", :aggregate_failures do let(:params) { { body: 'Hello!' } } subject do - put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user), params: params + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user, admin_mode: user.admin?), params: params end context 'when only body param is present' do @@ -329,40 +329,40 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| end it 'returns a 404 error when note id not found' do - put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{non_existing_record_id}", user), - params: { body: 'Hello!' } + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{non_existing_record_id}", user, admin_mode: user.admin?), + params: { body: 'Hello!' } expect(response).to have_gitlab_http_status(:not_found) end it 'returns a 400 bad request error if body is empty' do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "notes/#{note.id}", user), params: { body: '' } + "notes/#{note.id}", user, admin_mode: user.admin?), params: { body: '' } expect(response).to have_gitlab_http_status(:bad_request) end end - describe "DELETE /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do + describe "DELETE /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id", :aggregate_failures do it 'deletes a note' do delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "notes/#{note.id}", user) + "notes/#{note.id}", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:no_content) # Check if note is really deleted delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "notes/#{note.id}", user) + "notes/#{note.id}", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end it 'returns a 404 error when note id not found' do - delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{non_existing_record_id}", user) + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{non_existing_record_id}", user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end it_behaves_like '412 response' do - let(:request) { api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user) } + let(:request) { api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user, admin_mode: user.admin?) } end end end @@ -370,16 +370,16 @@ end RSpec.shared_examples 'noteable API with confidential notes' do |parent_type, noteable_type, id_name| it_behaves_like 'noteable API', parent_type, noteable_type, id_name - describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes", :aggregate_failures do let(:params) { { body: 'hi!' } } subject do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?), params: params end context 'with internal param' do it "creates a confidential note if internal is set to true" do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(internal: true) + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?), params: params.merge(internal: true) expect(response).to have_gitlab_http_status(:created) expect(json_response['body']).to eq('hi!') @@ -391,7 +391,7 @@ RSpec.shared_examples 'noteable API with confidential notes' do |parent_type, no context 'with deprecated confidential param' do it "creates a confidential note if confidential is set to true" do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(confidential: true) + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?), params: params.merge(confidential: true) expect(response).to have_gitlab_http_status(:created) expect(json_response['body']).to eq('hi!') 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 b55639a6b82..f53532d00d7 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 @@ -507,55 +507,118 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project| it_behaves_like 'returning response status', status end - shared_examples 'handling different package names, visibilities and user roles' do - where(:package_name_type, :visibility, :user_role, :expected_result, :expected_status) do - :scoped_naming_convention | :public | :anonymous | :accept | :ok - :scoped_naming_convention | :public | :guest | :accept | :ok - :scoped_naming_convention | :public | :reporter | :accept | :ok - :scoped_no_naming_convention | :public | :anonymous | :accept | :ok - :scoped_no_naming_convention | :public | :guest | :accept | :ok - :scoped_no_naming_convention | :public | :reporter | :accept | :ok - :unscoped | :public | :anonymous | :accept | :ok - :unscoped | :public | :guest | :accept | :ok - :unscoped | :public | :reporter | :accept | :ok - :non_existing | :public | :anonymous | :reject | :not_found - :non_existing | :public | :guest | :reject | :not_found - :non_existing | :public | :reporter | :reject | :not_found - - :scoped_naming_convention | :private | :anonymous | :reject | :not_found - :scoped_naming_convention | :private | :guest | :reject | :forbidden - :scoped_naming_convention | :private | :reporter | :accept | :ok - :scoped_no_naming_convention | :private | :anonymous | :reject | :not_found - :scoped_no_naming_convention | :private | :guest | :reject | :forbidden - :scoped_no_naming_convention | :private | :reporter | :accept | :ok - :unscoped | :private | :anonymous | :reject | :not_found - :unscoped | :private | :guest | :reject | :forbidden - :unscoped | :private | :reporter | :accept | :ok - :non_existing | :private | :anonymous | :reject | :not_found - :non_existing | :private | :guest | :reject | :forbidden - :non_existing | :private | :reporter | :reject | :not_found - - :scoped_naming_convention | :internal | :anonymous | :reject | :not_found - :scoped_naming_convention | :internal | :guest | :accept | :ok - :scoped_naming_convention | :internal | :reporter | :accept | :ok - :scoped_no_naming_convention | :internal | :anonymous | :reject | :not_found - :scoped_no_naming_convention | :internal | :guest | :accept | :ok - :scoped_no_naming_convention | :internal | :reporter | :accept | :ok - :unscoped | :internal | :anonymous | :reject | :not_found - :unscoped | :internal | :guest | :accept | :ok - :unscoped | :internal | :reporter | :accept | :ok - :non_existing | :internal | :anonymous | :reject | :not_found - :non_existing | :internal | :guest | :reject | :not_found - :non_existing | :internal | :reporter | :reject | :not_found + shared_examples 'handling all conditions' do + where(:auth, :package_name_type, :visibility, :user_role, :expected_result, :expected_status) do + nil | :scoped_naming_convention | :public | nil | :accept | :ok + nil | :scoped_no_naming_convention | :public | nil | :accept | :ok + nil | :unscoped | :public | nil | :accept | :ok + nil | :non_existing | :public | nil | :reject | :not_found + nil | :scoped_naming_convention | :private | nil | :reject | :not_found + nil | :scoped_no_naming_convention | :private | nil | :reject | :not_found + nil | :unscoped | :private | nil | :reject | :not_found + nil | :non_existing | :private | nil | :reject | :not_found + nil | :scoped_naming_convention | :internal | nil | :reject | :not_found + nil | :scoped_no_naming_convention | :internal | nil | :reject | :not_found + nil | :unscoped | :internal | nil | :reject | :not_found + nil | :non_existing | :internal | nil | :reject | :not_found + + :oauth | :scoped_naming_convention | :public | :guest | :accept | :ok + :oauth | :scoped_naming_convention | :public | :reporter | :accept | :ok + :oauth | :scoped_no_naming_convention | :public | :guest | :accept | :ok + :oauth | :scoped_no_naming_convention | :public | :reporter | :accept | :ok + :oauth | :unscoped | :public | :guest | :accept | :ok + :oauth | :unscoped | :public | :reporter | :accept | :ok + :oauth | :non_existing | :public | :guest | :reject | :not_found + :oauth | :non_existing | :public | :reporter | :reject | :not_found + :oauth | :scoped_naming_convention | :private | :guest | :reject | :forbidden + :oauth | :scoped_naming_convention | :private | :reporter | :accept | :ok + :oauth | :scoped_no_naming_convention | :private | :guest | :reject | :forbidden + :oauth | :scoped_no_naming_convention | :private | :reporter | :accept | :ok + :oauth | :unscoped | :private | :guest | :reject | :forbidden + :oauth | :unscoped | :private | :reporter | :accept | :ok + :oauth | :non_existing | :private | :guest | :reject | :forbidden + :oauth | :non_existing | :private | :reporter | :reject | :not_found + :oauth | :scoped_naming_convention | :internal | :guest | :accept | :ok + :oauth | :scoped_naming_convention | :internal | :reporter | :accept | :ok + :oauth | :scoped_no_naming_convention | :internal | :guest | :accept | :ok + :oauth | :scoped_no_naming_convention | :internal | :reporter | :accept | :ok + :oauth | :unscoped | :internal | :guest | :accept | :ok + :oauth | :unscoped | :internal | :reporter | :accept | :ok + :oauth | :non_existing | :internal | :guest | :reject | :not_found + :oauth | :non_existing | :internal | :reporter | :reject | :not_found + + :personal_access_token | :scoped_naming_convention | :public | :guest | :accept | :ok + :personal_access_token | :scoped_naming_convention | :public | :reporter | :accept | :ok + :personal_access_token | :scoped_no_naming_convention | :public | :guest | :accept | :ok + :personal_access_token | :scoped_no_naming_convention | :public | :reporter | :accept | :ok + :personal_access_token | :unscoped | :public | :guest | :accept | :ok + :personal_access_token | :unscoped | :public | :reporter | :accept | :ok + :personal_access_token | :non_existing | :public | :guest | :reject | :not_found + :personal_access_token | :non_existing | :public | :reporter | :reject | :not_found + :personal_access_token | :scoped_naming_convention | :private | :guest | :reject | :forbidden + :personal_access_token | :scoped_naming_convention | :private | :reporter | :accept | :ok + :personal_access_token | :scoped_no_naming_convention | :private | :guest | :reject | :forbidden + :personal_access_token | :scoped_no_naming_convention | :private | :reporter | :accept | :ok + :personal_access_token | :unscoped | :private | :guest | :reject | :forbidden + :personal_access_token | :unscoped | :private | :reporter | :accept | :ok + :personal_access_token | :non_existing | :private | :guest | :reject | :forbidden + :personal_access_token | :non_existing | :private | :reporter | :reject | :not_found + :personal_access_token | :scoped_naming_convention | :internal | :guest | :accept | :ok + :personal_access_token | :scoped_naming_convention | :internal | :reporter | :accept | :ok + :personal_access_token | :scoped_no_naming_convention | :internal | :guest | :accept | :ok + :personal_access_token | :scoped_no_naming_convention | :internal | :reporter | :accept | :ok + :personal_access_token | :unscoped | :internal | :guest | :accept | :ok + :personal_access_token | :unscoped | :internal | :reporter | :accept | :ok + :personal_access_token | :non_existing | :internal | :guest | :reject | :not_found + :personal_access_token | :non_existing | :internal | :reporter | :reject | :not_found + + :job_token | :scoped_naming_convention | :public | :developer | :accept | :ok + :job_token | :scoped_no_naming_convention | :public | :developer | :accept | :ok + :job_token | :unscoped | :public | :developer | :accept | :ok + :job_token | :non_existing | :public | :developer | :reject | :not_found + :job_token | :scoped_naming_convention | :private | :developer | :accept | :ok + :job_token | :scoped_no_naming_convention | :private | :developer | :accept | :ok + :job_token | :unscoped | :private | :developer | :accept | :ok + :job_token | :non_existing | :private | :developer | :reject | :not_found + :job_token | :scoped_naming_convention | :internal | :developer | :accept | :ok + :job_token | :scoped_no_naming_convention | :internal | :developer | :accept | :ok + :job_token | :unscoped | :internal | :developer | :accept | :ok + :job_token | :non_existing | :internal | :developer | :reject | :not_found + + :deploy_token | :scoped_naming_convention | :public | nil | :accept | :ok + :deploy_token | :scoped_no_naming_convention | :public | nil | :accept | :ok + :deploy_token | :unscoped | :public | nil | :accept | :ok + :deploy_token | :non_existing | :public | nil | :reject | :not_found + :deploy_token | :scoped_naming_convention | :private | nil | :accept | :ok + :deploy_token | :scoped_no_naming_convention | :private | nil | :accept | :ok + :deploy_token | :unscoped | :private | nil | :accept | :ok + :deploy_token | :non_existing | :private | nil | :reject | :not_found + :deploy_token | :scoped_naming_convention | :internal | nil | :accept | :ok + :deploy_token | :scoped_no_naming_convention | :internal | nil | :accept | :ok + :deploy_token | :unscoped | :internal | nil | :accept | :ok + :deploy_token | :non_existing | :internal | nil | :reject | :not_found end with_them do - let(:anonymous) { user_role == :anonymous } + 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 - subject { get(url, headers: anonymous ? {} : headers) } + subject { get(url, headers: headers) } before do - project.send("add_#{user_role}", user) unless anonymous + project.send("add_#{user_role}", user) if user_role project.update!(visibility: visibility.to_s) end @@ -571,20 +634,6 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project| end end - shared_examples 'handling all conditions' do - context 'with oauth token' do - let(:headers) { build_token_auth_header(token.plaintext_token) } - - it_behaves_like 'handling different package names, visibilities and user roles' - end - - context 'with personal access token' do - let(:headers) { build_token_auth_header(personal_access_token.token) } - - it_behaves_like 'handling different package names, visibilities and user roles' - end - end - context 'with a group namespace' do it_behaves_like 'handling all conditions' end @@ -599,7 +648,6 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project| end RSpec.shared_examples 'handling create dist tag requests' do |scope: :project| - using RSpec::Parameterized::TableSyntax include_context 'set package name from package name type' let_it_be(:tag_name) { 'test' } @@ -617,82 +665,10 @@ RSpec.shared_examples 'handling create dist tag requests' do |scope: :project| it_behaves_like 'returning response status', status end - shared_examples 'handling different package names, visibilities and user roles' do - where(:package_name_type, :visibility, :user_role, :expected_result, :expected_status) do - :scoped_naming_convention | :public | :anonymous | :reject | :forbidden - :scoped_naming_convention | :public | :guest | :reject | :forbidden - :scoped_naming_convention | :public | :developer | :accept | :ok - :scoped_no_naming_convention | :public | :anonymous | :reject | :forbidden - :scoped_no_naming_convention | :public | :guest | :reject | :forbidden - :scoped_no_naming_convention | :public | :developer | :accept | :ok - :unscoped | :public | :anonymous | :reject | :forbidden - :unscoped | :public | :guest | :reject | :forbidden - :unscoped | :public | :developer | :accept | :ok - :non_existing | :public | :anonymous | :reject | :forbidden - :non_existing | :public | :guest | :reject | :forbidden - :non_existing | :public | :developer | :reject | :not_found - - :scoped_naming_convention | :private | :anonymous | :reject | :not_found - :scoped_naming_convention | :private | :guest | :reject | :forbidden - :scoped_naming_convention | :private | :developer | :accept | :ok - :scoped_no_naming_convention | :private | :anonymous | :reject | :not_found - :scoped_no_naming_convention | :private | :guest | :reject | :forbidden - :scoped_no_naming_convention | :private | :developer | :accept | :ok - :unscoped | :private | :anonymous | :reject | :not_found - :unscoped | :private | :guest | :reject | :forbidden - :unscoped | :private | :developer | :accept | :ok - :non_existing | :private | :anonymous | :reject | :not_found - :non_existing | :private | :guest | :reject | :forbidden - :non_existing | :private | :developer | :reject | :not_found - - :scoped_naming_convention | :internal | :anonymous | :reject | :forbidden - :scoped_naming_convention | :internal | :guest | :reject | :forbidden - :scoped_naming_convention | :internal | :developer | :accept | :ok - :scoped_no_naming_convention | :internal | :anonymous | :reject | :forbidden - :scoped_no_naming_convention | :internal | :guest | :reject | :forbidden - :scoped_no_naming_convention | :internal | :developer | :accept | :ok - :unscoped | :internal | :anonymous | :reject | :forbidden - :unscoped | :internal | :guest | :reject | :forbidden - :unscoped | :internal | :developer | :accept | :ok - :non_existing | :internal | :anonymous | :reject | :forbidden - :non_existing | :internal | :guest | :reject | :forbidden - :non_existing | :internal | :developer | :reject | :not_found - end - - with_them do - let(:anonymous) { user_role == :anonymous } - - subject { put(url, env: env, headers: headers) } - - before do - project.send("add_#{user_role}", user) unless anonymous - project.update!(visibility: visibility.to_s) - end - - example_name = "#{params[:expected_result]} create package tag request" - status = params[:expected_status] - - if scope == :instance && params[:package_name_type] != :scoped_naming_convention - example_name = 'reject create package tag request' - status = :not_found - end - - it_behaves_like example_name, status: status - end - end - shared_examples 'handling all conditions' do - context 'with oauth token' do - let(:headers) { build_token_auth_header(token.plaintext_token) } + subject { put(url, env: env, headers: headers) } - it_behaves_like 'handling different package names, visibilities and user roles' - end - - context 'with personal access token' do - let(:headers) { build_token_auth_header(personal_access_token.token) } - - it_behaves_like 'handling different package names, visibilities and user roles' - end + it_behaves_like 'handling different package names, visibilities and user roles for tags create or delete', action: :create, scope: scope end context 'with a group namespace' do @@ -709,7 +685,6 @@ RSpec.shared_examples 'handling create dist tag requests' do |scope: :project| end RSpec.shared_examples 'handling delete dist tag requests' do |scope: :project| - using RSpec::Parameterized::TableSyntax include_context 'set package name from package name type' let_it_be(:package_tag) { create(:packages_tag, package: package) } @@ -725,82 +700,10 @@ RSpec.shared_examples 'handling delete dist tag requests' do |scope: :project| it_behaves_like 'returning response status', status end - shared_examples 'handling different package names, visibilities and user roles' do - where(:package_name_type, :visibility, :user_role, :expected_result, :expected_status) do - :scoped_naming_convention | :public | :anonymous | :reject | :forbidden - :scoped_naming_convention | :public | :guest | :reject | :forbidden - :scoped_naming_convention | :public | :maintainer | :accept | :ok - :scoped_no_naming_convention | :public | :anonymous | :reject | :forbidden - :scoped_no_naming_convention | :public | :guest | :reject | :forbidden - :scoped_no_naming_convention | :public | :maintainer | :accept | :ok - :unscoped | :public | :anonymous | :reject | :forbidden - :unscoped | :public | :guest | :reject | :forbidden - :unscoped | :public | :maintainer | :accept | :ok - :non_existing | :public | :anonymous | :reject | :forbidden - :non_existing | :public | :guest | :reject | :forbidden - :non_existing | :public | :maintainer | :reject | :not_found - - :scoped_naming_convention | :private | :anonymous | :reject | :not_found - :scoped_naming_convention | :private | :guest | :reject | :forbidden - :scoped_naming_convention | :private | :maintainer | :accept | :ok - :scoped_no_naming_convention | :private | :anonymous | :reject | :not_found - :scoped_no_naming_convention | :private | :guest | :reject | :forbidden - :scoped_no_naming_convention | :private | :maintainer | :accept | :ok - :unscoped | :private | :anonymous | :reject | :not_found - :unscoped | :private | :guest | :reject | :forbidden - :unscoped | :private | :maintainer | :accept | :ok - :non_existing | :private | :anonymous | :reject | :not_found - :non_existing | :private | :guest | :reject | :forbidden - :non_existing | :private | :maintainer | :reject | :not_found - - :scoped_naming_convention | :internal | :anonymous | :reject | :forbidden - :scoped_naming_convention | :internal | :guest | :reject | :forbidden - :scoped_naming_convention | :internal | :maintainer | :accept | :ok - :scoped_no_naming_convention | :internal | :anonymous | :reject | :forbidden - :scoped_no_naming_convention | :internal | :guest | :reject | :forbidden - :scoped_no_naming_convention | :internal | :maintainer | :accept | :ok - :unscoped | :internal | :anonymous | :reject | :forbidden - :unscoped | :internal | :guest | :reject | :forbidden - :unscoped | :internal | :maintainer | :accept | :ok - :non_existing | :internal | :anonymous | :reject | :forbidden - :non_existing | :internal | :guest | :reject | :forbidden - :non_existing | :internal | :maintainer | :reject | :not_found - end - - with_them do - let(:anonymous) { user_role == :anonymous } - - subject { delete(url, headers: headers) } - - before do - project.send("add_#{user_role}", user) unless anonymous - project.update!(visibility: visibility.to_s) - end - - example_name = "#{params[:expected_result]} delete package tag request" - status = params[:expected_status] - - if scope == :instance && params[:package_name_type] != :scoped_naming_convention - example_name = 'reject delete package tag request' - status = :not_found - end - - it_behaves_like example_name, status: status - end - end - shared_examples 'handling all conditions' do - context 'with oauth token' do - let(:headers) { build_token_auth_header(token.plaintext_token) } - - it_behaves_like 'handling different package names, visibilities and user roles' - end - - context 'with personal access token' do - let(:headers) { build_token_auth_header(personal_access_token.token) } + subject { delete(url, headers: headers) } - it_behaves_like 'handling different package names, visibilities and user roles' - end + it_behaves_like 'handling different package names, visibilities and user roles for tags create or delete', action: :delete, scope: scope end context 'with a group namespace' do @@ -815,3 +718,134 @@ RSpec.shared_examples 'handling delete dist tag requests' do |scope: :project| end end end + +RSpec.shared_examples 'handling different package names, visibilities and user roles for tags create or delete' do |action:, scope: :project| + using RSpec::Parameterized::TableSyntax + + role = action == :create ? :developer : :maintainer + + where(:auth, :package_name_type, :visibility, :user_role, :expected_result, :expected_status) do + nil | :scoped_naming_convention | :public | nil | :reject | :unauthorized + nil | :scoped_no_naming_convention | :public | nil | :reject | :unauthorized + nil | :unscoped | :public | nil | :reject | :unauthorized + nil | :non_existing | :public | nil | :reject | :unauthorized + nil | :scoped_naming_convention | :private | nil | :reject | :unauthorized + nil | :scoped_no_naming_convention | :private | nil | :reject | :unauthorized + nil | :unscoped | :private | nil | :reject | :unauthorized + nil | :non_existing | :private | nil | :reject | :unauthorized + nil | :scoped_naming_convention | :internal | nil | :reject | :unauthorized + nil | :scoped_no_naming_convention | :internal | nil | :reject | :unauthorized + nil | :unscoped | :internal | nil | :reject | :unauthorized + nil | :non_existing | :internal | nil | :reject | :unauthorized + + :oauth | :scoped_naming_convention | :public | :guest | :reject | :forbidden + :oauth | :scoped_naming_convention | :public | role | :accept | :ok + :oauth | :scoped_no_naming_convention | :public | :guest | :reject | :forbidden + :oauth | :scoped_no_naming_convention | :public | role | :accept | :ok + :oauth | :unscoped | :public | :guest | :reject | :forbidden + :oauth | :unscoped | :public | role | :accept | :ok + :oauth | :non_existing | :public | :guest | :reject | :forbidden + :oauth | :non_existing | :public | role | :reject | :not_found + :oauth | :scoped_naming_convention | :private | :guest | :reject | :forbidden + :oauth | :scoped_naming_convention | :private | role | :accept | :ok + :oauth | :scoped_no_naming_convention | :private | :guest | :reject | :forbidden + :oauth | :scoped_no_naming_convention | :private | role | :accept | :ok + :oauth | :unscoped | :private | :guest | :reject | :forbidden + :oauth | :unscoped | :private | role | :accept | :ok + :oauth | :non_existing | :private | :guest | :reject | :forbidden + :oauth | :non_existing | :private | role | :reject | :not_found + :oauth | :scoped_naming_convention | :internal | :guest | :reject | :forbidden + :oauth | :scoped_naming_convention | :internal | role | :accept | :ok + :oauth | :scoped_no_naming_convention | :internal | :guest | :reject | :forbidden + :oauth | :scoped_no_naming_convention | :internal | role | :accept | :ok + :oauth | :unscoped | :internal | :guest | :reject | :forbidden + :oauth | :unscoped | :internal | role | :accept | :ok + :oauth | :non_existing | :internal | :guest | :reject | :forbidden + :oauth | :non_existing | :internal | role | :reject | :not_found + + :personal_access_token | :scoped_naming_convention | :public | :guest | :reject | :forbidden + :personal_access_token | :scoped_naming_convention | :public | role | :accept | :ok + :personal_access_token | :scoped_no_naming_convention | :public | :guest | :reject | :forbidden + :personal_access_token | :scoped_no_naming_convention | :public | role | :accept | :ok + :personal_access_token | :unscoped | :public | :guest | :reject | :forbidden + :personal_access_token | :unscoped | :public | role | :accept | :ok + :personal_access_token | :non_existing | :public | :guest | :reject | :forbidden + :personal_access_token | :non_existing | :public | role | :reject | :not_found + :personal_access_token | :scoped_naming_convention | :private | :guest | :reject | :forbidden + :personal_access_token | :scoped_naming_convention | :private | role | :accept | :ok + :personal_access_token | :scoped_no_naming_convention | :private | :guest | :reject | :forbidden + :personal_access_token | :scoped_no_naming_convention | :private | role | :accept | :ok + :personal_access_token | :unscoped | :private | :guest | :reject | :forbidden + :personal_access_token | :unscoped | :private | role | :accept | :ok + :personal_access_token | :non_existing | :private | :guest | :reject | :forbidden + :personal_access_token | :non_existing | :private | role | :reject | :not_found + :personal_access_token | :scoped_naming_convention | :internal | :guest | :reject | :forbidden + :personal_access_token | :scoped_naming_convention | :internal | role | :accept | :ok + :personal_access_token | :scoped_no_naming_convention | :internal | :guest | :reject | :forbidden + :personal_access_token | :scoped_no_naming_convention | :internal | role | :accept | :ok + :personal_access_token | :unscoped | :internal | :guest | :reject | :forbidden + :personal_access_token | :unscoped | :internal | role | :accept | :ok + :personal_access_token | :non_existing | :internal | :guest | :reject | :forbidden + :personal_access_token | :non_existing | :internal | role | :reject | :not_found + + :job_token | :scoped_naming_convention | :public | role | :accept | :ok + :job_token | :scoped_no_naming_convention | :public | role | :accept | :ok + :job_token | :unscoped | :public | role | :accept | :ok + :job_token | :non_existing | :public | role | :reject | :not_found + :job_token | :scoped_naming_convention | :private | role | :accept | :ok + :job_token | :scoped_no_naming_convention | :private | role | :accept | :ok + :job_token | :unscoped | :private | role | :accept | :ok + :job_token | :non_existing | :private | role | :reject | :not_found + :job_token | :scoped_naming_convention | :internal | role | :accept | :ok + :job_token | :scoped_no_naming_convention | :internal | role | :accept | :ok + :job_token | :unscoped | :internal | role | :accept | :ok + :job_token | :non_existing | :internal | role | :reject | :not_found + + :deploy_token | :scoped_naming_convention | :public | nil | :accept | :ok + :deploy_token | :scoped_no_naming_convention | :public | nil | :accept | :ok + :deploy_token | :unscoped | :public | nil | :accept | :ok + :deploy_token | :non_existing | :public | nil | :reject | :not_found + :deploy_token | :scoped_naming_convention | :private | nil | :accept | :ok + :deploy_token | :scoped_no_naming_convention | :private | nil | :accept | :ok + :deploy_token | :unscoped | :private | nil | :accept | :ok + :deploy_token | :non_existing | :private | nil | :reject | :not_found + :deploy_token | :scoped_naming_convention | :internal | nil | :accept | :ok + :deploy_token | :scoped_no_naming_convention | :internal | nil | :accept | :ok + :deploy_token | :unscoped | :internal | nil | :accept | :ok + :deploy_token | :non_existing | :internal | nil | :reject | :not_found + 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) + end + + example_name = "#{params[:expected_result]} #{action} package tag request" + status = params[:expected_status] + + if scope == :instance && params[:package_name_type] != :scoped_naming_convention + example_name = "reject #{action} package tag request" + # Due to #authenticate_non_get, anonymous requests on private resources + # are rejected with unauthorized status + status = params[:auth].nil? ? :unauthorized : :not_found + end + + it_behaves_like example_name, status: status + end +end diff --git a/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb index 1d79a61fbb0..7c20ea661b5 100644 --- a/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb @@ -1,13 +1,5 @@ # frozen_string_literal: true -RSpec.shared_examples 'rejects package tags access' do |status:| - before do - package.update!(name: package_name) unless package_name == 'non-existing-package' - end - - it_behaves_like 'returning response status', status -end - RSpec.shared_examples 'accept package tags request' do |status:| using RSpec::Parameterized::TableSyntax include_context 'dependency proxy helpers context' @@ -23,6 +15,7 @@ RSpec.shared_examples 'accept package tags request' do |status:| end it_behaves_like 'returning response status', status + it_behaves_like 'track event', :list_tags it 'returns a valid json response' do subject @@ -63,6 +56,7 @@ RSpec.shared_examples 'accept create package tag request' do |user_type| end it_behaves_like 'returning response status', :no_content + it_behaves_like 'track event', :create_tag it 'creates the package tag' do expect { subject }.to change { Packages::Tag.count }.by(1) @@ -145,6 +139,7 @@ RSpec.shared_examples 'accept delete package tag request' do |user_type| end it_behaves_like 'returning response status', :no_content + it_behaves_like 'track event', :delete_tag it 'returns a valid response' do subject @@ -190,3 +185,21 @@ RSpec.shared_examples 'accept delete package tag request' do |user_type| end end end + +RSpec.shared_examples 'track event' do |event_name| + let(:event_user) do + if auth == :deploy_token + deploy_token + elsif user_role + user + end + end + + let(:snowplow_gitlab_standard_context) do + { project: project, namespace: project.namespace, property: 'i_package_npm_user' }.tap do |context| + context[:user] = event_user if event_user + end + end + + it_behaves_like 'a package tracking event', described_class.name, event_name.to_s +end diff --git a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb index 17d8b9c7fab..7cafe8bb368 100644 --- a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb @@ -36,6 +36,7 @@ RSpec.shared_examples 'handling nuget service requests' do |example_names_with_s with_them do let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } subject { get api(url), headers: headers } @@ -72,6 +73,7 @@ RSpec.shared_examples 'handling nuget service requests' do |example_names_with_s with_them do let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') } let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) } + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } subject { get api(url), headers: headers } @@ -140,6 +142,7 @@ RSpec.shared_examples 'handling nuget metadata requests with package name' do |e with_them do let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } subject { get api(url), headers: headers } @@ -207,6 +210,7 @@ RSpec.shared_examples 'handling nuget metadata requests with package name and pa with_them do let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } subject { get api(url), headers: headers } @@ -277,6 +281,7 @@ RSpec.shared_examples 'handling nuget search requests' do |example_names_with_st with_them do let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } subject { get api(url), headers: headers } 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 98264baa61d..3168f25e4fa 100644 --- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb @@ -143,37 +143,37 @@ RSpec.shared_examples 'job token for package uploads' do |authorize_endpoint: fa end 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] + [ + 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) + subject 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) + 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 -RSpec.shared_examples 'not a package tracking event' do - before do - stub_feature_flags(collect_package_events: true) - end - +RSpec.shared_examples 'not a package tracking event' do |category, action| it 'does not create a gitlab tracking event', :snowplow, :aggregate_failures do - expect { subject }.not_to change { Packages::Event.count } + subject - expect_no_snowplow_event + expect_no_snowplow_event category: category, action: action end end @@ -183,3 +183,15 @@ RSpec.shared_examples 'bumping the package last downloaded at field' do .to change { package.reload.last_downloaded_at }.from(nil).to(instance_of(ActiveSupport::TimeWithZone)) end end + +RSpec.shared_examples 'a successful package creation' do + it 'creates npm package with file' do + expect { subject } + .to change { project.packages.count }.by(1) + .and change { Packages::PackageFile.count }.by(1) + .and change { Packages::Tag.count }.by(1) + .and change { Packages::Npm::Metadatum.count }.by(1) + + expect(response).to have_gitlab_http_status(:ok) + end +end diff --git a/spec/support/shared_examples/requests/api/pipelines/visibility_table_shared_examples.rb b/spec/support/shared_examples/requests/api/pipelines/visibility_table_shared_examples.rb index 8dd2ef6ccc6..9847ea4e1e2 100644 --- a/spec/support/shared_examples/requests/api/pipelines/visibility_table_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/pipelines/visibility_table_shared_examples.rb @@ -224,10 +224,10 @@ RSpec.shared_examples 'pipelines visibility table' do project.project_feature.update!(project_feature_attributes) project.add_role(ci_user, user_role) if user_role && user_role != :non_member - get api(pipelines_api_path, api_user) + get api(pipelines_api_path, api_user, admin_mode: is_admin) end - it do + specify do expect(response).to have_gitlab_http_status(response_status) expect(api_response).to match(expected_response) 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 6065b1163c4..9bd430c3b4f 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 @@ -254,6 +254,13 @@ RSpec.shared_examples 'pypi simple API endpoint' do with_them do let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + let(:snowplow_gitlab_standard_context) do + if user_role == :anonymous || (visibility_level == :public && !user_token) + snowplow_context + else + snowplow_context.merge(user: user) + end + end before do project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) @@ -269,7 +276,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: group, property: 'i_package_pypi_user' } } + let(:snowplow_gitlab_standard_context) { snowplow_context.merge({ project: project, user: 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 2154a76d765..3913d29e086 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 @@ -9,7 +9,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| let(:repository_storage_move_id) { storage_move.id } def get_container_repository_storage_move - get api(url, user) + get api(url, user, admin_mode: user.admin?) end it 'returns a container repository storage move', :aggregate_failures do @@ -39,7 +39,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| shared_examples 'get container repository storage move list' do def get_container_repository_storage_moves - get api(url, user) + get api(url, user, admin_mode: user.admin?) end it 'returns container repository storage moves', :aggregate_failures do @@ -70,7 +70,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'] } + json_ids = json_response.pluck('id') expect(json_ids).to eq([storage_move.id, storage_move_middle.id, storage_move_oldest.id]) end @@ -90,7 +90,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| let(:container_id) { non_existing_record_id } it 'returns not found' do - get api(url, user) + get api(url, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end @@ -108,7 +108,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| let(:repository_storage_move_id) { storage_move.id } it 'returns not found' do - get api(url, user) + get api(url, user, admin_mode: user.admin?) expect(response).to have_gitlab_http_status(:not_found) end @@ -127,20 +127,20 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| end end - describe "POST /#{container_type}/:id/repository_storage_moves" do + describe "POST /#{container_type}/:id/repository_storage_moves", :aggregate_failures do let(:container_id) { container.id } let(:url) { "/#{container_type}/#{container_id}/repository_storage_moves" } let(:destination_storage_name) { 'test_second_storage' } def create_container_repository_storage_move - post api(url, user), params: { destination_storage_name: destination_storage_name } + post api(url, user, admin_mode: user.admin?), params: { destination_storage_name: destination_storage_name } end before do stub_storage_settings('test_second_storage' => { 'path' => 'tmp/tests/extra_storage' }) end - it 'schedules a container repository storage move', :aggregate_failures do + it 'schedules a container repository storage move' do create_container_repository_storage_move storage_move = container.repository_storage_moves.last @@ -158,7 +158,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| it { expect { create_container_repository_storage_move }.to be_denied_for(:user) } end - context 'destination_storage_name is missing', :aggregate_failures do + context 'destination_storage_name is missing' do let(:destination_storage_name) { nil } it 'schedules a container repository storage move' do @@ -192,7 +192,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| let(:destination_storage_name) { 'test_second_storage' } def create_container_repository_storage_moves - post api(url, user), params: { + post api(url, user, admin_mode: user.admin?), params: { source_storage_name: source_storage_name, destination_storage_name: destination_storage_name } diff --git a/spec/support/shared_examples/requests/api/resolvable_discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/resolvable_discussions_shared_examples.rb index b5139bd8c99..2770e293683 100644 --- a/spec/support/shared_examples/requests/api/resolvable_discussions_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/resolvable_discussions_shared_examples.rb @@ -71,8 +71,7 @@ RSpec.shared_examples 'resolvable discussions API' do |parent_type, noteable_typ it 'returns a 404 error when note id not found' do put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ - "discussions/#{note.discussion_id}/notes/#{non_existing_record_id}", user), - params: { body: 'Hello!' } + "discussions/#{note.discussion_id}/notes/#{non_existing_record_id}", user), params: { body: 'Hello!' } expect(response).to have_gitlab_http_status(:not_found) end diff --git a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb index 1b92eb56f54..56f2394c005 100644 --- a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb @@ -1,12 +1,19 @@ # frozen_string_literal: true RSpec.shared_examples 'raw snippet files' do - let_it_be(:user_token) { create(:personal_access_token, user: snippet.author) } let(:snippet_id) { snippet.id } - let(:user) { snippet.author } + let_it_be(:user) { snippet.author } let(:file_path) { '%2Egitattributes' } let(:ref) { 'master' } + let_it_be(:user_token) do + if user.admin? + create(:personal_access_token, :admin_mode, user: user) + else + create(:personal_access_token, user: user) + end + end + subject { get api(api_path, personal_access_token: user_token) } context 'with an invalid snippet ID' do @@ -15,8 +22,10 @@ RSpec.shared_examples 'raw snippet files' do it 'returns 404' do subject - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response['message']).to eq('404 Snippet Not Found') + aggregate_failures do + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 Snippet Not Found') + end end end @@ -185,7 +194,7 @@ RSpec.shared_examples 'snippet individual non-file updates' do end RSpec.shared_examples 'invalid snippet updates' do - it 'returns 404 for invalid snippet id' do + it 'returns 404 for invalid snippet id', :aggregate_failures do update_snippet(snippet_id: non_existing_record_id, params: { title: 'foo' }) expect(response).to have_gitlab_http_status(:not_found) @@ -204,7 +213,7 @@ RSpec.shared_examples 'invalid snippet updates' do expect(response).to have_gitlab_http_status(:bad_request) end - it 'returns 400 if title is blank' do + it 'returns 400 if title is blank', :aggregate_failures do update_snippet(params: { title: '' }) expect(response).to have_gitlab_http_status(:bad_request) @@ -236,7 +245,9 @@ RSpec.shared_examples 'snippet access with different users' do it 'returns the correct response' do request_user = user_for(requester) - get api(path, request_user) + admin_mode = requester == :admin + + get api(path, request_user, admin_mode: admin_mode) expect(response).to have_gitlab_http_status(status) end @@ -250,8 +261,6 @@ RSpec.shared_examples 'snippet access with different users' do other_user when :admin admin - else - nil end end diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb index 40843ccbd15..ff3947c0e73 100644 --- a/spec/support/shared_examples/requests/api/status_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb @@ -21,6 +21,23 @@ RSpec.shared_examples '400 response' do end end +RSpec.shared_examples '401 response' do + let(:message) { nil } + + before do + # Fires the request + request + end + + it 'returns 401' do + expect(response).to have_gitlab_http_status(:unauthorized) + + if message.present? + expect(json_response['message']).to eq(message) + end + end +end + RSpec.shared_examples '403 response' do before do # Fires the request @@ -54,7 +71,7 @@ RSpec.shared_examples '412 response' do let(:params) { nil } let(:success_status) { 204 } - context 'for a modified ressource' do + context 'for a modified resource' do before do delete request, params: params, headers: { 'HTTP_IF_UNMODIFIED_SINCE' => '1990-01-12T00:00:48-0600' } end @@ -65,7 +82,7 @@ RSpec.shared_examples '412 response' do end end - context 'for an unmodified ressource' do + context 'for an unmodified resource' do before do delete request, params: params, headers: { 'HTTP_IF_UNMODIFIED_SINCE' => Time.now } end diff --git a/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb b/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb index 86a1fd76d09..398421c7a79 100644 --- a/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb @@ -173,8 +173,7 @@ RSpec.shared_examples 'time tracking endpoints' do |issuable_name| describe "GET /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_stats" do it "returns the time stats for #{issuable_name}" do - issuable.update!(spend_time: { duration: 1800, user_id: user.id }, - time_estimate: 3600) + issuable.update!(spend_time: { duration: 1800, user_id: user.id }, time_estimate: 3600) get api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_stats", user) diff --git a/spec/support/shared_examples/requests/applications_controller_shared_examples.rb b/spec/support/shared_examples/requests/applications_controller_shared_examples.rb index 642930dd982..4a7a7492398 100644 --- a/spec/support/shared_examples/requests/applications_controller_shared_examples.rb +++ b/spec/support/shared_examples/requests/applications_controller_shared_examples.rb @@ -7,40 +7,14 @@ RSpec.shared_examples 'applications controller - GET #show' do expect(response).to render_template :show end - - context 'when application is viewed after being created' do - before do - create_application - stub_feature_flags(hash_oauth_secrets: false) - end - - it 'sets `@created` instance variable to `true`' do - get show_path - - expect(assigns[:created]).to eq(true) - end - end - - context 'when application is reviewed' do - before do - stub_feature_flags(hash_oauth_secrets: false) - end - - it 'sets `@created` instance variable to `false`' do - get show_path - - expect(assigns[:created]).to eq(false) - end - end end end RSpec.shared_examples 'applications controller - POST #create' do - it "sets `#{OauthApplications::CREATED_SESSION_KEY}` session key to `true`" do - stub_feature_flags(hash_oauth_secrets: false) + it "sets `@created` instance variable to `true`" do create_application - expect(session[OauthApplications::CREATED_SESSION_KEY]).to eq(true) + expect(assigns[:created]).to eq(true) end end diff --git a/spec/support/shared_examples/requests/graphql_shared_examples.rb b/spec/support/shared_examples/requests/graphql_shared_examples.rb index d133c5ea641..2c08f946468 100644 --- a/spec/support/shared_examples/requests/graphql_shared_examples.rb +++ b/spec/support/shared_examples/requests/graphql_shared_examples.rb @@ -58,5 +58,5 @@ end RSpec.shared_examples 'a mutation on an unauthorized resource' do it_behaves_like 'a mutation that returns top-level errors', - errors: [::Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + errors: [::Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] end diff --git a/spec/support/shared_examples/requests/projects/aws/aws__ff_examples.rb b/spec/support/shared_examples/requests/projects/aws/aws__ff_examples.rb new file mode 100644 index 00000000000..2221baf5b90 --- /dev/null +++ b/spec/support/shared_examples/requests/projects/aws/aws__ff_examples.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'requires feature flag `cloudseed_aws` enabled' do + context 'when feature flag is disabled' do + before do + project.add_maintainer(user) + stub_feature_flags(cloudseed_aws: false) + end + + it 'renders not found' do + sign_in(user) + + subject + + expect(response).to have_gitlab_http_status(:not_found) + 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 3f457890f35..dafa324b3c6 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -567,8 +567,8 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do it 'does not throttle the requests' do (1 + requests_per_period).times do post registry_endpoint, - params: { events: events }.to_json, - headers: registry_headers.merge('Authorization' => secret_token) + params: { events: events }.to_json, + headers: registry_headers.merge('Authorization' => secret_token) expect(response).to have_gitlab_http_status(:ok) end diff --git a/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb b/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb deleted file mode 100644 index f8a752a5673..00000000000 --- a/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb +++ /dev/null @@ -1,130 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'not accessible to non-admin users' do - context 'with unauthenticated user' do - it 'redirects to signin page' do - subject - - expect(response).to redirect_to(new_user_session_path) - end - end - - context 'with authenticated non-admin user' do - before do - login_as(create(:user)) - end - - it 'returns status not_found' do - subject - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - context 'with authenticated admin user without admin mode' do - before do - login_as(create(:admin)) - end - - it 'redirects to enable admin mode' do - subject - - expect(response).to redirect_to(new_admin_session_path) - end - end -end - -# Requires subject and worker_class and status_api to be defined -# let(:worker_class) { SelfMonitoringProjectCreateWorker } -# let(:status_api) { status_create_self_monitoring_project_admin_application_settings_path } -# subject { post create_self_monitoring_project_admin_application_settings_path } -RSpec.shared_examples 'triggers async worker, returns sidekiq job_id with response accepted' do - before do - allow(worker_class).to receive(:with_status).and_return(worker_class) - end - - it 'returns sidekiq job_id of expected length' do - subject - - job_id = json_response['job_id'] - - aggregate_failures do - expect(job_id).to be_present - expect(job_id.length).to be <= Admin::ApplicationSettingsController::PARAM_JOB_ID_MAX_SIZE - end - end - - it 'triggers async worker' do - expect(worker_class).to receive(:perform_async) - - subject - end - - it 'returns accepted response' do - subject - - aggregate_failures do - expect(response).to have_gitlab_http_status(:accepted) - expect(json_response.keys).to contain_exactly('job_id', 'monitor_status') - expect(json_response).to include( - 'monitor_status' => status_api - ) - end - end - - it 'returns job_id' do - fake_job_id = 'b5b28910d97563e58c2fe55f' - allow(worker_class).to receive(:perform_async).and_return(fake_job_id) - - subject - - expect(json_response).to include('job_id' => fake_job_id) - end -end - -# Requires job_id and subject to be defined -# let(:job_id) { 'job_id' } -# subject do -# get status_create_self_monitoring_project_admin_application_settings_path, -# params: { job_id: job_id } -# end -RSpec.shared_examples 'handles invalid job_id' do - context 'with invalid job_id' do - let(:job_id) { 'a' * 51 } - - it 'returns bad_request if job_id too long' do - subject - - aggregate_failures do - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response).to eq('message' => 'Parameter "job_id" cannot ' \ - "exceed length of #{Admin::ApplicationSettingsController::PARAM_JOB_ID_MAX_SIZE}") - end - end - end -end - -# Requires in_progress_message and subject to be defined -# let(:in_progress_message) { 'Job to create self-monitoring project is in progress' } -# subject do -# get status_create_self_monitoring_project_admin_application_settings_path, -# params: { job_id: job_id } -# end -RSpec.shared_examples 'sets polling header and returns accepted' do - it 'sets polling header' do - expect(::Gitlab::PollingInterval).to receive(:set_header) - - subject - end - - it 'returns accepted' do - subject - - aggregate_failures do - expect(response).to have_gitlab_http_status(:accepted) - expect(json_response).to eq( - 'message' => in_progress_message - ) - end - end -end diff --git a/spec/support/shared_examples/requests/user_activity_shared_examples.rb b/spec/support/shared_examples/requests/user_activity_shared_examples.rb index 37da1ce5c63..9c0165f7150 100644 --- a/spec/support/shared_examples/requests/user_activity_shared_examples.rb +++ b/spec/support/shared_examples/requests/user_activity_shared_examples.rb @@ -5,7 +5,7 @@ RSpec.shared_examples 'updating of user activity' do |paths_to_visit| before do group = create(:group, name: 'group') - project = create(:project, :public, namespace: group, name: 'project') + project = create(:project, :public, namespace: group, path: 'project') create(:issue, project: project, iid: 10) create(:merge_request, source_project: project, iid: 15) diff --git a/spec/support/shared_examples/security_training_providers_importer.rb b/spec/support/shared_examples/security_training_providers_importer.rb index 69d92964270..81b3d22ab23 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(security_training_providers.all.map(&:name)).to match_array(['Kontra', 'Secure Code Warrior']) + expect { 3.times { subject } }.to change { security_training_providers.count }.from(0).to(3) + expect(security_training_providers.all.map(&:name)).to match_array(['Kontra', 'Secure Code Warrior', 'SecureFlag']) end end diff --git a/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb b/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb index 32adf98969c..df01f9a5b0b 100644 --- a/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb +++ b/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb @@ -2,13 +2,15 @@ RSpec.shared_examples 'diff file base entity' do it 'exposes essential attributes' do - expect(subject).to include(:content_sha, :submodule, :submodule_link, - :submodule_tree_url, :old_path_html, - :new_path_html, :blob, :can_modify_blob, - :file_hash, :file_path, :old_path, :new_path, - :viewer, :diff_refs, :stored_externally, - :external_storage, :renamed_file, :deleted_file, - :a_mode, :b_mode, :new_file, :file_identifier_hash) + expect(subject).to include( + :content_sha, :submodule, :submodule_link, + :submodule_tree_url, :old_path_html, + :new_path_html, :blob, :can_modify_blob, + :file_hash, :file_path, :old_path, :new_path, + :viewer, :diff_refs, :stored_externally, + :external_storage, :renamed_file, :deleted_file, + :a_mode, :b_mode, :new_file, :file_identifier_hash + ) end # Converted diff files from GitHub import does not contain blob file @@ -30,13 +32,70 @@ RSpec.shared_examples 'diff file entity' do it_behaves_like 'diff file base entity' it 'exposes correct attributes' do - expect(subject).to include(:added_lines, :removed_lines, - :context_lines_path) + expect(subject).to include(:added_lines, :removed_lines, :context_lines_path) end - it 'includes viewer' do - expect(subject[:viewer].with_indifferent_access) + context 'when a viewer' do + let(:collapsed) { false } + let(:added_lines) { 1 } + let(:removed_lines) { 0 } + let(:highlighted_lines) { nil } + + before do + allow(diff_file).to receive(:diff_lines_for_serializer) + .and_return(highlighted_lines) + + allow(diff_file).to receive(:added_lines) + .and_return(added_lines) + + allow(diff_file).to receive(:removed_lines) + .and_return(removed_lines) + + allow(diff_file).to receive(:collapsed?) + .and_return(collapsed) + end + + it 'matches the schema' do + expect(subject[:viewer].with_indifferent_access) .to match_schema('entities/diff_viewer') + end + + context 'when it is a whitespace only change' do + it 'has whitespace_only true' do + expect(subject[:viewer][:whitespace_only]) + .to eq(true) + end + end + + context 'when the highlighted lines arent shown' do + before do + allow(diff_file).to receive(:text?) + .and_return(false) + end + + it 'has whitespace_only nil' do + expect(subject[:viewer][:whitespace_only]) + .to eq(nil) + end + end + + context 'when it is a new file' do + let(:added_lines) { 0 } + + it 'has whitespace_only false' do + expect(subject[:viewer][:whitespace_only]) + .to eq(false) + end + end + + context 'when it is a collapsed file' do + let(:collapsed) { true } + + it 'has whitespace_only false' do + expect(subject[:viewer][:whitespace_only]) + .to eq(false) + end + end end context 'diff files' do diff --git a/spec/support/shared_examples/serializers/note_entity_shared_examples.rb b/spec/support/shared_examples/serializers/note_entity_shared_examples.rb index b5e3a407b53..e8238480ced 100644 --- a/spec/support/shared_examples/serializers/note_entity_shared_examples.rb +++ b/spec/support/shared_examples/serializers/note_entity_shared_examples.rb @@ -18,7 +18,8 @@ RSpec.shared_examples 'note entity' do :noteable_note_url, :report_abuse_path, :resolvable, - :type + :type, + :external_author ) end diff --git a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb deleted file mode 100644 index c2252c83140..00000000000 --- a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'logs kubernetes errors' do - let(:error_hash) do - { - service: service.class.name, - app_id: application.id, - project_ids: application.cluster.project_ids, - group_ids: [], - error_code: error_code - } - end - - it 'logs into kubernetes.log and Sentry' do - expect(Gitlab::ErrorTracking).to receive(:track_exception).with( - error, - hash_including(error_hash) - ) - - service.execute - end -end diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/shared_examples/services/clusters/create_service_shared_examples.rb index 80fa7c58515..7cd76e45ecd 100644 --- a/spec/support/services/clusters/create_service_shared.rb +++ b/spec/support/shared_examples/services/clusters/create_service_shared_examples.rb @@ -1,41 +1,5 @@ # frozen_string_literal: true -RSpec.shared_context 'valid cluster create params' do - let(:clusterable) { Clusters::Instance.new } - let(:params) do - { - name: 'test-cluster', - provider_type: :gcp, - provider_gcp_attributes: { - gcp_project_id: 'gcp-project', - zone: 'us-central1-a', - num_nodes: 1, - machine_type: 'machine_type-a', - legacy_abac: 'true' - }, - clusterable: clusterable - } - end -end - -RSpec.shared_context 'invalid cluster create params' do - let(:clusterable) { Clusters::Instance.new } - let(:params) do - { - name: 'test-cluster', - provider_type: :gcp, - provider_gcp_attributes: { - gcp_project_id: '!!!!!!!', - zone: 'us-central1-a', - num_nodes: 1, - machine_type: 'machine_type-a' - }, - clusterable: clusterable - - } - end -end - RSpec.shared_examples 'create cluster service success' do it 'creates a cluster object' do expect { subject } diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb index 58659775d8c..493a96b8dae 100644 --- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb +++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb @@ -79,7 +79,8 @@ RSpec.shared_examples 'an accessible' do let(:access) do [{ 'type' => 'repository', 'name' => project.full_path, - 'actions' => actions }] + 'actions' => actions, + 'meta' => { 'project_path' => project.full_path } }] end it_behaves_like 'a valid token' @@ -244,12 +245,14 @@ RSpec.shared_examples 'a container registry auth service' do { 'type' => 'repository', 'name' => project.full_path, - 'actions' => ['pull'] + 'actions' => ['pull'], + 'meta' => { 'project_path' => project.full_path } }, { 'type' => 'repository', 'name' => "#{project.full_path}/*", - 'actions' => ['pull'] + 'actions' => ['pull'], + 'meta' => { 'project_path' => project.full_path } } ] end @@ -822,16 +825,20 @@ RSpec.shared_examples 'a container registry auth service' do [ { 'type' => 'repository', 'name' => internal_project.full_path, - 'actions' => ['pull'] }, + 'actions' => ['pull'], + 'meta' => { 'project_path' => internal_project.full_path } }, { 'type' => 'repository', 'name' => private_project.full_path, - 'actions' => ['pull'] }, + 'actions' => ['pull'], + 'meta' => { 'project_path' => private_project.full_path } }, { 'type' => 'repository', 'name' => public_project.full_path, - 'actions' => ['pull'] }, + 'actions' => ['pull'], + 'meta' => { 'project_path' => public_project.full_path } }, { 'type' => 'repository', 'name' => public_project_private_container_registry.full_path, - 'actions' => ['pull'] } + 'actions' => ['pull'], + 'meta' => { 'project_path' => public_project_private_container_registry.full_path } } ] end end @@ -845,10 +852,12 @@ RSpec.shared_examples 'a container registry auth service' do [ { 'type' => 'repository', 'name' => internal_project.full_path, - 'actions' => ['pull'] }, + 'actions' => ['pull'], + 'meta' => { 'project_path' => internal_project.full_path } }, { 'type' => 'repository', 'name' => public_project.full_path, - 'actions' => ['pull'] } + 'actions' => ['pull'], + 'meta' => { 'project_path' => public_project.full_path } } ] end end @@ -862,7 +871,8 @@ RSpec.shared_examples 'a container registry auth service' do [ { 'type' => 'repository', 'name' => public_project.full_path, - 'actions' => ['pull'] } + 'actions' => ['pull'], + 'meta' => { 'project_path' => public_project.full_path } } ] end end @@ -1258,4 +1268,29 @@ RSpec.shared_examples 'a container registry auth service' do end end end + + context 'with a project with a path containing special characters' do + let_it_be(:bad_project) { create(:project) } + + before do + bad_project.update_attribute(:path, "#{bad_project.path}_") + end + + describe '#access_token' do + let(:token) { described_class.access_token(['pull'], [bad_project.full_path]) } + let(:access) do + [{ 'type' => 'repository', + 'name' => bad_project.full_path, + 'actions' => ['pull'] }] + end + + subject { { token: token } } + + it_behaves_like 'a valid token' + + it 'has the correct scope' do + expect(payload).to include('access' => access) + end + end + end end diff --git a/spec/support/services/deploy_token_shared_examples.rb b/spec/support/shared_examples/services/deploy_token_shared_examples.rb index d322b3fc81d..814b6565497 100644 --- a/spec/support/services/deploy_token_shared_examples.rb +++ b/spec/support/shared_examples/services/deploy_token_shared_examples.rb @@ -50,7 +50,9 @@ RSpec.shared_examples 'a deploy token creation service' do end context 'when the deploy token is invalid' do - let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false, write_registry: false) } + let(:deploy_token_params) do + attributes_for(:deploy_token, read_repository: false, read_registry: false, write_registry: false) + end it 'does not create a new DeployToken' do expect { subject }.not_to change { DeployToken.count } @@ -75,7 +77,7 @@ RSpec.shared_examples 'a deploy token deletion service' do .and change { DeployToken.count }.by(-1) end - context 'invalid token id' do + context 'with invalid token id' do let(:deploy_token_params) { { token_id: 9999 } } it 'raises an error' do diff --git a/spec/support/shared_examples/services/import_csv_service_shared_examples.rb b/spec/support/shared_examples/services/import_csv_service_shared_examples.rb new file mode 100644 index 00000000000..1555497ae48 --- /dev/null +++ b/spec/support/shared_examples/services/import_csv_service_shared_examples.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples_for 'importer with email notification' do + it 'notifies user of import result' do + expect(Notify).to receive_message_chain(email_method, :deliver_later) + + subject + end +end + +RSpec.shared_examples 'correctly handles invalid files' do + shared_examples_for 'invalid file' do + it 'returns invalid file error' do + expect(subject[:success]).to eq(0) + expect(subject[:parse_error]).to eq(true) + end + end + + context 'when given file with unsupported extension' do + let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') } + + it_behaves_like 'invalid file' + end + + context 'when given empty file' do + let(:file) { fixture_file_upload('spec/fixtures/csv_empty.csv') } + + it_behaves_like 'invalid file' + end + + context 'when given file without headers' do + let(:file) { fixture_file_upload('spec/fixtures/csv_no_headers.csv') } + + it_behaves_like 'invalid file' + end +end diff --git a/spec/support/shared_examples/services/incident_shared_examples.rb b/spec/support/shared_examples/services/incident_shared_examples.rb index a87e7c1f801..db2b448f567 100644 --- a/spec/support/shared_examples/services/incident_shared_examples.rb +++ b/spec/support/shared_examples/services/incident_shared_examples.rb @@ -12,7 +12,6 @@ # include_examples 'incident issue' RSpec.shared_examples 'incident issue' do it 'has incident as issue type' do - expect(issue.issue_type).to eq('incident') expect(issue.work_item_type.base_type).to eq('incident') end end @@ -29,7 +28,6 @@ end # include_examples 'not an incident issue' RSpec.shared_examples 'not an incident issue' do it 'has not incident as issue type' do - expect(issue.issue_type).not_to eq('incident') expect(issue.work_item_type.base_type).not_to eq('incident') end end diff --git a/spec/support/services/issuable_description_quick_actions_shared_examples.rb b/spec/support/shared_examples/services/issuable/issuable_description_quick_actions_shared_examples.rb index 1970301e4c9..1970301e4c9 100644 --- a/spec/support/services/issuable_description_quick_actions_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable/issuable_description_quick_actions_shared_examples.rb diff --git a/spec/support/services/issuable_import_csv_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/issuable_import_csv_service_shared_examples.rb index 0dea6cfb729..5336e0f4c2f 100644 --- a/spec/support/services/issuable_import_csv_service_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable/issuable_import_csv_service_shared_examples.rb @@ -18,45 +18,14 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type| end end - shared_examples_for 'importer with email notification' do - it 'notifies user of import result' do - expect(Notify).to receive_message_chain(email_method, :deliver_later) - - subject - end - end - - shared_examples_for 'invalid file' do - it 'returns invalid file error' do - expect(subject[:success]).to eq(0) - expect(subject[:parse_error]).to eq(true) - end - - it_behaves_like 'importer with email notification' - it_behaves_like 'an issuable importer' - end - describe '#execute' do before do project.add_developer(user) end - context 'invalid file extension' do - let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') } - - it_behaves_like 'invalid file' - end - - context 'empty file' do - let(:file) { fixture_file_upload('spec/fixtures/csv_empty.csv') } - - it_behaves_like 'invalid file' - end - - context 'file without headers' do - let(:file) { fixture_file_upload('spec/fixtures/csv_no_headers.csv') } - - it_behaves_like 'invalid file' + it_behaves_like 'correctly handles invalid files' do + it_behaves_like 'importer with email notification' + it_behaves_like 'an issuable importer' end context 'with a file generated by Gitlab CSV export' do @@ -78,7 +47,7 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type| it_behaves_like 'an issuable importer' end - context 'comma delimited file' do + context 'with comma delimited file' do let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') } it 'imports CSV without errors' do @@ -97,7 +66,7 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type| it_behaves_like 'an issuable importer' end - context 'tab delimited file with error row' do + context 'with tab delimited file with error row' do let(:file) { fixture_file_upload('spec/fixtures/csv_tab.csv') } it 'imports CSV with some error rows' do @@ -116,7 +85,7 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type| it_behaves_like 'an issuable importer' end - context 'semicolon delimited file with CRLF' do + context 'with semicolon delimited file with CRLF' do let(:file) { fixture_file_upload('spec/fixtures/csv_semicolon.csv') } it 'imports CSV with a blank row' do diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb index feea21be428..85a05bbe56d 100644 --- a/spec/support/services/issuable_update_service_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb @@ -5,10 +5,10 @@ RSpec.shared_examples 'issuable update service' do described_class.new(project, user, opts).execute(open_issuable) end - context 'changing state' do + describe 'changing state' do let(:hook_event) { :"#{closed_issuable.class.name.underscore.to_sym}_hooks" } - context 'to reopened' do + describe 'to reopened' do let(:expected_payload) do include( changes: include( @@ -23,14 +23,19 @@ RSpec.shared_examples 'issuable update service' do end it 'executes hooks' do - expect(project).to receive(:execute_hooks).with(expected_payload, hook_event) - expect(project).to receive(:execute_integrations).with(expected_payload, hook_event) - - described_class.new(**described_class.constructor_container_arg(project), current_user: user, params: { state_event: 'reopen' }).execute(closed_issuable) + hooks_container = described_class < Issues::BaseService ? project.project_namespace : project + expect(hooks_container).to receive(:execute_hooks).with(expected_payload, hook_event) + expect(hooks_container).to receive(:execute_integrations).with(expected_payload, hook_event) + + described_class.new( + **described_class.constructor_container_arg(project), + current_user: user, + params: { state_event: 'reopen' } + ).execute(closed_issuable) end end - context 'to closed' do + describe 'to closed' do let(:expected_payload) do include( changes: include( @@ -45,10 +50,15 @@ RSpec.shared_examples 'issuable update service' do end it 'executes hooks' do - expect(project).to receive(:execute_hooks).with(expected_payload, hook_event) - expect(project).to receive(:execute_integrations).with(expected_payload, hook_event) - - described_class.new(**described_class.constructor_container_arg(project), current_user: user, params: { state_event: 'close' }).execute(open_issuable) + hooks_container = described_class < Issues::BaseService ? project.project_namespace : project + expect(hooks_container).to receive(:execute_hooks).with(expected_payload, hook_event) + expect(hooks_container).to receive(:execute_integrations).with(expected_payload, hook_event) + + described_class.new( + **described_class.constructor_container_arg(project), + current_user: user, + params: { state_event: 'close' } + ).execute(open_issuable) end end end @@ -97,3 +107,31 @@ RSpec.shared_examples 'broadcasting issuable labels updates' do end end end + +RSpec.shared_examples_for 'issuable update service updating last_edited_at values' do + context 'when updating the title of the issuable' do + 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( + not_change(issuable, :last_edited_at) + ).and( + not_change(issuable, :last_edited_by) + ) + end + end + + context 'when updating the description of the issuable' do + let(:update_params) { { description: 'updated description' } } + + 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 } + ).and( + change { issuable.last_edited_by } + ) + end + end +end 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 deleted file mode 100644 index ff7acc7e907..00000000000 --- a/spec/support/shared_examples/services/issuable/update_service_shared_examples.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples_for 'issuable update service updating last_edited_at values' do - context 'when updating the title of the issuable' do - 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( - not_change(issuable, :last_edited_at) - ).and( - not_change(issuable, :last_edited_by) - ) - end - end - - context 'when updating the description of the issuable' do - let(:update_params) { { description: 'updated description' } } - - 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 } - ).and( - change { issuable.last_edited_by } - ) - end - 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 e47ff2fcd59..0bf8bc4ff04 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 @@ -34,7 +34,11 @@ RSpec.shared_examples 'issuable link creation' do end it 'returns error' do - is_expected.to eq(message: "No matching #{issuable_type} found. Make sure that you are adding a valid #{issuable_type} URL.", status: :error, http_status: 404) + if issuable_type == :issue + is_expected.to eq(message: "Couldn't link #{issuable_type}. You must have at least the Reporter role in both projects.", status: :error, http_status: 403) + else + is_expected.to eq(message: "No matching #{issuable_type} found. Make sure that you are adding a valid #{issuable_type} URL.", status: :error, http_status: 404) + end end it 'no relationship is created' do diff --git a/spec/support/services/issues/move_and_clone_services_shared_examples.rb b/spec/support/shared_examples/services/issues/move_and_clone_services_shared_examples.rb index 2b2e90c0461..2b2e90c0461 100644 --- a/spec/support/services/issues/move_and_clone_services_shared_examples.rb +++ b/spec/support/shared_examples/services/issues/move_and_clone_services_shared_examples.rb diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb index ae98ce689e3..e77d73d1c72 100644 --- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb +++ b/spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb @@ -54,10 +54,10 @@ RSpec.shared_examples "migrating a deleted user's associated records to the ghos end end - context "race conditions" do + describe "race conditions" do context "when #{record_class_name} migration fails and is rolled back" do before do - allow_any_instance_of(ActiveRecord::Associations::CollectionProxy) + allow_next_instance_of(ActiveRecord::Associations::CollectionProxy) .to receive(:update_all).and_raise(ActiveRecord::StatementTimeout) end diff --git a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb index a3042ac2e26..cb544f42765 100644 --- a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb +++ b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb @@ -29,26 +29,76 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do let_it_be(:architecture_amd64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'amd64') } let_it_be(:architecture_arm64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'arm64') } - let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T08:00:00Z', file_sha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', file_md5: 'd41d8cd98f00b204e9800998ecf8427e', file_fixture: nil, size: 0) } # updated - let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_all, updated_at: '2020-01-24T09:00:00Z', file_sha256: 'a') } # destroyed - let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_amd64, updated_at: '2020-01-24T10:54:59Z', file_sha256: 'b') } # destroyed, 1 second before last generation - let_it_be(:component_file4) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'c') } # kept, last generation - let_it_be(:component_file5) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'd') } # kept, last generation - let_it_be(:component_file6) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_amd64, updated_at: '2020-01-25T15:17:18Z', file_sha256: 'e') } # kept, less than 1 hour ago - - def check_component_file(release_date, component_name, component_file_type, architecture_name, expected_content) + let_it_be(:component_file_old_main_amd64) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_amd64, updated_at: '2020-01-24T08:00:00Z', file_sha256: 'a') } # destroyed + + let_it_be(:component_file_oldest_kept_contrib_all) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'b') } # oldest kept + let_it_be(:component_file_oldest_kept_contrib_amd64) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_amd64, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'c') } # oldest kept + let_it_be(:component_file_recent_contrib_amd64) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_amd64, updated_at: '2020-01-25T15:17:18Z', file_sha256: 'd') } # kept, less than 1 hour ago + + let_it_be(:component_file_empty_contrib_all_di) { create("debian_#{container_type}_component_file", :di_packages, :empty, component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z') } # oldest kept + let_it_be(:component_file_empty_contrib_amd64_di) { create("debian_#{container_type}_component_file", :di_packages, :empty, component: component_contrib, architecture: architecture_amd64, updated_at: '2020-01-24T10:55:00Z') } # touched, as last empty + let_it_be(:component_file_recent_contrib_amd64_di) { create("debian_#{container_type}_component_file", :di_packages, component: component_contrib, architecture: architecture_amd64, updated_at: '2020-01-25T15:17:18Z', file_sha256: 'f') } # kept, less than 1 hour ago + + let(:pool_prefix) do + prefix = "pool/#{distribution.codename}" + prefix += "/#{project.id}" if container_type == :group + prefix += "/#{package.name[0]}/#{package.name}/#{package.version}" + prefix + end + + let(:expected_main_amd64_di_content) do + <<~MAIN_AMD64_DI_CONTENT + Section: misc + Priority: extra + Filename: #{pool_prefix}/sample-udeb_1.2.3~alpha2_amd64.udeb + Size: 409600 + SHA256: #{package.package_files.with_debian_file_type(:udeb).first.file_sha256} + MAIN_AMD64_DI_CONTENT + end + + let(:expected_main_amd64_di_sha256) { Digest::SHA256.hexdigest(expected_main_amd64_di_content) } + let!(:component_file_old_main_amd64_di) do # touched + create("debian_#{container_type}_component_file", :di_packages, component: component_main, architecture: architecture_amd64, updated_at: '2020-01-24T08:00:00Z', file_sha256: expected_main_amd64_di_sha256).tap do |cf| + cf.update! file: CarrierWaveStringFile.new(expected_main_amd64_di_content), size: expected_main_amd64_di_content.size + end + end + + def check_component_file( + release_date, component_name, component_file_type, architecture_name, expected_content, + updated: true, id_of: nil + ) component_file = distribution .component_files .with_component_name(component_name) .with_file_type(component_file_type) .with_architecture_name(architecture_name) + .with_compression_type(nil) .order_updated_asc .last + if expected_content.nil? + expect(component_file).to be_nil + return + end + expect(component_file).not_to be_nil - expect(component_file.updated_at).to eq(release_date) - unless expected_content.nil? + if id_of + expect(component_file&.id).to eq(id_of.id) + else + # created + expect(component_file&.id).to be > component_file_old_main_amd64_di.id + end + + if updated + expect(component_file.updated_at).to eq(release_date) + else + expect(component_file.updated_at).not_to eq(release_date) + end + + if expected_content == '' + expect(component_file.size).to eq(0) + else expect(expected_content).not_to include('MD5') component_file.file.use_file do |file_path| expect(File.read(file_path)).to eq(expected_content) @@ -57,30 +107,23 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do end it 'generates Debian distribution and component files', :aggregate_failures do - current_time = Time.utc(2020, 01, 25, 15, 17, 18, 123456) + current_time = Time.utc(2020, 1, 25, 15, 17, 19) travel_to(current_time) do expect(Gitlab::ErrorTracking).not_to receive(:log_exception) - components_count = 2 - architectures_count = 3 - - initial_count = 6 - destroyed_count = 2 - updated_count = 1 - created_count = components_count * (architectures_count * 2 + 1) - updated_count + initial_count = 8 + destroyed_count = 1 + created_count = 4 # main_amd64 + main_sources + empty contrib_all + empty contrib_amd64 expect { subject } .to not_change { Packages::Package.count } .and not_change { Packages::PackageFile.count } .and change { distribution.reload.updated_at }.to(current_time.round) .and change { distribution.component_files.reset.count }.from(initial_count).to(initial_count - destroyed_count + created_count) - .and change { component_file1.reload.updated_at }.to(current_time.round) + .and change { component_file_old_main_amd64_di.reload.updated_at }.to(current_time.round) package_files = package.package_files.order(id: :asc).preload_debian_file_metadata.to_a - pool_prefix = "pool/#{distribution.codename}" - pool_prefix += "/#{project.id}" if container_type == :group - pool_prefix += "/#{package.name[0]}/#{package.name}/#{package.version}" expected_main_amd64_content = <<~EOF Package: libsample0 Source: #{package.name} @@ -120,17 +163,9 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do SHA256: #{package_files[3].file_sha256} EOF - expected_main_amd64_di_content = <<~EOF - Section: misc - Priority: extra - Filename: #{pool_prefix}/sample-udeb_1.2.3~alpha2_amd64.udeb - Size: 409600 - SHA256: #{package_files[4].file_sha256} - EOF - expected_main_sources_content = <<~EOF Package: #{package.name} - Binary: sample-dev, libsample0, sample-udeb + Binary: sample-dev, libsample0, sample-udeb, sample-ddeb Version: #{package.version} Maintainer: #{package_files[1].debian_fields['Maintainer']} Build-Depends: debhelper-compat (= 13) @@ -139,13 +174,13 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do Format: 3.0 (native) Files: #{package_files[1].file_md5} #{package_files[1].size} #{package_files[1].file_name} - d5ca476e4229d135a88f9c729c7606c9 864 sample_1.2.3~alpha2.tar.xz + #{package_files[0].file_md5} 964 #{package_files[0].file_name} Checksums-Sha256: #{package_files[1].file_sha256} #{package_files[1].size} #{package_files[1].file_name} - 40e4682bb24a73251ccd7c7798c0094a649091e5625d6a14bcec9b4e7174f3da 864 sample_1.2.3~alpha2.tar.xz + #{package_files[0].file_sha256} 964 #{package_files[0].file_name} Checksums-Sha1: #{package_files[1].file_sha1} #{package_files[1].size} #{package_files[1].file_name} - c5cfc111ea924842a89a06d5673f07dfd07de8ca 864 sample_1.2.3~alpha2.tar.xz + #{package_files[0].file_sha1} 964 #{package_files[0].file_name} Homepage: #{package_files[1].debian_fields['Homepage']} Section: misc Priority: extra @@ -157,42 +192,38 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do check_component_file(current_time.round, 'main', :packages, 'arm64', nil) check_component_file(current_time.round, 'main', :di_packages, 'all', nil) - check_component_file(current_time.round, 'main', :di_packages, 'amd64', expected_main_amd64_di_content) + check_component_file(current_time.round, 'main', :di_packages, 'amd64', expected_main_amd64_di_content, id_of: component_file_old_main_amd64_di) check_component_file(current_time.round, 'main', :di_packages, 'arm64', nil) check_component_file(current_time.round, 'main', :sources, nil, expected_main_sources_content) - check_component_file(current_time.round, 'contrib', :packages, 'all', nil) - check_component_file(current_time.round, 'contrib', :packages, 'amd64', nil) + check_component_file(current_time.round, 'contrib', :packages, 'all', '') + check_component_file(current_time.round, 'contrib', :packages, 'amd64', '') check_component_file(current_time.round, 'contrib', :packages, 'arm64', nil) - check_component_file(current_time.round, 'contrib', :di_packages, 'all', nil) - check_component_file(current_time.round, 'contrib', :di_packages, 'amd64', nil) + check_component_file(current_time.round, 'contrib', :di_packages, 'all', '', updated: false, id_of: component_file_empty_contrib_all_di) + check_component_file(current_time.round, 'contrib', :di_packages, 'amd64', '', id_of: component_file_empty_contrib_amd64_di) check_component_file(current_time.round, 'contrib', :di_packages, 'arm64', nil) check_component_file(current_time.round, 'contrib', :sources, nil, nil) - main_amd64_size = expected_main_amd64_content.length - main_amd64_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_content) + expected_main_amd64_size = expected_main_amd64_content.bytesize + expected_main_amd64_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_content) - contrib_all_size = component_file1.size - contrib_all_sha256 = component_file1.file_sha256 + expected_main_amd64_di_size = expected_main_amd64_di_content.length - main_amd64_di_size = expected_main_amd64_di_content.length - main_amd64_di_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_di_content) - - main_sources_size = expected_main_sources_content.length - main_sources_sha256 = Digest::SHA256.hexdigest(expected_main_sources_content) + expected_main_sources_size = expected_main_sources_content.length + expected_main_sources_sha256 = Digest::SHA256.hexdigest(expected_main_sources_content) expected_release_content = <<~EOF Codename: #{distribution.codename} - Date: Sat, 25 Jan 2020 15:17:18 +0000 - Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 + Date: Sat, 25 Jan 2020 15:17:19 +0000 + Valid-Until: Mon, 27 Jan 2020 15:17:19 +0000 Acquire-By-Hash: yes Architectures: all amd64 arm64 Components: contrib main SHA256: - #{contrib_all_sha256} #{contrib_all_size.to_s.rjust(8)} contrib/binary-all/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-all/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages @@ -201,11 +232,11 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/source/Sources e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-all/Packages - #{main_amd64_sha256} #{main_amd64_size.to_s.rjust(8)} main/binary-amd64/Packages - #{main_amd64_di_sha256} #{main_amd64_di_size.to_s.rjust(8)} main/debian-installer/binary-amd64/Packages + #{expected_main_amd64_sha256} #{expected_main_amd64_size.to_s.rjust(8)} main/binary-amd64/Packages + #{expected_main_amd64_di_sha256} #{expected_main_amd64_di_size.to_s.rjust(8)} main/debian-installer/binary-amd64/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/debian-installer/binary-arm64/Packages - #{main_sources_sha256} #{main_sources_size.to_s.rjust(8)} main/source/Sources + #{expected_main_sources_sha256} #{expected_main_sources_size.to_s.rjust(8)} main/source/Sources EOF expected_release_content = "Suite: #{distribution.suite}\n#{expected_release_content}" if distribution.suite @@ -222,7 +253,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do context 'without components and architectures' do it 'generates minimal distribution', :aggregate_failures do - travel_to(Time.utc(2020, 01, 25, 15, 17, 18, 123456)) do + travel_to(Time.utc(2020, 1, 25, 15, 17, 18, 123456)) do expect(Gitlab::ErrorTracking).not_to receive(:log_exception) expect { subject } diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb index f63693dbf26..7a4d7f81e96 100644 --- a/spec/support/shared_examples/services/packages_shared_examples.rb +++ b/spec/support/shared_examples/services/packages_shared_examples.rb @@ -76,7 +76,7 @@ RSpec.shared_examples 'returns packages' do |container_type, user_type| subject expect(json_response.length).to eq(2) - expect(json_response.map { |package| package['id'] }).to contain_exactly(package1.id, package2.id) + expect(json_response.pluck('id')).to contain_exactly(package1.id, package2.id) end end end @@ -123,7 +123,7 @@ RSpec.shared_examples 'returns packages with subgroups' do |container_type, user subject expect(json_response.length).to eq(3) - expect(json_response.map { |package| package['id'] }).to contain_exactly(package1.id, package2.id, package3.id) + expect(json_response.pluck('id')).to contain_exactly(package1.id, package2.id, package3.id) end end end @@ -138,7 +138,7 @@ RSpec.shared_examples 'package sorting' do |order_by| it 'returns the sorted packages' do subject - expect(json_response.map { |package| package['id'] }).to eq(packages.map(&:id)) + expect(json_response.pluck('id')).to eq(packages.map(&:id)) end end @@ -148,7 +148,7 @@ RSpec.shared_examples 'package sorting' do |order_by| it 'returns the sorted packages' do subject - expect(json_response.map { |package| package['id'] }).to eq(packages.reverse.map(&:id)) + expect(json_response.pluck('id')).to eq(packages.reverse.map(&:id)) end end end @@ -225,7 +225,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false| subject expect(json_response.length).to eq(1) - expect(json_response.map { |package| package['package_type'] }).to contain_exactly(package_type) + expect(json_response.pluck('package_type')).to contain_exactly(package_type) end end end @@ -253,7 +253,7 @@ RSpec.shared_examples 'with versionless packages' do it 'does not return the package' do subject - expect(json_response.map { |package| package['id'] }).not_to include(versionless_package.id) + expect(json_response.pluck('id')).not_to include(versionless_package.id) end end @@ -268,7 +268,7 @@ RSpec.shared_examples 'with versionless packages' do it 'returns the package' do subject - expect(json_response.map { |package| package['id'] }).to include(versionless_package.id) + expect(json_response.pluck('id')).to include(versionless_package.id) end end end @@ -295,7 +295,7 @@ RSpec.shared_examples 'with status param' do it 'does not return the package' do subject - expect(json_response.map { |package| package['id'] }).not_to include(hidden_package.id) + expect(json_response.pluck('id')).not_to include(hidden_package.id) end end @@ -309,7 +309,7 @@ RSpec.shared_examples 'with status param' do it 'returns the package' do subject - expect(json_response.map { |package| package['id'] }).to include(hidden_package.id) + expect(json_response.pluck('id')).to include(hidden_package.id) end end end diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb index 9f940d27341..2070cac24b0 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 @@ -63,35 +63,6 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| expect(gitlab_shell.repository_exists?('default', old_project_repository_path)).to be(false) expect(gitlab_shell.repository_exists?('default', old_repository_path)).to be(false) end - - context ':repack_after_shard_migration feature flag disabled' do - before do - stub_feature_flags(repack_after_shard_migration: false) - end - - it 'does not enqueue a GC run' do - expect { subject.execute } - .not_to change { Projects::GitGarbageCollectWorker.jobs.count } - end - end - - context ':repack_after_shard_migration feature flag enabled' do - before do - stub_feature_flags(repack_after_shard_migration: true) - end - - it 'does not enqueue a GC run if housekeeping is disabled' do - stub_application_setting(housekeeping_enabled: false) - - expect { subject.execute } - .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) - end - end end context 'when the filesystems are the same' do diff --git a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb index 209be09c807..21dc3c2bf70 100644 --- a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb +++ b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb @@ -114,7 +114,8 @@ RSpec.shared_examples_for 'services security ci configuration create service' do it 'fails with error' do expect(project).to receive(:ci_config_for).and_return(unsupported_yaml) - expect { result }.to raise_error(Gitlab::Graphql::Errors::MutationError, '.gitlab-ci.yml with aliases/anchors is not supported. Please change the CI configuration manually.') + expect { result }.to raise_error(Gitlab::Graphql::Errors::MutationError, Gitlab::Utils::ErrorMessage.to_user_facing( + _(".gitlab-ci.yml with aliases/anchors is not supported. Please change the CI configuration manually."))) end end @@ -145,7 +146,7 @@ RSpec.shared_examples_for 'services security ci configuration create service' do let_it_be(:repository) { project.repository } it 'is successful' do - expect(repository).to receive(:root_ref_sha).and_raise(StandardError) + expect(repository).to receive(:commit).and_return(nil) expect(result.status).to eq(:success) end end @@ -168,7 +169,7 @@ RSpec.shared_examples_for 'services security ci configuration create service' do it 'returns an error' do expect { result }.to raise_error { |error| expect(error).to be_a(Gitlab::Graphql::Errors::MutationError) - expect(error.message).to eq('You must <a target="_blank" rel="noopener noreferrer" ' \ + expect(error.message).to eq('UF You must <a target="_blank" rel="noopener noreferrer" ' \ 'href="http://localhost/help/user/project/repository/index.md' \ '#add-files-to-a-repository">add at least one file to the repository' \ '</a> before using Security features.') diff --git a/spec/support/services/service_response_shared_examples.rb b/spec/support/shared_examples/services/service_response_shared_examples.rb index 186627347fb..e55f16a2994 100644 --- a/spec/support/services/service_response_shared_examples.rb +++ b/spec/support/shared_examples/services/service_response_shared_examples.rb @@ -6,9 +6,7 @@ RSpec.shared_examples 'returning an error service response' do |message: nil| expect(result).to be_error - if message - expect(result.message).to eq(message) - end + expect(result.message).to eq(message) if message end end @@ -18,8 +16,6 @@ RSpec.shared_examples 'returning a success service response' do |message: nil| expect(result).to be_success - if message - expect(result.message).to eq(message) - end + expect(result.message).to eq(message) if message 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 e72e8e79411..d3b3434b339 100644 --- a/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb +++ b/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb @@ -5,7 +5,6 @@ RSpec.shared_examples 'issue_edit snowplow tracking' do let(:action) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_ACTION } let(:label) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_LABEL } let(:namespace) { project.namespace } - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } it_behaves_like 'Snowplow event tracking with RedisHLL context' end 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 deleted file mode 100644 index ac064ed4c33..00000000000 --- a/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples "setting work item's milestone" do - context "when 'milestone' param does not exist" do - let(:params) { {} } - - it "does not set the work item's milestone" do - expect { execute_callback }.to not_change(work_item, :milestone) - end - end - - context "when 'milestone' is not in the work item's project's hierarchy" do - let(:another_group_milestone) { create(:milestone, group: create(:group)) } - let(:params) { { milestone_id: another_group_milestone.id } } - - it "does not set the work item's milestone" do - expect { execute_callback }.to not_change(work_item, :milestone) - end - end - - context 'when assigning a group milestone' do - let(:params) { { milestone_id: group_milestone.id } } - - it "sets the work item's milestone" do - expect { execute_callback } - .to change { work_item.milestone } - .from(nil) - .to(group_milestone) - end - end - - context 'when assigning a project milestone' do - let(:params) { { milestone_id: project_milestone.id } } - - it "sets the work item's milestone" do - expect { execute_callback } - .to change { work_item.milestone } - .from(nil) - .to(project_milestone) - end - end -end diff --git a/spec/support/shared_examples/views/pipeline_status_changes_email.rb b/spec/support/shared_examples/views/pipeline_status_changes_email.rb index 698f11c2216..fe6cc5e03d2 100644 --- a/spec/support/shared_examples/views/pipeline_status_changes_email.rb +++ b/spec/support/shared_examples/views/pipeline_status_changes_email.rb @@ -8,12 +8,14 @@ RSpec.shared_examples 'pipeline status changes email' do let(:merge_request) { create(:merge_request, :simple, source_project: project) } let(:pipeline) do - create(:ci_pipeline, - project: project, - user: user, - ref: project.default_branch, - sha: project.commit.sha, - status: status) + create( + :ci_pipeline, + project: project, + user: user, + ref: project.default_branch, + sha: project.commit.sha, + status: status + ) end before do diff --git a/spec/support/shared_examples/work_items/export_and_import_shared_examples.rb b/spec/support/shared_examples/work_items/export_and_import_shared_examples.rb new file mode 100644 index 00000000000..bbbfacfdf53 --- /dev/null +++ b/spec/support/shared_examples/work_items/export_and_import_shared_examples.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +RSpec.shared_examples_for 'a exported file that can be imported' do + before do + origin_project.add_reporter(user) + target_project.add_reporter(user) + end + + def export_work_items_for(project) + origin_work_items = WorkItem.where(project: origin_project) + export = described_class.new(origin_work_items, project) + export.email(user) + attachment = ActionMailer::Base.deliveries.last.attachments.first + file = Tempfile.new('temp_work_item_export.csv') + file.write(attachment.read) + + file + end + + def import_file_for(project, file) + uploader = FileUploader.new(project) + uploader.store!(file) + import_service = WorkItems::ImportCsvService.new(user, target_project, uploader) + + import_service.execute + end + + it 'imports work item with correct attributes', :aggregate_failures do + csv_file = export_work_items_for(origin_project) + + imported_work_items = ::WorkItems::WorkItemsFinder.new(user, project: target_project).execute + expect { import_file_for(target_project, csv_file) }.to change { imported_work_items.count }.by 1 + imported_work_item = imported_work_items.first + expect(imported_work_item.author).to eq(user) + expected_matching_fields.each do |field| + expect(imported_work_item.public_send(field)).to eq(work_item.public_send(field)) + end + end +end diff --git a/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb index e224b71da91..095c32c3136 100644 --- a/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb +++ b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb @@ -50,14 +50,20 @@ RSpec.shared_examples 'batched background migrations execution worker' do 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) + it 'returns database_max_running_batched_background_migrations application setting' do + stub_application_setting(database_max_running_batched_background_migrations: 3) + + expect(described_class.max_running_jobs) + .to eq(Gitlab::CurrentSettings.database_max_running_batched_background_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) + it 'returns database_max_running_batched_background_migrations application setting' do + stub_application_setting(database_max_running_batched_background_migrations: 3) + + expect(described_class.new.max_running_jobs) + .to eq(Gitlab::CurrentSettings.database_max_running_batched_background_migrations) 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 8ec955940c0..06877aee565 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 @@ -88,9 +88,9 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d end end - context 'when the feature flag is disabled' do + context 'when the tracking database is shared' do before do - stub_feature_flags(execute_batched_migrations_on_schedule: false) + skip_if_database_exists(tracking_database) end it 'does nothing' do @@ -101,22 +101,17 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d end end - context 'when the feature flag is enabled' do - let(:base_model) { Gitlab::Database.database_base_models[tracking_database] } - + context 'when the tracking database is not shared' do before do - stub_feature_flags(execute_batched_migrations_on_schedule: true) - - allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration) - .with(connection: base_model.connection) - .and_return(nil) + skip_if_shared_database(tracking_database) end - context 'when database config is shared' do - it 'does nothing' do - expect(Gitlab::Database).to receive(:db_config_share_with) - .with(base_model.connection_db_config).and_return('main') + context 'when the feature flag is disabled' do + before do + stub_feature_flags(execute_batched_migrations_on_schedule: false) + end + it 'does nothing' do expect(worker).not_to receive(:active_migration) expect(worker).not_to receive(:run_active_migration) @@ -124,123 +119,146 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d end end - context 'when no active migrations exist' do - context 'when parallel execution is disabled' do - before do - stub_feature_flags(batched_migrations_parallel_execution: false) - end + context 'when the feature flag is enabled' do + let(:base_model) { Gitlab::Database.database_base_models[tracking_database] } + let(:connection) { base_model.connection } + + before do + stub_feature_flags(execute_batched_migrations_on_schedule: true) + allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration) + .with(connection: connection) + .and_return(nil) + end + + context 'when database config is shared' do it 'does nothing' do + expect(Gitlab::Database).to receive(:db_config_share_with) + .with(base_model.connection_db_config).and_return('main') + + expect(worker).not_to receive(:active_migration) 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 + context 'when no active migrations exist' do + context 'when parallel execution is disabled' do + before do + stub_feature_flags(batched_migrations_parallel_execution: false) + end - it 'does nothing' do - expect(worker).not_to receive(:queue_migrations_for_execution) + it 'does nothing' do + expect(worker).not_to receive(:run_active_migration) - worker.perform + worker.perform + end end - end - end - context 'when active migrations exist' do - let(:job_interval) { 5.minutes } - let(:lease_timeout) { 15.minutes } - let(:lease_key) { described_class.name.demodulize.underscore } - let(:migration_id) { 123 } - let(:migration) do - build( - :batched_background_migration, :active, - id: migration_id, interval: job_interval, table_name: table_name - ) - end + context 'when parallel execution is enabled' do + before do + stub_feature_flags(batched_migrations_parallel_execution: true) + end - let(:execution_worker_class) do - case tracking_database - when :main - Database::BatchedBackgroundMigration::MainExecutionWorker - when :ci - Database::BatchedBackgroundMigration::CiExecutionWorker + it 'does nothing' do + expect(worker).not_to receive(:queue_migrations_for_execution) + + worker.perform + end end end - before do - allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration) - .with(connection: base_model.connection) - .and_return(migration) - end + context 'when active migrations exist' do + let(:job_interval) { 5.minutes } + let(:lease_timeout) { 15.minutes } + let(:lease_key) { described_class.name.demodulize.underscore } + let(:migration_id) { 123 } + let(:migration) do + build( + :batched_background_migration, :active, + id: migration_id, interval: job_interval, table_name: table_name + ) + end + + let(:execution_worker_class) do + case tracking_database + when :main + Database::BatchedBackgroundMigration::MainExecutionWorker + when :ci + Database::BatchedBackgroundMigration::CiExecutionWorker + end + end - context 'when parallel execution is disabled' do before do - stub_feature_flags(batched_migrations_parallel_execution: false) + allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration) + .with(connection: connection) + .and_return(migration) end - let(:execution_worker) { instance_double(execution_worker_class) } + context 'when parallel execution is disabled' do + before do + stub_feature_flags(batched_migrations_parallel_execution: false) + 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 } + 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(execution_worker_class).to receive(:new).and_return(execution_worker) - expect(execution_worker).to receive(:perform_work).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(worker).to receive(:run_active_migration).and_call_original + 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 + 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) + worker.perform + end + end - expect(lease).to receive(:try_obtain).and_return(true) + it 'always cleans up the exclusive lease' do + lease = stub_exclusive_lease_taken(lease_key, timeout: lease_timeout) - expect(worker).to receive(:run_active_migration).and_raise(RuntimeError, 'I broke') - expect(lease).to receive(:cancel) + expect(lease).to receive(:try_obtain).and_return(true) - expect { worker.perform }.to raise_error(RuntimeError, 'I broke') - end + expect(worker).to receive(:run_active_migration).and_raise(RuntimeError, 'I broke') + expect(lease).to receive(:cancel) - it 'delegetes the execution to ExecutionWorker' do - base_model = Gitlab::Database.database_base_models[tracking_database] + expect { worker.perform }.to raise_error(RuntimeError, 'I broke') + end - 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) + it 'delegetes the execution to ExecutionWorker' do + expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(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 + worker.perform + end end - end - context 'when parallel execution is enabled' do - before do - stub_feature_flags(batched_migrations_parallel_execution: true) - end + context 'when parallel execution is enabled' do + before do + stub_feature_flags(batched_migrations_parallel_execution: true) + end - 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]) + 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]) - expected_arguments = [ - [tracking_database.to_s, migration_id] - ] + expected_arguments = [ + [tracking_database.to_s, migration_id] + ] - expect(execution_worker_class).to receive(:perform_with_capacity).with(expected_arguments) + expect(execution_worker_class).to receive(:perform_with_capacity).with(expected_arguments) - worker.perform + worker.perform + end end end end @@ -248,7 +266,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d end end - describe 'executing an entire migration', :freeze_time, if: Gitlab::Database.has_config?(tracking_database) do + describe 'executing an entire migration', :freeze_time, if: Gitlab::Database.has_database?(tracking_database) do include Gitlab::Database::DynamicModelHelpers include Database::DatabaseHelpers diff --git a/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb b/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb deleted file mode 100644 index e6da96e12ec..00000000000 --- a/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -# This shared_example requires the following variables: -# let(:service_class) { Gitlab::DatabaseImporters::SelfMonitoring::Project::DeleteService } -# let(:service) { instance_double(service_class) } -RSpec.shared_examples 'executes service' do - before do - allow(service_class).to receive(:new) { service } - end - - it 'runs the service' do - expect(service).to receive(:execute) - - subject.perform - end -end - -RSpec.shared_examples 'returns in_progress based on Sidekiq::Status' do - it 'returns true when job is enqueued' do - jid = described_class.with_status.perform_async - - expect(described_class.in_progress?(jid)).to eq(true) - end - - it 'returns false when job does not exist' do - expect(described_class.in_progress?('fake_jid')).to eq(false) - end -end diff --git a/spec/support/stub_dot_com_check.rb b/spec/support/stub_dot_com_check.rb new file mode 100644 index 00000000000..6934b33d111 --- /dev/null +++ b/spec/support/stub_dot_com_check.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + %i[saas saas_registration].each do |metadata| + config.before(:context, metadata) do + # Ensure Gitlab.com? returns true during context. + # This is needed for let_it_be which is shared across examples, + # therefore the value must be changed in before_all, + # but RSpec prevent stubbing method calls in before_all, + # therefore we have to resort to temporarily swap url value. + @_original_gitlab_url = Gitlab.config.gitlab['url'] + Gitlab.config.gitlab['url'] = Gitlab::Saas.com_url + end + + config.after(:context, metadata) do + # Swap back original value + Gitlab.config.gitlab['url'] = @_original_gitlab_url + end + end +end diff --git a/spec/support/stub_member_access_level.rb b/spec/support/stub_member_access_level.rb new file mode 100644 index 00000000000..62e932ee1fc --- /dev/null +++ b/spec/support/stub_member_access_level.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module StubMemberAccessLevel + # Stubs access level of a member of +object+. + # + # The following types are supported: + # * `Project` - stubs `project.team.max_member_access(user.id)` + # * `Group` - stubs `group.max_member_access_for_user(user)` + # + # @example + # + # stub_member_access_level(project, maintainer: user) + # project.team.max_member_access(user.id) # => Gitlab::Access::MAINTAINER + # + # stub_member_access_level(group, developer: user) + # group.max_member_access_for_user(user) # => Gitlab::Access::DEVELOPER + # + # stub_member_access_level(project, reporter: user, guest: [guest1, guest2]) + # project.team.max_member_access(user.id) # => Gitlab::Access::REPORTER + # project.team.max_member_access(guests.first.id) # => Gitlab::Access::GUEST + # project.team.max_member_access(guests.last.id) # => Gitlab::Access::GUEST + # + # @param object [Project, Group] Object to be stubbed. + # @param access_levels [Hash<Symbol, User>, Hash<Symbol, [User]>] Map of access level to users + def stub_member_access_level(object, **access_levels) + expectation = case object + when Project + ->(user) { expect(object.team).to receive(:max_member_access).with(user.id) } + when Group + ->(user) { expect(object).to receive(:max_member_access_for_user).with(user) } + else + raise ArgumentError, + "Stubbing member access level unsupported for #{object.inspect} (#{object.class})" + end + + access_levels.each do |access_level, users| + access_level = Gitlab::Access.sym_options_with_owner.fetch(access_level) do + raise ArgumentError, "Invalid access level #{access_level.inspect}" + end + + Array(users).each do |user| + expectation.call(user).at_least(1).times.and_return(access_level) + end + end + end +end diff --git a/spec/support/tmpdir.rb b/spec/support/tmpdir.rb index ea8e26d2878..92126ec1522 100644 --- a/spec/support/tmpdir.rb +++ b/spec/support/tmpdir.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'tmpdir' + module TmpdirHelper def mktmpdir @tmpdir_helper_dirs ||= [] |