diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-18 09:45:46 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-18 09:45:46 +0000 |
commit | a7b3560714b4d9cc4ab32dffcd1f74a284b93580 (patch) | |
tree | 7452bd5c3545c2fa67a28aa013835fb4fa071baf /spec/support | |
parent | ee9173579ae56a3dbfe5afe9f9410c65bb327ca7 (diff) | |
download | gitlab-ce-a7b3560714b4d9cc4ab32dffcd1f74a284b93580.tar.gz |
Add latest changes from gitlab-org/gitlab@14-8-stable-eev14.8.0-rc42
Diffstat (limited to 'spec/support')
70 files changed, 902 insertions, 265 deletions
diff --git a/spec/support/cross_database_modification.rb b/spec/support/cross_database_modification.rb new file mode 100644 index 00000000000..e0d91001c03 --- /dev/null +++ b/spec/support/cross_database_modification.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.after do |example| + [::ApplicationRecord, ::Ci::ApplicationRecord].each do |base_class| + base_class.gitlab_transactions_stack.clear if base_class.respond_to?(:gitlab_transactions_stack) + end + end +end diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb index fb70f82ef87..e3a05f17593 100644 --- a/spec/support/db_cleaner.rb +++ b/spec/support/db_cleaner.rb @@ -73,6 +73,8 @@ module DbCleaner end end + Gitlab::Database::Partitioning.sync_partitions_ignore_db_error + puts "Databases re-creation done in #{Gitlab::Metrics::System.monotonic_time - start}" end diff --git a/spec/support/flaky_tests.rb b/spec/support/flaky_tests.rb index 5ce55c47aab..4df0d23bfc3 100644 --- a/spec/support/flaky_tests.rb +++ b/spec/support/flaky_tests.rb @@ -4,14 +4,14 @@ 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 "$SUITE_FLAKY_RSPEC_REPORT_PATH is empty." if ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'].to_s.empty? - raise "#{ENV['SUITE_FLAKY_RSPEC_REPORT_PATH']} doesn't exist" unless File.exist?(ENV['SUITE_FLAKY_RSPEC_REPORT_PATH']) + raise "#{RspecFlaky::Config.suite_flaky_examples_report_path} doesn't exist" unless File.exist?(RspecFlaky::Config.suite_flaky_examples_report_path) - RspecFlaky::Report.load(ENV['SUITE_FLAKY_RSPEC_REPORT_PATH']).map { |_, flaky_test_data| flaky_test_data.to_h[:example_id] } + 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 [] @@ -29,8 +29,9 @@ RSpec.configure do |config| end config.after(:suite) do - next unless ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH'] + next unless RspecFlaky::Config.skipped_flaky_tests_report_path + next if $skipped_flaky_tests_report.empty? # rubocop:disable Style/GlobalVars - File.write(ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH'], "#{$skipped_flaky_tests_report.join("\n")}\n") # 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/gitlab_experiment.rb b/spec/support/gitlab_experiment.rb index 3d099dc689c..823aab0436e 100644 --- a/spec/support/gitlab_experiment.rb +++ b/spec/support/gitlab_experiment.rb @@ -10,6 +10,16 @@ RSpec.configure do |config| # Disable all caching for experiments in tests. config.before do allow(Gitlab::Experiment::Configuration).to receive(:cache).and_return(nil) + + # Disable all deprecation warnings in the test environment, which can be + # resolved one by one and tracked in: + # + # https://gitlab.com/gitlab-org/gitlab/-/issues/350944 + allow(Gitlab::Experiment::Configuration).to receive(:deprecator).and_wrap_original do |method, version| + method.call(version).tap do |deprecator| + deprecator.silenced = true + end + end end config.before(:each, :experiment) do diff --git a/spec/support/graphql/arguments.rb b/spec/support/graphql/arguments.rb index d8c334c2ca4..20e940030f8 100644 --- a/spec/support/graphql/arguments.rb +++ b/spec/support/graphql/arguments.rb @@ -41,6 +41,7 @@ module Graphql when Hash then "{#{new(value)}}" when Integer, Float, Symbol then value.to_s when String then "\"#{value.gsub(/"/, '\\"')}\"" + when Time, Date then "\"#{value.iso8601}\"" when nil then 'null' when true then 'true' when false then 'false' diff --git a/spec/support/helpers/fake_blob_helpers.rb b/spec/support/helpers/fake_blob_helpers.rb index 6c8866deac4..47fb9a345a3 100644 --- a/spec/support/helpers/fake_blob_helpers.rb +++ b/spec/support/helpers/fake_blob_helpers.rb @@ -4,13 +4,14 @@ module FakeBlobHelpers class FakeBlob include BlobLike - attr_reader :path, :size, :data, :lfs_oid, :lfs_size + attr_reader :path, :size, :data, :lfs_oid, :lfs_size, :mode - def initialize(path: 'file.txt', size: 1.kilobyte, data: 'foo', binary: false, lfs: nil) + def initialize(path: 'file.txt', size: 1.kilobyte, data: 'foo', binary: false, lfs: nil, mode: nil) @path = path @size = size @data = data @binary = binary + @mode = mode @lfs_pointer = lfs.present? if @lfs_pointer diff --git a/spec/support/helpers/features/invite_members_modal_helper.rb b/spec/support/helpers/features/invite_members_modal_helper.rb index 11040562b49..2a4f78ca57f 100644 --- a/spec/support/helpers/features/invite_members_modal_helper.rb +++ b/spec/support/helpers/features/invite_members_modal_helper.rb @@ -8,7 +8,7 @@ module Spec def invite_member(name, role: 'Guest', expires_at: nil) click_on 'Invite members' - page.within '[data-testid="invite-members-modal"]' do + page.within '[data-testid="invite-modal"]' do find('[data-testid="members-token-select-input"]').set(name) wait_for_requests diff --git a/spec/support/helpers/features/iteration_helpers.rb b/spec/support/helpers/features/iteration_helpers.rb new file mode 100644 index 00000000000..8e1d252f55f --- /dev/null +++ b/spec/support/helpers/features/iteration_helpers.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module IterationHelpers + def iteration_period(iteration) + "#{iteration.start_date.to_s(:medium)} - #{iteration.due_date.to_s(:medium)}" + end +end diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb index 905c439f4d9..a4ee618457d 100644 --- a/spec/support/helpers/gitaly_setup.rb +++ b/spec/support/helpers/gitaly_setup.rb @@ -120,7 +120,7 @@ module GitalySetup end def build_gitaly - run_command(%w[make all git], env: env.merge('GIT_VERSION' => nil)) + run_command(%w[make all WITH_BUNDLED_GIT=YesPlease], env: env.merge('GIT_VERSION' => nil)) end def start_gitaly(toml = nil) @@ -327,8 +327,8 @@ module GitalySetup message += "\nCheck log/gitaly-test.log for errors.\n" - unless ci? - message += "\nIf binaries are missing, try running `make -C tmp/tests/gitaly build git.`\n" + unless ENV['CI'] + message += "\nIf binaries are missing, try running `make -C tmp/tests/gitaly all WITH_BUNDLED_GIT=YesPlease`.\n" message += "\nOtherwise, try running `rm -rf #{tmp_tests_gitaly_dir}`." end @@ -336,7 +336,7 @@ module GitalySetup end def git_binary - File.join(tmp_tests_gitaly_dir, "_build", "deps", "git", "install", "bin", "git") + File.join(tmp_tests_gitaly_dir, "_build", "bin", "gitaly-git") end def gitaly_binary diff --git a/spec/support/helpers/import_spec_helper.rb b/spec/support/helpers/import_spec_helper.rb index d8fb2ba08af..26b78acbc44 100644 --- a/spec/support/helpers/import_spec_helper.rb +++ b/spec/support/helpers/import_spec_helper.rb @@ -25,7 +25,7 @@ module ImportSpecHelper end def stub_omniauth_provider(name) - provider = OpenStruct.new( + provider = ActiveSupport::InheritableOptions.new( name: name, app_id: 'asd123', app_secret: 'asd123' diff --git a/spec/support/helpers/key_generator_helper.rb b/spec/support/helpers/key_generator_helper.rb deleted file mode 100644 index 58bde80a31f..00000000000 --- a/spec/support/helpers/key_generator_helper.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module Spec - module Support - module Helpers - class KeyGeneratorHelper - # The components in a openssh .pub / known_host RSA public key. - RSA_COMPONENTS = ['ssh-rsa', :e, :n].freeze - - attr_reader :size - - def initialize(size = 2048) - @size = size - end - - def generate - key = OpenSSL::PKey::RSA.generate(size) - components = RSA_COMPONENTS.map do |component| - key.respond_to?(component) ? encode_mpi(key.public_send(component)) : component - end - - # Ruby tries to be helpful and adds new lines every 60 bytes :( - 'ssh-rsa ' + [pack_pubkey_components(components)].pack('m').delete("\n") - end - - private - - # Encodes an openssh-mpi-encoded integer. - def encode_mpi(n) # rubocop:disable Naming/UncommunicativeMethodParamName - chars = [] - n = n.to_i - chars << (n & 0xff) && n >>= 8 while n != 0 - chars << 0 if chars.empty? || chars.last >= 0x80 - chars.reverse.pack('C*') - end - - # Packs string components into an openssh-encoded pubkey. - def pack_pubkey_components(strings) - (strings.flat_map { |s| [s.length].pack('N') }).zip(strings).join - end - end - end - end -end diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index 4e0e8dd96ee..c0734bae375 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -178,7 +178,7 @@ module LoginHelpers end def mock_saml_config - OpenStruct.new(name: 'saml', label: 'saml', args: { + ActiveSupport::InheritableOptions.new(name: 'saml', label: 'saml', args: { assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback', idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52', idp_sso_target_url: 'https://idp.example.com/sso/saml', diff --git a/spec/support/helpers/memory_usage_helper.rb b/spec/support/helpers/memory_usage_helper.rb deleted file mode 100644 index 02d1935921f..00000000000 --- a/spec/support/helpers/memory_usage_helper.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -module MemoryUsageHelper - extend ActiveSupport::Concern - - def gather_memory_data(csv_path) - write_csv_entry(csv_path, - { - example_group_path: TestEnv.topmost_example_group[:location], - example_group_description: TestEnv.topmost_example_group[:description], - time: Time.current, - job_name: ENV['CI_JOB_NAME'] - }.merge(get_memory_usage)) - end - - def write_csv_entry(path, entry) - CSV.open(path, "a", headers: entry.keys, write_headers: !File.exist?(path)) do |file| - file << entry.values - end - end - - def get_memory_usage - output, status = Gitlab::Popen.popen(%w(free -m)) - abort "`free -m` return code is #{status}: #{output}" unless status == 0 - - result = output.split("\n")[1].split(" ")[1..] - attrs = %i(m_total m_used m_free m_shared m_buffers_cache m_available).freeze - - attrs.zip(result).to_h - end - - included do |config| - config.after(:all) do - gather_memory_data(ENV['MEMORY_TEST_PATH']) if ENV['MEMORY_TEST_PATH'] - end - end -end diff --git a/spec/support/helpers/merge_request_diff_helpers.rb b/spec/support/helpers/merge_request_diff_helpers.rb index 30afde7efed..7515c789add 100644 --- a/spec/support/helpers/merge_request_diff_helpers.rb +++ b/spec/support/helpers/merge_request_diff_helpers.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true module MergeRequestDiffHelpers + PageEndReached = Class.new(StandardError) + def click_diff_line(line_holder, diff_side = nil) line = get_line_components(line_holder, diff_side) + scroll_to_elements_bottom(line_holder) line_holder.hover line[:num].find('.js-add-diff-note-button').click end @@ -27,4 +30,55 @@ module MergeRequestDiffHelpers line_holder.find('.diff-line-num', match: :first) { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] } end + + def has_reached_page_end + evaluate_script("(window.innerHeight + window.scrollY) >= document.body.offsetHeight") + end + + def scroll_to_elements_bottom(element) + evaluate_script("(function(el) { + window.scrollBy(0, el.getBoundingClientRect().bottom - window.innerHeight); + })(arguments[0]);", element.native) + end + + # we're not using Capybara's .obscured here because it also checks if the element is clickable + def within_viewport?(element) + evaluate_script("(function(el) { + var rect = el.getBoundingClientRect(); + return ( + rect.bottom >= 0 && + rect.right >= 0 && + rect.top <= (window.innerHeight || document.documentElement.clientHeight) && + rect.left <= (window.innerWidth || document.documentElement.clientWidth) + ); + })(arguments[0]);", element.native) + end + + def find_within_viewport(selector, **options) + begin + element = find(selector, **options, wait: 2) + rescue Capybara::ElementNotFound + return + end + return element if within_viewport?(element) + + nil + end + + def find_by_scrolling(selector, **options) + element = find_within_viewport(selector, **options) + return element if element + + page.execute_script "window.scrollTo(0,0)" + until element + + if has_reached_page_end + raise PageEndReached, "Failed to find any elements matching a selector '#{selector}' by scrolling. Page end reached." + end + + page.execute_script "window.scrollBy(0,window.innerHeight/1.5)" + element = find_within_viewport(selector, **options) + end + element + end end diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb index a4322618cd3..fa2705a64fa 100644 --- a/spec/support/helpers/note_interaction_helpers.rb +++ b/spec/support/helpers/note_interaction_helpers.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true module NoteInteractionHelpers + include MergeRequestDiffHelpers + def open_more_actions_dropdown(note) - note_element = find("#note_#{note.id}") + 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) diff --git a/spec/support/helpers/rack_attack_spec_helpers.rb b/spec/support/helpers/rack_attack_spec_helpers.rb index d50a6382a40..c82a87dc58e 100644 --- a/spec/support/helpers/rack_attack_spec_helpers.rb +++ b/spec/support/helpers/rack_attack_spec_helpers.rb @@ -26,14 +26,14 @@ module RackAttackSpecHelpers { 'AUTHORIZATION' => "Basic #{encoded_login}" } end - def expect_rejection(&block) + def expect_rejection(name = nil, &block) yield expect(response).to have_gitlab_http_status(:too_many_requests) expect(response.headers.to_h).to include( 'RateLimit-Limit' => a_string_matching(/^\d+$/), - 'RateLimit-Name' => a_string_matching(/^throttle_.*$/), + 'RateLimit-Name' => name || a_string_matching(/^throttle_.*$/), 'RateLimit-Observed' => a_string_matching(/^\d+$/), 'RateLimit-Remaining' => a_string_matching(/^\d+$/), 'Retry-After' => a_string_matching(/^\d+$/) diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb index bbba58d60d6..f275be39dc4 100644 --- a/spec/support/helpers/repo_helpers.rb +++ b/spec/support/helpers/repo_helpers.rb @@ -129,7 +129,7 @@ eos commit_message: 'Add new content') Files::CreateService.new( project, - project.owner, + project.first_owner, commit_message: commit_message, start_branch: start_branch, branch_name: branch_name, diff --git a/spec/support/helpers/session_helpers.rb b/spec/support/helpers/session_helpers.rb index 236585296e5..394a401afca 100644 --- a/spec/support/helpers/session_helpers.rb +++ b/spec/support/helpers/session_helpers.rb @@ -1,6 +1,22 @@ # frozen_string_literal: true module SessionHelpers + # Stub a session in Redis, for use in request specs where we can't mock the session directly. + # This also needs the :clean_gitlab_redis_sessions tag on the spec. + def stub_session(session_hash) + unless RSpec.current_example.metadata[:clean_gitlab_redis_sessions] + raise 'Add :clean_gitlab_redis_sessions to your spec!' + end + + session_id = Rack::Session::SessionId.new(SecureRandom.hex) + + Gitlab::Redis::Sessions.with do |redis| + redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) + end + + cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id + end + def expect_single_session_with_authenticated_ttl expect_single_session_with_expiration(Settings.gitlab['session_expire_delay'] * 60) end diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb index c3459f7bc81..749554f7786 100644 --- a/spec/support/helpers/stub_gitlab_calls.rb +++ b/spec/support/helpers/stub_gitlab_calls.rb @@ -51,6 +51,8 @@ module StubGitlabCalls allow(Gitlab.config.registry).to receive_messages(registry_settings) allow(Auth::ContainerRegistryAuthenticationService) .to receive(:full_access_token).and_return('token') + allow(Auth::ContainerRegistryAuthenticationService) + .to receive(:import_access_token).and_return('token') end def stub_container_registry_tags(repository: :any, tags: [], with_manifest: false) diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 5c3ca92c4d0..18c25f4b770 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -371,17 +371,6 @@ module TestEnv FileUtils.rm_rf(path) end - def current_example_group - Thread.current[:current_example_group] - end - - # looking for a top-level `describe` - def topmost_example_group - example_group = current_example_group - example_group = example_group[:parent_example_group] until example_group[:parent_example_group].nil? - example_group - end - def seed_db Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.import end diff --git a/spec/support/import_export/import_export.yml b/spec/support/import_export/import_export.yml index 116bc8d0b9c..fa10e1335c5 100644 --- a/spec/support/import_export/import_export.yml +++ b/spec/support/import_export/import_export.yml @@ -28,4 +28,4 @@ excluded_attributes: - :iid project: - :id - - :created_at
\ No newline at end of file + - :created_at diff --git a/spec/support/matchers/event_store.rb b/spec/support/matchers/event_store.rb new file mode 100644 index 00000000000..96a71ae3c22 --- /dev/null +++ b/spec/support/matchers/event_store.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +RSpec::Matchers.define :event_type do |event_class| + match do |actual| + actual.instance_of?(event_class) && + actual.data == @expected_data + end + + chain :containing do |expected_data| + @expected_data = expected_data + end +end diff --git a/spec/support/matchers/schema_matcher.rb b/spec/support/matchers/schema_matcher.rb index 5e08e96f4e1..d2f32b60464 100644 --- a/spec/support/matchers/schema_matcher.rb +++ b/spec/support/matchers/schema_matcher.rb @@ -36,12 +36,38 @@ end RSpec::Matchers.define :match_response_schema do |schema, dir: nil, **options| match do |response| - schema_path = Pathname.new(SchemaPath.expand(schema, dir)) - validator = SchemaPath.validator(schema_path) + @schema_path = Pathname.new(SchemaPath.expand(schema, dir)) + validator = SchemaPath.validator(@schema_path) - data = Gitlab::Json.parse(response.body) + @data = Gitlab::Json.parse(response.body) - validator.valid?(data) + @schema_errors = validator.validate(@data) + @schema_errors.none? + end + + failure_message do |actual| + message = [] + + message << <<~MESSAGE + expected JSON response to match schema #{@schema_path.inspect}. + + JSON input: #{Gitlab::Json.pretty_generate(@data).indent(2)} + + Schema errors: + MESSAGE + + @schema_errors.each do |error| + property_name, actual_value = error.values_at('data_pointer', 'data') + property_name = 'root' if property_name.empty? + + message << <<~MESSAGE + Property: #{property_name} + Actual value: #{Gitlab::Json.pretty_generate(actual_value).indent(2)} + Error: #{JSONSchemer::Errors.pretty(error)} + MESSAGE + end + + message.join("\n") end end diff --git a/spec/support/shared_contexts/container_repositories_shared_context.rb b/spec/support/shared_contexts/container_repositories_shared_context.rb new file mode 100644 index 00000000000..7f61631dce0 --- /dev/null +++ b/spec/support/shared_contexts/container_repositories_shared_context.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +RSpec.shared_context 'importable repositories' do + let_it_be(:project) { create(:project) } + let_it_be(:valid_container_repository) { create(:container_repository, project: project, created_at: 2.days.ago) } + let_it_be(:valid_container_repository2) { create(:container_repository, project: project, created_at: 1.year.ago) } + let_it_be(:importing_container_repository) { create(:container_repository, :importing, project: project, created_at: 2.days.ago) } + let_it_be(:new_container_repository) { create(:container_repository, project: project) } + + let_it_be(:denied_group) { create(:group) } + let_it_be(:denied_project) { create(:project, group: denied_group) } + let_it_be(:denied_container_repository) { create(:container_repository, project: denied_project, created_at: 2.days.ago) } + + before do + stub_application_setting(container_registry_import_created_before: 1.day.ago) + stub_feature_flags( + container_registry_phase_2_deny_list: false, + container_registry_migration_limit_gitlab_org: false + ) + + Feature::FlipperGate.create!( + feature_key: 'container_registry_phase_2_deny_list', + key: 'actors', + value: "Group:#{denied_group.id}" + ) + end +end 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 3d2b0433b21..3ea6658c0c1 100644 --- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb +++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb @@ -18,6 +18,8 @@ Integration.available_integration_names.each do |integration| 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)/ 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 54bb9fd108e..fadd46a7e12 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 @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_context 'project service Jira context' do +RSpec.shared_context 'project integration Jira context' do let(:url) { 'https://jira.example.com' } let(:test_url) { 'https://jira.example.com/rest/api/2/serverInfo' } 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 6414a4d1eb3..bac7bd00f46 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,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_context 'project service activation' do +RSpec.shared_context 'project integration activation' do include_context 'integration activation' let_it_be(:project) { create(:project) } diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb index 6b15eadc1c1..3479dac0077 100644 --- a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb +++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true RSpec.shared_context 'GroupProjectsFinder context' do - let_it_be(:group) { create(:group) } + let_it_be(:root_group) { create(:group) } + let_it_be(:group) { create(:group, parent: root_group) } let_it_be(:subgroup) { create(:group, parent: group) } let_it_be(:current_user) { create(:user) } let(:params) { {} } @@ -16,6 +17,9 @@ RSpec.shared_context 'GroupProjectsFinder context' do let_it_be(:shared_project_3) { create(:project, :internal, path: '5', name: 'c') } let_it_be(:subgroup_project) { create(:project, :public, path: '6', group: subgroup, name: 'b') } let_it_be(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup, name: 'a') } + let_it_be(:root_group_public_project) { create(:project, :public, path: '8', group: root_group, name: 'root-public-project') } + let_it_be(:root_group_private_project) { create(:project, :private, path: '9', group: root_group, name: 'root-private-project') } + let_it_be(:root_group_private_project_2) { create(:project, :private, path: '10', group: root_group, name: 'root-private-project-2') } before do shared_project_1.project_group_links.create!(group_access: Gitlab::Access::MAINTAINER, group: group) diff --git a/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb b/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb index 9ac3d4a04f9..13e7ecf2669 100644 --- a/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb +++ b/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb @@ -11,7 +11,7 @@ RSpec.shared_context 'package details setup' do let(:package_files) { all_graphql_fields_for('PackageFile') } let(:dependency_links) { all_graphql_fields_for('PackageDependencyLink') } let(:pipelines) { all_graphql_fields_for('Pipeline', max_depth: 1) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:package_details) { graphql_data_at(:package) } let(:metadata_response) { graphql_data_at(:package, :metadata) } let(:first_file) { package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } } diff --git a/spec/support/shared_contexts/lib/container_registry/client_shared_context.rb b/spec/support/shared_contexts/lib/container_registry/client_shared_context.rb new file mode 100644 index 00000000000..a87a3247b95 --- /dev/null +++ b/spec/support/shared_contexts/lib/container_registry/client_shared_context.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +RSpec.shared_context 'container registry client' do + let(:token) { '12345' } + let(:options) { { token: token } } + let(:registry_api_url) { 'http://container-registry' } + let(:client) { described_class.new(registry_api_url, options) } + let(:push_blob_headers) do + { + 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json', + 'Authorization' => "bearer #{token}", + 'Content-Type' => 'application/octet-stream', + 'User-Agent' => "GitLab/#{Gitlab::VERSION}" + } + end + + let(:headers_with_accept_types) do + { + 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json', + 'Authorization' => "bearer #{token}", + 'User-Agent' => "GitLab/#{Gitlab::VERSION}" + } + end + + let(:expected_faraday_headers) { { user_agent: "GitLab/#{Gitlab::VERSION}" } } + let(:expected_faraday_request_options) { Gitlab::HTTP::DEFAULT_TIMEOUT_OPTIONS } +end diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb index 27967850389..576a8aa44fa 100644 --- a/spec/support/shared_contexts/navbar_structure_context.rb +++ b/spec/support/shared_contexts/navbar_structure_context.rb @@ -22,6 +22,7 @@ RSpec.shared_context 'project navbar structure' do nav_sub_items: [ _('Activity'), _('Labels'), + _('Planning hierarchy'), _('Members') ] }, 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 0dfd76de79c..76db2bd82f1 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -47,6 +47,7 @@ RSpec.shared_context 'GroupPolicy context' do create_custom_emoji create_package create_package_settings + read_cluster ] end @@ -54,7 +55,7 @@ RSpec.shared_context 'GroupPolicy context' do %i[ destroy_package create_projects - read_cluster create_cluster update_cluster admin_cluster add_cluster + create_cluster update_cluster admin_cluster add_cluster ] 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 c39252cef13..3641edc845a 100644 --- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb @@ -17,7 +17,7 @@ RSpec.shared_context 'ProjectPolicy context' do %i[ award_emoji create_issue create_merge_request_in create_note create_project read_issue_board read_issue read_issue_iid read_issue_link - read_label read_issue_board_list read_milestone read_note read_project + read_label read_planning_hierarchy read_issue_board_list read_milestone read_note read_project read_project_for_iids read_project_member read_release read_snippet read_wiki upload_file ] 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 21be989d697..e26b8cd8b37 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 @@ -3,7 +3,7 @@ RSpec.shared_context 'container repository delete tags service shared context' do let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, :private) } - let_it_be(:repository) { create(:container_repository, :root, project: project) } + let_it_be_with_reload(:repository) { create(:container_repository, :root, project: project) } let(:params) { { tags: tags } } diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb index 62b35923bcd..5ed8dc7ce98 100644 --- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb @@ -205,10 +205,30 @@ RSpec.shared_examples 'handle uploads' do allow_any_instance_of(FileUploader).to receive(:image?).and_return(true) end - it "responds with status 200" do - show_upload + context "enforce_auth_checks_on_uploads feature flag" do + context "with flag enabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: true) + end - expect(response).to have_gitlab_http_status(:ok) + it "responds with status 302" do + show_upload + + expect(response).to have_gitlab_http_status(:redirect) + end + end + + context "with flag disabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end end end @@ -276,10 +296,30 @@ RSpec.shared_examples 'handle uploads' do allow_any_instance_of(FileUploader).to receive(:image?).and_return(true) end - it "responds with status 200" do - show_upload + context "enforce_auth_checks_on_uploads feature flag" do + context "with flag enabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: true) + end + + it "responds with status 404" do + show_upload + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context "with flag disabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload - expect(response).to have_gitlab_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) + end + end end end diff --git a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb index e0a032b1a43..8a07e52019c 100644 --- a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb +++ b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb @@ -2,7 +2,7 @@ RSpec.shared_examples 'comment on merge request file' do it 'adds a comment' do - click_diff_line(find("[id='#{sample_commit.line_code}']")) + click_diff_line(find_by_scrolling("[id='#{sample_commit.line_code}']")) page.within('.js-discussion-note-form') do fill_in(:note_note, with: 'Line is wrong') 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 1c816ee4b0a..456175e7113 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 @@ -62,7 +62,7 @@ RSpec.shared_examples 'a creatable merge request' do end it 'updates the branches when selecting a new target project', :js do - target_project_member = target_project.owner + target_project_member = target_project.first_owner ::Branches::CreateService.new(target_project, target_project_member) .execute('a-brand-new-branch-to-test', 'master') diff --git a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb index a9dac7a391f..281a70e46c4 100644 --- a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb +++ b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb @@ -54,7 +54,10 @@ RSpec.shared_examples 'labels sidebar widget' do end fill_in 'Search', with: 'Devel' - sleep 1 + expect(page).to have_css('.labels-fetch-loading') + wait_for_all_requests + + expect(page).to have_css('[data-testid="dropdown-content"] .gl-new-dropdown-item') expect(page.all(:css, '[data-testid="dropdown-content"] .gl-new-dropdown-item').length).to eq(1) find_field('Search').native.send_keys(:enter) 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 52451839281..c63faace6b2 100644 --- a/spec/support/shared_examples/features/variable_list_shared_examples.rb +++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb @@ -166,7 +166,7 @@ RSpec.shared_examples 'variable list' do wait_for_requests expect(find('.flash-container')).to be_present - expect(find('.flash-text').text).to have_content('Variables key (key) has already been taken') + expect(find('[data-testid="alert-danger"]').text).to have_content('Variables key (key) has already been taken') end it 'prevents a variable to be added if no values are provided when a variable is set to masked' do 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 1a981f42086..2285d9a17e2 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 @@ -85,7 +85,9 @@ RSpec.shared_examples 'User previews wiki changes' do end it 'renders content with CommonMark' do - fill_in :wiki_content, with: "1. one\n - sublist\n" + # 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" # the above generates two separate lists (not embedded) in CommonMark diff --git a/spec/support/shared_examples/graphql/boards_shared_examples.rb b/spec/support/shared_examples/graphql/boards_shared_examples.rb new file mode 100644 index 00000000000..e8a4c17fb92 --- /dev/null +++ b/spec/support/shared_examples/graphql/boards_shared_examples.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'querying a GraphQL type recent boards' do + describe 'Get list of recently visited boards' do + let(:boards_data) { graphql_data[board_type]['recentIssueBoards']['nodes'] } + + context 'when the request is correct' do + before do + visit_board + parent.add_reporter(user) + post_graphql(query, current_user: user) + end + + it_behaves_like 'a working graphql query' + + it 'returns recent boards for user successfully' do + expect(response).to have_gitlab_http_status(:ok) + expect(graphql_errors).to be_nil + expect(boards_data.size).to eq(1) + expect(boards_data[0]['name']).to eq(board.name) + end + end + + context 'when requests has errors' do + context 'when there are no recently visited boards' do + it 'returns empty result' do + post_graphql(query, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to be_nil + expect(boards_data).to be_empty + end + end + end + end + + def query(query_params: {}, full_path: parent.full_path) + board_nodes = <<~NODE + nodes { + name + } + NODE + + graphql_query_for( + board_type.to_sym, + { full_path: full_path }, + query_graphql_field(:recent_issue_boards, query_params, board_nodes) + ) + end + + def visit_board + if board_type == 'group' + create(:board_group_recent_visit, group: parent, board: board, user: user) + else + create(:board_project_recent_visit, project: parent, board: board, user: user) + end + end +end diff --git a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb index 011a2157f24..b17e59f0797 100644 --- a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb @@ -16,17 +16,4 @@ RSpec.shared_examples 'a mutation which can mutate a spammable' do subject end end - - describe "#spam_action_response_fields" do - it 'resolves with spam action fields' do - subject - - # NOTE: We do not need to assert on the specific values of spam action fields here, we only need - # to verify that #spam_action_response_fields was invoked and that the fields are present in the - # response. The specific behavior of #spam_action_response_fields is covered in the - # HasSpamActionResponseFields unit tests. - expect(mutation_response.keys) - .to include('spam', 'spamLogId', 'needsCaptchaResponse', 'captchaSiteKey') - end - end end diff --git a/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb index 2bb3d807aa7..14b2663a72c 100644 --- a/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb @@ -18,7 +18,7 @@ RSpec.shared_examples_for 'graphql mutations security ci configuration' do ServiceResponse.success(payload: { branch: branch, success_path: success_path }) end - let(:error) { "An error occured!" } + let(:error) { "An error occurred!" } let(:service_error_response) do ServiceResponse.error(message: error) diff --git a/spec/support/shared_examples/integrations/integration_settings_form.rb b/spec/support/shared_examples/integrations/integration_settings_form.rb new file mode 100644 index 00000000000..d0bb40e43ee --- /dev/null +++ b/spec/support/shared_examples/integrations/integration_settings_form.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'integration settings form' do + include IntegrationsHelper + # Note: these specs don't validate channel fields + # which are present on a few integrations + it 'displays all the integrations' do + aggregate_failures do + integrations.each do |integration| + navigate_to_integration(integration) + + page.within('form.integration-settings-form') do + expect(page).to have_field('Active', type: 'checkbox', wait: 0), + "#{integration.title} active field not present" + + fields = parse_json(fields_for_integration(integration)) + fields.each do |field| + field_name = field[:name] + expect(page).to have_field(field[:title], wait: 0), + "#{integration.title} field #{field_name} not present" + end + + events = parse_json(trigger_events_for_integration(integration)) + events.each do |trigger| + # normalizing the title because capybara location is case sensitive + title = normalize_title trigger[:title], integration + + expect(page).to have_field(title, type: 'checkbox', wait: 0), + "#{integration.title} field #{title} checkbox not present" + end + end + end + end + end + + private + + def normalize_title(title, integration) + return 'Merge request' if integration.is_a?(Integrations::Jira) && title == 'merge_request' + + title.titlecase + end + + def parse_json(json) + Gitlab::Json.parse(json, symbolize_names: true) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb index 5baa6478225..fdca326dbea 100644 --- a/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true RSpec.shared_examples 'tracks assignment and records the subject' do |experiment, subject_type| + before do + stub_experiments(experiment => true) + end + it 'tracks the assignment', :experiment do expect(experiment(experiment)) .to track(:assignment) @@ -11,9 +15,7 @@ RSpec.shared_examples 'tracks assignment and records the subject' do |experiment end it 'records the subject' do - stub_experiments(experiment => :candidate) - - expect(Experiment).to receive(:add_subject).with(experiment.to_s, variant: :experimental, subject: subject) + expect(Experiment).to receive(:add_subject).with(experiment.to_s, variant: anything, subject: subject) action end diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/code_review_extension_request_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/code_review_extension_request_examples.rb new file mode 100644 index 00000000000..6221366ab51 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/code_review_extension_request_examples.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'a request from an extension' do |event| + before do + stub_application_setting(usage_ping_enabled: true) + end + + def count_unique(date_from:, date_to:) + Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to) + end + + def track_action(params) + described_class.track_api_request_when_trackable(**params) + end + + it 'tracks when the user agent is matching' do + aggregate_failures do + expect(track_action(user: user1, **user_agent)).to be_truthy + expect(track_action(user: user1, **user_agent)).to be_truthy + expect(track_action(user: user2, **user_agent)).to be_truthy + + expect(count_unique(date_from: time - 1.week, date_to: time + 1.week)).to eq(2) + end + end + + it 'does not track when the user agent is not matching' do + aggregate_failures do + user_agent = { user_agent: 'normal_user_agent' } + + expect(track_action(user: user1, **user_agent)).to be_falsey + expect(track_action(user: user1, **user_agent)).to be_falsey + expect(track_action(user: user2, **user_agent)).to be_falsey + + expect(count_unique(date_from: time - 1.week, date_to: time + 1.week)).to eq(0) + end + end + + it 'does not track if user agent is not present' do + expect(track_action(user: nil, user_agent: nil)).to be_nil + end + + it 'does not track if user is not present' do + expect(track_action(user: nil, **user_agent)).to be_nil + end +end diff --git a/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb index d3fd28727b5..b4c438771ce 100644 --- a/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb +++ b/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.shared_examples 'ZenTao menu with CE version' do let(:project) { create(:project, has_external_issue_tracker: true) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } let(:zentao_integration) { create(:zentao_integration, project: project) } diff --git a/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb b/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb index 42eec74e64f..5f59d43ad19 100644 --- a/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb +++ b/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb @@ -7,6 +7,12 @@ RSpec.shared_examples 'it has loose foreign keys' do let(:fully_qualified_table_name) { "#{connection.current_schema}.#{table_name}" } let(:deleted_records) { LooseForeignKeys::DeletedRecord.where(fully_qualified_table_name: fully_qualified_table_name) } + around do |example| + LooseForeignKeys::DeletedRecord.using_connection(connection) do + example.run + end + end + it 'has at least one loose foreign key definition' do definitions = Gitlab::Database::LooseForeignKeys.definitions_by_table[table_name] expect(definitions.size).to be > 0 @@ -69,7 +75,9 @@ RSpec.shared_examples 'cleanup by a loose foreign key' do expect(find_model).to be_present - LooseForeignKeys::ProcessDeletedRecordsService.new(connection: model.connection).execute + LooseForeignKeys::DeletedRecord.using_connection(parent.connection) do + LooseForeignKeys::ProcessDeletedRecordsService.new(connection: parent.connection).execute + end if foreign_key_definition.on_delete.eql?(:async_delete) expect(find_model).not_to be_present diff --git a/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb b/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb index d823e7ac221..8ff30021d6e 100644 --- a/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb +++ b/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb @@ -178,4 +178,22 @@ RSpec.shared_examples 'StageEventModel' do end end end + + describe '#total_time' do + it 'calcualtes total time from the start_event_timestamp and end_event_timestamp columns' do + model = described_class.new(start_event_timestamp: Time.new(2022, 1, 1, 12, 5, 0), end_event_timestamp: Time.new(2022, 1, 1, 12, 6, 30)) + + expect(model.total_time).to eq(90) + end + + context 'when total time is calculated in SQL as an extra column' do + it 'returns the SQL calculated time' do + create(stage_event_factory) # rubocop:disable Rails/SaveBang + + model = described_class.select('*, 5 AS total_time').first + + expect(model.total_time).to eq(5) + end + end + 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 5b4b8c8fcc1..f7e09cfca62 100644 --- a/spec/support/shared_examples/models/member_shared_examples.rb +++ b/spec/support/shared_examples/models/member_shared_examples.rb @@ -301,8 +301,9 @@ RSpec.shared_examples_for "member creation" do end context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do + let(:task_project) { source.is_a?(Group) ? create(:project, group: source) : source } + it 'creates a member_task with the correct attributes', :aggregate_failures do - task_project = source.is_a?(Group) ? create(:project, group: source) : source described_class.new(source, user, :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id).execute member = source.members.last @@ -310,6 +311,43 @@ RSpec.shared_examples_for "member creation" do expect(member.tasks_to_be_done).to match_array([:ci, :code]) expect(member.member_task.project).to eq(task_project) end + + context 'with an already existing member' do + before do + source.add_user(user, :developer) + end + + it 'does not update tasks to be done if tasks already exist', :aggregate_failures do + member = source.members.find_by(user_id: user.id) + create(:member_task, member: member, project: task_project, tasks_to_be_done: %w(code ci)) + + expect do + described_class.new(source, + user, + :developer, + tasks_to_be_done: %w(issues), + tasks_project_id: task_project.id).execute + end.not_to change(MemberTask, :count) + + member.reset + expect(member.tasks_to_be_done).to match_array([:code, :ci]) + expect(member.member_task.project).to eq(task_project) + end + + it 'adds tasks to be done if they do not exist', :aggregate_failures do + expect do + described_class.new(source, + user, + :developer, + tasks_to_be_done: %w(issues), + tasks_project_id: task_project.id).execute + end.to change(MemberTask, :count).by(1) + + member = source.members.find_by(user_id: user.id) + expect(member.tasks_to_be_done).to match_array([:issues]) + expect(member.member_task.project).to eq(task_project) + end + end end end end @@ -393,14 +431,52 @@ RSpec.shared_examples_for "bulk member creation" do end context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do + let(:task_project) { source.is_a?(Group) ? create(:project, group: source) : source } + it 'creates a member_task with the correct attributes', :aggregate_failures do - task_project = source.is_a?(Group) ? create(:project, group: source) : source members = described_class.add_users(source, [user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id) member = members.last expect(member.tasks_to_be_done).to match_array([:ci, :code]) expect(member.member_task.project).to eq(task_project) end + + context 'with an already existing member' do + before do + source.add_user(user1, :developer) + end + + it 'does not update tasks to be done if tasks already exist', :aggregate_failures do + member = source.members.find_by(user_id: user1.id) + create(:member_task, member: member, project: task_project, tasks_to_be_done: %w(code ci)) + + expect do + described_class.add_users(source, + [user1.id], + :developer, + tasks_to_be_done: %w(issues), + tasks_project_id: task_project.id) + end.not_to change(MemberTask, :count) + + member.reset + expect(member.tasks_to_be_done).to match_array([:code, :ci]) + expect(member.member_task.project).to eq(task_project) + end + + it 'adds tasks to be done if they do not exist', :aggregate_failures do + expect do + described_class.add_users(source, + [user1.id], + :developer, + tasks_to_be_done: %w(issues), + tasks_project_id: task_project.id) + end.to change(MemberTask, :count).by(1) + + member = source.members.find_by(user_id: user1.id) + expect(member.tasks_to_be_done).to match_array([:issues]) + expect(member.member_task.project).to eq(task_project) + end + end end end end diff --git a/spec/support/shared_examples/models/note_access_check_shared_examples.rb b/spec/support/shared_examples/models/note_access_check_shared_examples.rb index 44edafe9091..0c9992b832f 100644 --- a/spec/support/shared_examples/models/note_access_check_shared_examples.rb +++ b/spec/support/shared_examples/models/note_access_check_shared_examples.rb @@ -3,7 +3,7 @@ RSpec.shared_examples 'users with note access' do it 'returns true' do users.each do |user| - expect(note.system_note_with_references_visible_for?(user)).to be_truthy + expect(note.system_note_visible_for?(user)).to be_truthy expect(note.readable_by?(user)).to be_truthy end end @@ -12,7 +12,7 @@ end RSpec.shared_examples 'users without note access' do it 'returns false' do users.each do |user| - expect(note.system_note_with_references_visible_for?(user)).to be_falsy + expect(note.system_note_visible_for?(user)).to be_falsy expect(note.readable_by?(user)).to be_falsy 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 3f8c3b8960b..6b0ae589efb 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 @@ -235,18 +235,6 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| it 'does not return them' do expect(subject.to_a).not_to include(package_file_pending_destruction) end - - context 'with packages_installable_package_files disabled' do - before do - stub_feature_flags(packages_installable_package_files: false) - end - - it 'returns them' do - subject - - expect(subject.to_a).to include(package_file_pending_destruction) - end - end end end end diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb index 06326ffac97..ad0bbc0aeff 100644 --- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb +++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb @@ -115,14 +115,14 @@ RSpec.shared_examples 'UpdateProjectStatistics' do |with_counter_attribute| expect(ProjectStatistics) .not_to receive(:increment_statistic) - expect(Projects::DestroyService.new(project, project.owner).execute).to eq(true) + expect(Projects::DestroyService.new(project, project.first_owner).execute).to eq(true) end it 'does not schedule a namespace statistics worker' do expect(Namespaces::ScheduleAggregationWorker) .not_to receive(:perform_async) - expect(Projects::DestroyService.new(project, project.owner).execute).to eq(true) + expect(Projects::DestroyService.new(project, project.first_owner).execute).to eq(true) end end end diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb index b43b7946e69..bcb5464ed5b 100644 --- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb +++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb @@ -299,4 +299,51 @@ RSpec.shared_examples 'namespace traversal scopes' do include_examples '.self_and_descendant_ids' end end + + shared_examples '.self_and_hierarchy' do + let(:base_scope) { Group.where(id: base_groups) } + + subject { base_scope.self_and_hierarchy } + + context 'with ancestors only' do + let(:base_groups) { [group_1, group_2] } + + it { is_expected.to match_array(groups) } + end + + context 'with descendants only' do + let(:base_groups) { [deep_nested_group_1, deep_nested_group_2] } + + it { is_expected.to match_array(groups) } + end + + context 'nodes with both ancestors and descendants' do + let(:base_groups) { [nested_group_1, nested_group_2] } + + it { is_expected.to match_array(groups) } + end + + context 'with duplicate base groups' do + let(:base_groups) { [nested_group_1, nested_group_1] } + + it { is_expected.to contain_exactly(group_1, nested_group_1, deep_nested_group_1) } + end + end + + describe '.self_and_hierarchy' do + it_behaves_like '.self_and_hierarchy' + + context "use_traversal_ids_for_self_and_hierarchy_scopes feature flag is false" do + before do + stub_feature_flags(use_traversal_ids_for_self_and_hierarchy_scopes: false) + end + + it_behaves_like '.self_and_hierarchy' + + it 'make recursive queries' do + base_groups = Group.where(id: nested_group_1) + expect { base_groups.self_and_hierarchy.load }.to make_queries_matching(/WITH RECURSIVE/) + end + end + end end diff --git a/spec/support/shared_examples/path_extraction_shared_examples.rb b/spec/support/shared_examples/path_extraction_shared_examples.rb index 39c7c1f2a94..d76348aa26a 100644 --- a/spec/support/shared_examples/path_extraction_shared_examples.rb +++ b/spec/support/shared_examples/path_extraction_shared_examples.rb @@ -40,12 +40,13 @@ RSpec.shared_examples 'assigns ref vars' do end context 'path contains space' do - let(:params) { { path: 'with space', ref: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } } + let(:ref) { '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } + let(:path) { 'with space' } it 'is not converted to %20 in @path' do assign_ref_vars - expect(@path).to eq(params[:path]) + expect(@path).to eq(path) end end diff --git a/spec/support/shared_examples/policies/clusterable_shared_examples.rb b/spec/support/shared_examples/policies/clusterable_shared_examples.rb index b96aa71acbe..faf283f9059 100644 --- a/spec/support/shared_examples/policies/clusterable_shared_examples.rb +++ b/spec/support/shared_examples/policies/clusterable_shared_examples.rb @@ -6,12 +6,24 @@ RSpec.shared_examples 'clusterable policies' do subject { described_class.new(current_user, clusterable) } + context 'with a reporter' do + before do + clusterable.add_reporter(current_user) + end + + it { expect_disallowed(:read_cluster) } + it { expect_disallowed(:add_cluster) } + it { expect_disallowed(:create_cluster) } + it { expect_disallowed(:update_cluster) } + it { expect_disallowed(:admin_cluster) } + end + context 'with a developer' do before do clusterable.add_developer(current_user) end - it { expect_disallowed(:read_cluster) } + it { expect_allowed(:read_cluster) } it { expect_disallowed(:add_cluster) } it { expect_disallowed(:create_cluster) } it { expect_disallowed(:update_cluster) } diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb index 052fd0622d0..f414500f202 100644 --- a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb @@ -66,7 +66,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| it 'shows the help state when icon is clicked' do page.within '.time-tracking-component-wrap' do - find('.help-button').click + find('[data-testid="helpButton"]').click expect(page).to have_content 'Track time with quick actions' expect(page).to have_content 'Learn more' end @@ -92,8 +92,8 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| it 'hides the help state when close icon is clicked' do page.within '.time-tracking-component-wrap' do - find('.help-button').click - find('.close-help-button').click + find('[data-testid="helpButton"]').click + find('[data-testid="closeHelpButton"]').click expect(page).not_to have_content 'Track time with quick actions' expect(page).not_to have_content 'Learn more' @@ -102,7 +102,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| it 'displays the correct help url' do page.within '.time-tracking-component-wrap' do - find('.help-button').click + find('[data-testid="helpButton"]').click expect(find_link('Learn more')[:href]).to have_content('/help/user/project/time_tracking.md') end diff --git a/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb index 28decb4011d..2258bdd2c79 100644 --- a/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb @@ -73,6 +73,16 @@ RSpec.shared_examples 'rebase quick action' do expect(page).to have_content 'This merge request cannot be rebased while there are conflicts.' end end + + context 'when the merge request branch is protected from force push' do + let!(:protected_branch) { create(:protected_branch, project: project, name: merge_request.source_branch, allow_force_push: false) } + + it 'does not rebase the MR' do + add_note("/rebase") + + expect(page).to have_content 'This merge request branch is protected from force push.' + end + end end context 'when the current user cannot rebase the MR' 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 8bffd1f71e9..a42a1fda62e 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 @@ -10,6 +10,8 @@ RSpec.shared_examples 'when the snippet is not found' do end RSpec.shared_examples 'snippet edit usage data counters' do + include SessionHelpers + context 'when user is sessionless' do it 'does not track usage data actions' do expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).not_to receive(:track_snippet_editor_edit_action) @@ -20,14 +22,7 @@ RSpec.shared_examples 'snippet edit usage data counters' do context 'when user is not sessionless', :clean_gitlab_redis_sessions do before do - session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d') - session_hash = { 'warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]] } - - Gitlab::Redis::Sessions.with do |redis| - redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) - end - - cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id + stub_session('warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]]) end it 'tracks usage data actions', :clean_gitlab_redis_sessions 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 882c79cb03f..127b1a6d4c4 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 @@ -3,11 +3,11 @@ RSpec.shared_examples 'group and project packages query' do include GraphqlHelpers - let_it_be(:versionaless_package) { create(:maven_package, project: project1, version: nil) } - let_it_be(:maven_package) { create(:maven_package, project: project1, name: 'tab', version: '4.0.0', created_at: 5.days.ago) } - let_it_be(:package) { create(:npm_package, project: project1, name: 'uab', version: '5.0.0', created_at: 4.days.ago) } - let_it_be(:composer_package) { create(:composer_package, project: project2, name: 'vab', version: '6.0.0', created_at: 3.days.ago) } - let_it_be(:debian_package) { create(:debian_package, project: project2, name: 'zab', version: '7.0.0', created_at: 2.days.ago) } + let_it_be(:versionless_package) { create(:maven_package, project: project1, version: nil) } + let_it_be(:maven_package) { create(:maven_package, project: project1, name: 'bab', version: '6.0.0', created_at: 1.day.ago) } + let_it_be(:npm_package) { create(:npm_package, project: project1, name: 'cab', version: '7.0.0', created_at: 4.days.ago) } + let_it_be(:composer_package) { create(:composer_package, project: project2, name: 'dab', version: '4.0.0', created_at: 3.days.ago) } + let_it_be(:debian_package) { create(:debian_package, project: project2, name: 'aab', version: '5.0.0', created_at: 2.days.ago) } let_it_be(:composer_metadatum) do create(:composer_metadatum, package: composer_package, target_sha: 'afdeh', @@ -21,11 +21,11 @@ RSpec.shared_examples 'group and project packages query' do let(:fields) do <<~QUERY - count - nodes { - #{all_graphql_fields_for('packages'.classify, excluded: ['project'])} - metadata { #{query_graphql_fragment('ComposerMetadata')} } - } + count + nodes { + #{all_graphql_fields_for('packages'.classify, excluded: ['project'])} + metadata { #{query_graphql_fragment('ComposerMetadata')} } + } QUERY end @@ -47,7 +47,7 @@ RSpec.shared_examples 'group and project packages query' do it 'returns packages successfully' do expect(package_names).to contain_exactly( - package.name, + npm_package.name, maven_package.name, debian_package.name, composer_package.name @@ -88,7 +88,23 @@ RSpec.shared_examples 'group and project packages query' do end describe 'sorting and pagination' do - let_it_be(:ascending_packages) { [maven_package, package, composer_package, debian_package].map { |package| global_id_of(package)} } + let_it_be(:packages_order_map) do + { + TYPE_ASC: [maven_package, npm_package, composer_package, debian_package], + TYPE_DESC: [debian_package, composer_package, npm_package, maven_package], + + NAME_ASC: [debian_package, maven_package, npm_package, composer_package], + NAME_DESC: [composer_package, npm_package, maven_package, debian_package], + + VERSION_ASC: [composer_package, debian_package, maven_package, npm_package], + VERSION_DESC: [npm_package, maven_package, debian_package, composer_package], + + CREATED_ASC: [npm_package, composer_package, debian_package, maven_package], + CREATED_DESC: [maven_package, debian_package, composer_package, npm_package] + } + end + + let(:expected_packages) { sorted_packages.map { |package| global_id_of(package) } } let(:data_path) { [resource_type, :packages] } @@ -96,22 +112,14 @@ RSpec.shared_examples 'group and project packages query' do resource.add_reporter(current_user) end - [:CREATED_ASC, :NAME_ASC, :VERSION_ASC, :TYPE_ASC].each do |order| + [:CREATED_ASC, :NAME_ASC, :VERSION_ASC, :TYPE_ASC, :CREATED_DESC, :NAME_DESC, :VERSION_DESC, :TYPE_DESC].each do |order| context "#{order}" do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { order } - let(:first_param) { 4 } - let(:all_records) { ascending_packages } - end - end - end + let(:sorted_packages) { packages_order_map.fetch(order) } - [:CREATED_DESC, :NAME_DESC, :VERSION_DESC, :TYPE_DESC].each do |order| - context "#{order}" do it_behaves_like 'sorted paginated query' do let(:sort_param) { order } let(:first_param) { 4 } - let(:all_records) { ascending_packages.reverse } + let(:all_records) { expected_packages } end end end @@ -180,7 +188,7 @@ RSpec.shared_examples 'group and project packages query' do context 'include_versionless' do let(:params) { { include_versionless: true } } - it { is_expected.to include({ "name" => versionaless_package.name }) } + it { is_expected.to include({ "name" => versionless_package.name }) } end end end 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 9385706d991..ab93f54111b 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 @@ -49,17 +49,5 @@ RSpec.shared_examples 'a package with files' do expect(response_package_file_ids).not_to include(package_file_pending_destruction.to_global_id.to_s) end - - context 'with packages_installable_package_files disabled' do - before(:context) do - stub_feature_flags(packages_installable_package_files: false) - end - - it 'returns them' do - expect(package.reload.package_files).to include(package_file_pending_destruction) - - expect(response_package_file_ids).to include(package_file_pending_destruction.to_global_id.to_s) - 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 0434d0beb7e..2a157f6e855 100644 --- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb @@ -190,7 +190,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| if parent_type == 'projects' context 'by a project owner' do - let(:user) { project.owner } + 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 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 b294467d482..c6c6c44dce8 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -580,3 +580,88 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do end end end + +# Requires let variables: +# * throttle_setting_prefix: "throttle_authenticated", "throttle_unauthenticated" +RSpec.shared_examples 'rate-limited frontend API requests' do + let(:requests_per_period) { 1 } + let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) } + let(:csrf_session) { { _csrf_token: csrf_token } } + let(:personal_access_token) { nil } + + let(:api_path) { '/projects' } + + # These don't actually exist, so a 404 is the expected response. + let(:files_api_path) { '/projects/1/repository/files/ref/path' } + let(:packages_api_path) { '/projects/1/packages/foo' } + let(:deprecated_api_path) { '/groups/1?with_projects=true' } + + def get_api(path: api_path, csrf: false) + headers = csrf ? { 'X-CSRF-Token' => csrf_token } : nil + get api(path, personal_access_token: personal_access_token), headers: headers + end + + def expect_not_found(&block) + yield + + expect(response).to have_gitlab_http_status(:not_found) + end + + before do + stub_application_setting( + "#{throttle_setting_prefix}_enabled" => true, + "#{throttle_setting_prefix}_requests_per_period" => requests_per_period, + "#{throttle_setting_prefix}_api_enabled" => true, + "#{throttle_setting_prefix}_api_requests_per_period" => requests_per_period, + "#{throttle_setting_prefix}_web_enabled" => true, + "#{throttle_setting_prefix}_web_requests_per_period" => requests_per_period, + "#{throttle_setting_prefix}_files_api_enabled" => true, + "#{throttle_setting_prefix}_packages_api_enabled" => true, + "#{throttle_setting_prefix}_deprecated_api_enabled" => true + ) + + stub_session(csrf_session) + end + + context 'with a CSRF token' do + it 'uses the rate limit for web requests' do + requests_per_period.times { get_api csrf: true } + + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true } + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: files_api_path } + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: packages_api_path } + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: deprecated_api_path } + + # API rate limit is not triggered yet + expect_ok { get_api } + expect_not_found { get_api path: files_api_path } + expect_not_found { get_api path: packages_api_path } + expect_not_found { get_api path: deprecated_api_path } + end + + context 'without a CSRF session' do + let(:csrf_session) { nil } + + it 'always uses the rate limit for API requests' do + requests_per_period.times { get_api csrf: true } + + expect_rejection("#{throttle_setting_prefix}_api") { get_api csrf: true } + expect_rejection("#{throttle_setting_prefix}_api") { get_api } + end + end + end + + context 'without a CSRF token' do + it 'uses the rate limit for API requests' do + requests_per_period.times { get_api } + + expect_rejection("#{throttle_setting_prefix}_api") { get_api } + + # Web and custom API rate limits are not triggered yet + expect_ok { get_api csrf: true } + expect_not_found { get_api path: files_api_path } + expect_not_found { get_api path: packages_api_path } + expect_not_found { get_api path: deprecated_api_path } + end + end +end diff --git a/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb b/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb index d9b837258ce..a46c2f0ac5c 100644 --- a/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb @@ -140,19 +140,6 @@ RSpec.shared_examples 'issues move service' do |group| expect(issue2.reload.updated_at.change(usec: 0)).to eq updated_at2.change(usec: 0) end - if group - context 'when on a group board' do - it 'sends the board_group_id parameter' do - params.merge!(move_after_id: issue1.id, move_before_id: issue2.id) - - match_params = { move_between_ids: [issue1.id, issue2.id], board_group_id: parent.id } - expect(Issues::UpdateService).to receive(:new).with(project: issue.project, current_user: user, params: match_params).and_return(double(execute: build(:issue))) - - described_class.new(parent, user, params).execute(issue) - end - end - end - def reorder_issues(params, issues: []) issues.each do |issue| issue.move_to_end && issue.save! 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 87bf134eeb8..c808b9a5318 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 @@ -71,7 +71,6 @@ end RSpec.shared_examples 'an accessible' do before do stub_feature_flags(container_registry_migration_phase1: false) - stub_feature_flags(container_registry_cdn_redirect: false) end let(:access) do @@ -164,10 +163,9 @@ RSpec.shared_examples 'a container registry auth service' do before do stub_feature_flags(container_registry_migration_phase1: false) - stub_feature_flags(container_registry_cdn_redirect: false) end - describe '#full_access_token' do + describe '.full_access_token' do let_it_be(:project) { create(:project) } let(:token) { described_class.full_access_token(project.full_path) } @@ -181,7 +179,26 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'not a container repository factory' end - describe '#pull_access_token' do + describe '.import_access_token' do + let(:access) do + [{ 'type' => 'registry', + 'name' => 'import', + 'actions' => ['*'] }] + end + + let(:token) { described_class.import_access_token } + + subject { { token: token } } + + it_behaves_like 'a valid token' + it_behaves_like 'not a container repository factory' + + it 'has the correct scope' do + expect(payload).to include('access' => access) + end + end + + describe '.pull_access_token' do let_it_be(:project) { create(:project) } let(:token) { described_class.pull_access_token(project.full_path) } @@ -1126,4 +1143,72 @@ RSpec.shared_examples 'a container registry auth service' do end end end + + context 'when importing' do + let_it_be(:container_repository) { create(:container_repository, :root, :importing) } + let_it_be(:current_project) { container_repository.project } + let_it_be(:current_user) { create(:user) } + + before do + current_project.add_developer(current_user) + end + + shared_examples 'containing the import error' do + it 'includes a helpful error message' do + expect(subject[:errors].first).to include(message: /Your repository is currently being migrated/) + end + end + + context 'push request' do + let(:current_params) do + { scopes: ["repository:#{container_repository.path}:push"] } + end + + it_behaves_like 'a forbidden' do + it_behaves_like 'containing the import error' + end + end + + context 'delete request' do + let(:current_params) do + { scopes: ["repository:#{container_repository.path}:delete"] } + end + + it_behaves_like 'a forbidden' do + it_behaves_like 'containing the import error' + end + end + + context '* request' do + let(:current_params) do + { scopes: ["repository:#{container_repository.path}:*"] } + end + + it_behaves_like 'a forbidden' do + it_behaves_like 'containing the import error' + end + end + + context 'pull request' do + let(:current_params) do + { scopes: ["repository:#{container_repository.path}:pull"] } + end + + let(:project) { current_project } + + it_behaves_like 'a pullable' + end + + context 'mixed request' do + let(:current_params) do + { scopes: ["repository:#{container_repository.path}:pull,push"] } + end + + let(:project) { current_project } + + it_behaves_like 'a forbidden' do + it_behaves_like 'containing the import error' + end + end + 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 36b0acf5a51..cc26cf87322 100644 --- a/spec/support/shared_examples/services/incident_shared_examples.rb +++ b/spec/support/shared_examples/services/incident_shared_examples.rb @@ -28,28 +28,15 @@ end # # include_examples 'not an incident issue' RSpec.shared_examples 'not an incident issue' do - let(:label_properties) { attributes_for(:label, :incident) } - 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 - - it_behaves_like 'does not have incident label' -end - -RSpec.shared_examples 'does not have incident label' do - let(:label_properties) { attributes_for(:label, :incident) } - - it 'has not an incident label' do - expect(issue.labels).not_to include(have_attributes(label_properties)) - end end # This shared example is to test the execution of incident management label services # For example: # - IncidentManagement::CreateIncidentSlaExceededLabelService -# - IncidentManagement::CreateIncidentLabelService # It doesn't require any defined variables diff --git a/spec/support/shared_examples/views/registration_features_prompt_shared_examples.rb b/spec/support/shared_examples/views/registration_features_prompt_shared_examples.rb new file mode 100644 index 00000000000..661a96266f1 --- /dev/null +++ b/spec/support/shared_examples/views/registration_features_prompt_shared_examples.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'renders registration features prompt' do |disabled_field| + it 'renders a placeholder input with registration features message', :aggregate_failures do + render + + if disabled_field + expect(rendered).to have_field(disabled_field, disabled: true) + end + + expect(rendered).to have_content(s_("RegistrationFeatures|Want to %{feature_title} for free?") % { feature_title: s_('RegistrationFeatures|use this feature') }) + expect(rendered).to have_link(s_('RegistrationFeatures|Registration Features Program')) + end +end + +RSpec.shared_examples 'does not render registration features prompt' do |disabled_field| + it 'does not render a placeholder input with registration features message', :aggregate_failures do + render + + if disabled_field + expect(rendered).not_to have_field(disabled_field, disabled: true) + end + + expect(rendered).not_to have_content(s_("RegistrationFeatures|Want to %{feature_title} for free?") % { feature_title: s_('RegistrationFeatures|use this feature') }) + expect(rendered).not_to have_link(s_('RegistrationFeatures|Registration Features Program')) + end +end diff --git a/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb index 0d3e158d358..7fdf049a823 100644 --- a/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb +++ b/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'it runs background migration jobs' do |tracking_database, metric_name| +RSpec.shared_examples 'it runs background migration jobs' do |tracking_database| describe 'defining the job attributes' do it 'defines the data_consistency as always' do expect(described_class.get_data_consistency).to eq(:always) @@ -33,16 +33,6 @@ RSpec.shared_examples 'it runs background migration jobs' do |tracking_database, end end - describe '.unhealthy_metric_name' do - it 'does not raise an error' do - expect { described_class.unhealthy_metric_name }.not_to raise_error - end - - it 'overrides the method to return the unhealthy metric name' do - expect(described_class.unhealthy_metric_name).to eq(metric_name) - end - end - describe '.minimum_interval' do it 'returns 2 minutes' do expect(described_class.minimum_interval).to eq(2.minutes.to_i) @@ -189,11 +179,11 @@ RSpec.shared_examples 'it runs background migration jobs' do |tracking_database, end it 'increments the unhealthy counter' do - counter = Gitlab::Metrics.counter(metric_name, 'msg') + counter = Gitlab::Metrics.counter(:background_migration_database_health_reschedules, 'msg') expect(described_class).to receive(:perform_in) - expect { worker.perform('Foo', [10, 20]) }.to change { counter.get }.by(1) + expect { worker.perform('Foo', [10, 20]) }.to change { counter.get(db_config_name: tracking_database) }.by(1) end context 'when lease_attempts is 0' do diff --git a/spec/support/shared_examples/workers/project_export_shared_examples.rb b/spec/support/shared_examples/workers/project_export_shared_examples.rb index a9bcc3f4f7c..175ef9bd012 100644 --- a/spec/support/shared_examples/workers/project_export_shared_examples.rb +++ b/spec/support/shared_examples/workers/project_export_shared_examples.rb @@ -53,6 +53,10 @@ RSpec.shared_examples 'export worker' do it 'does not raise an exception when strategy is invalid' do expect(::Projects::ImportExport::ExportService).not_to receive(:new) + expect_next_instance_of(ProjectExportJob) do |job| + expect(job).to receive(:finish) + end + expect { subject.perform(user.id, project.id, { 'klass' => 'Whatever' }) }.not_to raise_error end @@ -63,6 +67,18 @@ RSpec.shared_examples 'export worker' do it 'does not raise error when user cannot be found' do expect { subject.perform(non_existing_record_id, project.id, {}) }.not_to raise_error end + + it 'fails the export job status' do + expect_next_instance_of(::Projects::ImportExport::ExportService) do |service| + expect(service).to receive(:execute).and_raise(Gitlab::ImportExport::Error) + end + + expect_next_instance_of(ProjectExportJob) do |job| + expect(job).to receive(:fail_op) + end + + expect { subject.perform(user.id, project.id, {}) }.to raise_error(Gitlab::ImportExport::Error) + end end end diff --git a/spec/support/stub_settings_source.rb b/spec/support/stub_settings_source.rb new file mode 100644 index 00000000000..c0e4e468b90 --- /dev/null +++ b/spec/support/stub_settings_source.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.around(:each, stub_settings_source: true) do |example| + original_instance = ::Settings.instance_variable_get(:@instance) + + example.run + + ::Settings.instance_variable_set(:@instance, original_instance) + end +end |