Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /spec/support
parente570267f2f6b326480d284e0164a6464ba4081bc (diff)
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/atlassian/jira_connect/schemata.rb2
-rw-r--r--spec/support/capybara.rb10
-rw-r--r--spec/support/db_cleaner.rb2
-rw-r--r--spec/support/factory_bot.rb8
-rw-r--r--spec/support/factory_default.rb12
-rw-r--r--spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb13
-rw-r--r--spec/support/gitlab_experiment.rb4
-rw-r--r--spec/support/helpers/board_helpers.rb17
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb38
-rw-r--r--spec/support/helpers/dns_helpers.rb2
-rw-r--r--spec/support/helpers/features/invite_members_modal_helper.rb50
-rw-r--r--spec/support/helpers/features/members_table_helpers.rb4
-rw-r--r--spec/support/helpers/gitaly_setup.rb195
-rw-r--r--spec/support/helpers/graphql_helpers.rb4
-rw-r--r--spec/support/helpers/ldap_helpers.rb2
-rw-r--r--spec/support/helpers/license_helper.rb2
-rw-r--r--spec/support/helpers/login_helpers.rb2
-rw-r--r--spec/support/helpers/migrations_helpers.rb2
-rw-r--r--spec/support/helpers/next_found_instance_of.rb2
-rw-r--r--spec/support/helpers/query_recorder.rb4
-rw-r--r--spec/support/helpers/redis_without_keys.rb4
-rw-r--r--spec/support/helpers/reload_helpers.rb2
-rw-r--r--spec/support/helpers/require_migration.rb4
-rw-r--r--spec/support/helpers/snowplow_helpers.rb12
-rw-r--r--spec/support/helpers/stub_configuration.rb2
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb2
-rw-r--r--spec/support/helpers/test_env.rb8
-rw-r--r--spec/support/helpers/usage_data_helpers.rb1
-rw-r--r--spec/support/matchers/access_matchers_generic.rb2
-rw-r--r--spec/support/matchers/markdown_matchers.rb2
-rw-r--r--spec/support/matchers/schema_matcher.rb58
-rw-r--r--spec/support/renameable_upload.rb2
-rw-r--r--spec/support/services/issuable_update_service_shared_examples.rb4
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb132
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/project_service_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb9
-rw-r--r--spec/support/shared_contexts/spam_constants.rb1
-rw-r--r--spec/support/shared_examples/alert_notification_service_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/boards/lists/update_service_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/board_sidebar_labels_examples.rb77
-rw-r--r--spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/features/sidebar_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/features/variable_list_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/finders/packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/graphql/mutations/boards/update_list_shared_examples.rb48
-rw-r--r--spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb114
-rw-r--r--spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb63
-rw-r--r--spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/lib/gitlab/jwt_token_shared_examples.rb49
-rw-r--r--spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/chat_service_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb82
-rw-r--r--spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/concerns/cron_schedulable_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb55
-rw-r--r--spec/support/shared_examples/models/packages/debian/component_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb45
-rw-r--r--spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/namespaces/traversal_examples.rb (renamed from spec/support/shared_examples/namespaces/namespace_traversal_examples.rb)17
-rw-r--r--spec/support/shared_examples/policies/project_policy_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb345
-rw-r--r--spec/support/shared_examples/requests/api/graphql/mutations/boards/update_list_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/requests/api/graphql/mutations/destroy_list_shared_examples.rb87
-rw-r--r--spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb129
-rw-r--r--spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb251
-rw-r--r--spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/requests/clusters/integrations_controller_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/row_lock_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/serializers/pipeline_artifacts_shared_example.rb21
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb161
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb113
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/services/alert_management_shared_examples.rb136
-rw-r--r--spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb65
-rw-r--r--spec/support/shared_examples/services/boards/create_service_shared_examples.rb25
-rw-r--r--spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/services/common_system_notes_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/destroy_label_links_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/services/issuable_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/merge_request_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb91
-rw-r--r--spec/support/shared_examples/services/updating_mentions_shared_examples.rb2
-rw-r--r--spec/support/stored_repositories.rb2
-rw-r--r--spec/support/stub_languages_translation_percentage.rb19
110 files changed, 2613 insertions, 722 deletions
diff --git a/spec/support/atlassian/jira_connect/schemata.rb b/spec/support/atlassian/jira_connect/schemata.rb
index d056c7cacf3..61e8aa8e15c 100644
--- a/spec/support/atlassian/jira_connect/schemata.rb
+++ b/spec/support/atlassian/jira_connect/schemata.rb
@@ -74,7 +74,7 @@ module Atlassian
'deploymentSequenceNumber' => { 'type' => 'integer' },
'updateSequenceNumber' => { 'type' => 'integer' },
'associations' => {
- 'type' => 'array',
+ 'type' => %w(array),
'items' => association_type,
'minItems' => 1
},
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index be2b41d6997..f9a28c8e40b 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -157,7 +157,7 @@ RSpec.configure do |config|
unless session.current_window.size == CAPYBARA_WINDOW_SIZE
begin
session.current_window.resize_to(*CAPYBARA_WINDOW_SIZE)
- rescue # ?
+ rescue StandardError # ?
end
end
end
@@ -170,14 +170,16 @@ RSpec.configure do |config|
Capybara.raise_server_errors = false
example.run
+ ensure
+ Capybara.raise_server_errors = true
+ end
+ config.append_after do |example|
if example.metadata[:screenshot]
screenshot = example.metadata[:screenshot][:image] || example.metadata[:screenshot][:html]
+ screenshot&.delete_prefix!(ENV.fetch('CI_PROJECT_DIR', ''))
example.metadata[:stdout] = %{[[ATTACHMENT|#{screenshot}]]}
end
-
- ensure
- Capybara.raise_server_errors = true
end
config.after(:example, :js) do |example|
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 77e1f6bcaa3..ff913ebf22b 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -16,4 +16,4 @@ module DbCleaner
end
end
-DbCleaner.prepend_if_ee('EE::DbCleaner')
+DbCleaner.prepend_mod_with('DbCleaner')
diff --git a/spec/support/factory_bot.rb b/spec/support/factory_bot.rb
index 5761e05d541..5f22fa11e9e 100644
--- a/spec/support/factory_bot.rb
+++ b/spec/support/factory_bot.rb
@@ -2,6 +2,14 @@
FactoryBot::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
+
+ # FactoryBot doesn't allow yet to add a helper that can be used in factories
+ # While the fixture_file_upload helper is reasonable to be used there:
+ #
+ # https://github.com/thoughtbot/factory_bot/issues/564#issuecomment-389491577
+ def fixture_file_upload(*args, **kwargs)
+ Rack::Test::UploadedFile.new(*args, **kwargs)
+ end
end
# Patching FactoryBot to allow stubbing non AR models
diff --git a/spec/support/factory_default.rb b/spec/support/factory_default.rb
index e116c28f132..31af022f6c0 100644
--- a/spec/support/factory_default.rb
+++ b/spec/support/factory_default.rb
@@ -1,5 +1,17 @@
# frozen_string_literal: true
+module Gitlab
+ module FreezeFactoryDefault
+ def set_factory_default(name, obj, preserve_traits: nil)
+ obj.freeze unless obj.frozen?
+
+ super
+ end
+ end
+end
+
+TestProf::FactoryDefault::DefaultSyntax.prepend Gitlab::FreezeFactoryDefault
+
RSpec.configure do |config|
config.after do |ex|
TestProf::FactoryDefault.reset unless ex.metadata[:factory_default] == :keep
diff --git a/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb b/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb
new file mode 100644
index 00000000000..c9ff566e94c
--- /dev/null
+++ b/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a correct instrumented metric value' do |options, expected_value|
+ let(:time_frame) { options[:time_frame] }
+
+ before do
+ allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false)
+ end
+
+ it 'has correct value' do
+ expect(described_class.new(time_frame: time_frame).value).to eq(expected_value)
+ end
+end
diff --git a/spec/support/gitlab_experiment.rb b/spec/support/gitlab_experiment.rb
index bd0c88f8049..b84adf82d29 100644
--- a/spec/support/gitlab_experiment.rb
+++ b/spec/support/gitlab_experiment.rb
@@ -12,10 +12,6 @@ class ApplicationExperiment # rubocop:disable Gitlab/NamespacedClass
super(...)
Feature.persist_used!(feature_flag_name)
end
-
- def should_track?
- true
- end
end
RSpec.configure do |config|
diff --git a/spec/support/helpers/board_helpers.rb b/spec/support/helpers/board_helpers.rb
index 6e145fed733..c4e69d06f52 100644
--- a/spec/support/helpers/board_helpers.rb
+++ b/spec/support/helpers/board_helpers.rb
@@ -4,6 +4,23 @@ module BoardHelpers
def click_card(card)
within card do
first('.board-card-number').click
+ wait_for_requests
+ end
+ end
+
+ def load_board(board_path)
+ visit board_path
+
+ wait_for_requests
+ end
+
+ def click_card_and_edit_label
+ click_card(card)
+
+ page.within(labels_select) do
+ click_button 'Edit'
+
+ wait_for_requests
end
end
end
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index 9e62eef14de..5510284b30d 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -3,6 +3,38 @@
module CycleAnalyticsHelpers
include GitHelpers
+ def toggle_value_stream_dropdown
+ page.find('[data-testid="dropdown-value-streams"]').click
+ end
+
+ def add_custom_stage_to_form
+ page.find_button(s_('CreateValueStreamForm|Add another stage')).click
+
+ index = page.all('[data-testid="value-stream-stage-fields"]').length
+ last_stage = page.all('[data-testid="value-stream-stage-fields"]').last
+
+ within last_stage do
+ find('[name*="custom-stage-name-"]').fill_in with: "Cool custom stage - name #{index}"
+ select_dropdown_option_by_value "custom-stage-start-event-", :merge_request_created
+ select_dropdown_option_by_value "custom-stage-end-event-", :merge_request_merged
+ end
+ end
+
+ def save_value_stream(custom_value_stream_name)
+ fill_in 'create-value-stream-name', with: custom_value_stream_name
+
+ page.find_button(s_('CreateValueStreamForm|Create Value Stream')).click
+ wait_for_requests
+ end
+
+ def create_custom_value_stream(custom_value_stream_name)
+ toggle_value_stream_dropdown
+ page.find_button(_('Create new Value Stream')).click
+
+ add_custom_stage_to_form
+ save_value_stream(custom_value_stream_name)
+ end
+
def wait_for_stages_to_load(selector = '.js-path-navigation')
expect(page).to have_selector selector
wait_for_requests
@@ -93,17 +125,17 @@ module CycleAnalyticsHelpers
target_branch: 'master'
}
- mr = MergeRequests::CreateService.new(project, user, opts).execute
+ mr = MergeRequests::CreateService.new(project: project, current_user: user, params: opts).execute
NewMergeRequestWorker.new.perform(mr, user)
mr
end
def merge_merge_requests_closing_issue(user, project, issue)
merge_requests = Issues::ReferencedMergeRequestsService
- .new(project, user)
+ .new(project: project, current_user: user)
.closed_by_merge_requests(issue)
- merge_requests.each { |merge_request| MergeRequests::MergeService.new(project, user, sha: merge_request.diff_head_sha).execute(merge_request) }
+ merge_requests.each { |merge_request| MergeRequests::MergeService.new(project: project, current_user: user, params: { sha: merge_request.diff_head_sha }).execute(merge_request) }
end
def deploy_master(user, project, environment: 'production')
diff --git a/spec/support/helpers/dns_helpers.rb b/spec/support/helpers/dns_helpers.rb
index 1795b0a9ac3..ba32ccbb6f1 100644
--- a/spec/support/helpers/dns_helpers.rb
+++ b/spec/support/helpers/dns_helpers.rb
@@ -18,7 +18,7 @@ module DnsHelpers
def stub_invalid_dns!
allow(Addrinfo).to receive(:getaddrinfo).with(/\Afoobar\.\w|(\d{1,3}\.){4,}\d{1,3}\z/i, anything, nil, :STREAM) do
- raise SocketError.new("getaddrinfo: Name or service not known")
+ raise SocketError, "getaddrinfo: Name or service not known"
end
end
diff --git a/spec/support/helpers/features/invite_members_modal_helper.rb b/spec/support/helpers/features/invite_members_modal_helper.rb
new file mode 100644
index 00000000000..1127c817656
--- /dev/null
+++ b/spec/support/helpers/features/invite_members_modal_helper.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Spec
+ module Support
+ module Helpers
+ module Features
+ module InviteMembersModalHelper
+ def invite_member(name, role: 'Guest', expires_at: nil)
+ click_on 'Invite members'
+
+ page.within '#invite-members-modal' do
+ fill_in 'Select members or type email addresses', with: name
+
+ wait_for_requests
+ click_button name
+ choose_options(role, expires_at)
+
+ click_button 'Invite'
+
+ page.refresh
+ 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)
+
+ click_button 'Invite'
+
+ page.refresh
+ end
+
+ def choose_options(role, expires_at)
+ unless role == 'Guest'
+ click_button 'Guest'
+ wait_for_requests
+ click_button role
+ end
+
+ fill_in 'YYYY-MM-DD', with: expires_at.try(:strftime, '%Y-%m-%d')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/features/members_table_helpers.rb b/spec/support/helpers/features/members_table_helpers.rb
index 4a0e218ed3e..2e86e014a1b 100644
--- a/spec/support/helpers/features/members_table_helpers.rb
+++ b/spec/support/helpers/features/members_table_helpers.rb
@@ -27,10 +27,6 @@ module Spec
all_rows[2]
end
- def invite_users_form
- page.find('[data-testid="invite-users-form"]')
- end
-
def find_row(name)
page.within(members_table) do
page.find('tbody > tr', text: name)
diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb
new file mode 100644
index 00000000000..2ce4bcfa943
--- /dev/null
+++ b/spec/support/helpers/gitaly_setup.rb
@@ -0,0 +1,195 @@
+# frozen_string_literal: true
+
+# This file contains environment settings for gitaly when it's running
+# as part of the gitlab-ce/ee test suite.
+#
+# Please be careful when modifying this file. Your changes must work
+# both for local development rspec runs, and in CI.
+
+require 'securerandom'
+require 'socket'
+require 'logger'
+
+module GitalySetup
+ LOGGER = begin
+ default_name = ENV['CI'] ? 'DEBUG' : 'WARN'
+ level_name = ENV['GITLAB_TESTING_LOG_LEVEL']&.upcase
+ level = Logger.const_get(level_name || default_name, true) # rubocop: disable Gitlab/ConstGetInheritFalse
+ Logger.new(STDOUT, level: level, formatter: ->(_, _, _, msg) { msg })
+ end
+
+ def tmp_tests_gitaly_dir
+ File.expand_path('../../../tmp/tests/gitaly', __dir__)
+ end
+
+ def tmp_tests_gitaly_bin_dir
+ File.join(tmp_tests_gitaly_dir, '_build', 'bin')
+ end
+
+ def tmp_tests_gitlab_shell_dir
+ File.expand_path('../../../tmp/tests/gitlab-shell', __dir__)
+ end
+
+ def rails_gitlab_shell_secret
+ File.expand_path('../../../.gitlab_shell_secret', __dir__)
+ 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
+ {
+ 'HOME' => File.expand_path('tmp/tests'),
+ 'GEM_PATH' => Gem.path.join(':'),
+ 'BUNDLE_APP_CONFIG' => File.join(gemfile_dir, '.bundle'),
+ 'BUNDLE_INSTALL_FLAGS' => nil,
+ 'BUNDLE_GEMFILE' => gemfile,
+ '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
+
+ # rubocop:disable GitlabSecurity/SystemCommandInjection
+ def set_bundler_config
+ system('bundle config set --local jobs 4', chdir: gemfile_dir)
+ system('bundle config set --local retry 3', chdir: gemfile_dir)
+
+ if ENV['CI']
+ bundle_path = File.expand_path('../../../vendor/gitaly-ruby', __dir__)
+ system('bundle', 'config', 'set', '--local', 'path', bundle_path, chdir: gemfile_dir)
+ end
+ end
+ # rubocop:enable GitlabSecurity/SystemCommandInjection
+
+ def config_path(service)
+ case service
+ when :gitaly
+ File.join(tmp_tests_gitaly_dir, 'config.toml')
+ when :gitaly2
+ File.join(tmp_tests_gitaly_dir, 'gitaly2.config.toml')
+ when :praefect
+ File.join(tmp_tests_gitaly_dir, 'praefect.config.toml')
+ end
+ end
+
+ def service_binary(service)
+ case service
+ when :gitaly, :gitaly2
+ 'gitaly'
+ when :praefect
+ 'praefect'
+ end
+ end
+
+ def install_gitaly_gems
+ system(env, "make #{tmp_tests_gitaly_dir}/.ruby-bundle", chdir: tmp_tests_gitaly_dir) # rubocop:disable GitlabSecurity/SystemCommandInjection
+ end
+
+ def build_gitaly
+ system(env, 'make', chdir: tmp_tests_gitaly_dir) # rubocop:disable GitlabSecurity/SystemCommandInjection
+ end
+
+ def start_gitaly
+ start(:gitaly)
+ end
+
+ def start_gitaly2
+ start(:gitaly2)
+ end
+
+ def start_praefect
+ start(:praefect)
+ end
+
+ def start(service)
+ args = ["#{tmp_tests_gitaly_bin_dir}/#{service_binary(service)}"]
+ args.push("-config") if service == :praefect
+ args.push(config_path(service))
+ pid = spawn(env, *args, [:out, :err] => "log/#{service}-test.log")
+
+ begin
+ try_connect!(service)
+ rescue StandardError
+ Process.kill('TERM', pid)
+ raise
+ end
+
+ pid
+ end
+
+ # Taken from Gitlab::Shell.generate_and_link_secret_token
+ def ensure_gitlab_shell_secret!
+ secret_file = rails_gitlab_shell_secret
+ shell_link = gitlab_shell_secret_file
+
+ unless File.size?(secret_file)
+ File.write(secret_file, SecureRandom.hex(16))
+ end
+
+ unless File.exist?(shell_link)
+ FileUtils.ln_s(secret_file, shell_link)
+ 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: File.dirname(gemfile))
+ end
+
+ def read_socket_path(service)
+ # This code needs to work in an environment where we cannot use bundler,
+ # so we cannot easily use the toml-rb gem. This ad-hoc parser should be
+ # good enough.
+ config_text = IO.read(config_path(service))
+
+ config_text.lines.each do |line|
+ match_data = line.match(/^\s*socket_path\s*=\s*"([^"]*)"$/)
+
+ return match_data[1] if match_data
+ end
+
+ raise "failed to find socket_path in #{config_path(service)}"
+ end
+
+ def try_connect!(service)
+ LOGGER.debug "Trying to connect to #{service}: "
+ timeout = 20
+ delay = 0.1
+ socket = read_socket_path(service)
+
+ Integer(timeout / delay).times do
+ UNIXSocket.new(socket)
+ LOGGER.debug " OK\n"
+
+ return
+ rescue Errno::ENOENT, Errno::ECONNREFUSED
+ LOGGER.debug '.'
+ sleep delay
+ end
+
+ LOGGER.warn " FAILED to connect to #{service}\n"
+
+ raise "could not connect to #{socket}"
+ end
+end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 9d6c6ab93e4..5dc6945ec5e 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -142,9 +142,9 @@ module GraphqlHelpers
Class.new(::Types::BaseObject) { graphql_name name }
end
- def resolver_instance(resolver_class, obj: nil, ctx: {}, field: nil, schema: GitlabSchema)
+ def resolver_instance(resolver_class, obj: nil, ctx: {}, field: nil, schema: GitlabSchema, subscription_update: false)
if ctx.is_a?(Hash)
- q = double('Query', schema: schema)
+ q = double('Query', schema: schema, subscription_update?: subscription_update)
ctx = GraphQL::Query::Context.new(query: q, object: obj, values: ctx)
end
diff --git a/spec/support/helpers/ldap_helpers.rb b/spec/support/helpers/ldap_helpers.rb
index 8154e3a4fc9..2f5f8be518c 100644
--- a/spec/support/helpers/ldap_helpers.rb
+++ b/spec/support/helpers/ldap_helpers.rb
@@ -71,4 +71,4 @@ module LdapHelpers
end
end
-LdapHelpers.include_if_ee('EE::LdapHelpers')
+LdapHelpers.include_mod_with('LdapHelpers')
diff --git a/spec/support/helpers/license_helper.rb b/spec/support/helpers/license_helper.rb
index a1defba9ccb..61afb2171f0 100644
--- a/spec/support/helpers/license_helper.rb
+++ b/spec/support/helpers/license_helper.rb
@@ -7,4 +7,4 @@ module LicenseHelpers
end
end
-LicenseHelpers.prepend_if_ee('EE::LicenseHelpers')
+LicenseHelpers.prepend_mod_with('LicenseHelpers')
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 86022e16d71..fc3eb976276 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -237,4 +237,4 @@ module LoginHelpers
end
end
-LoginHelpers.prepend_if_ee('EE::LoginHelpers')
+LoginHelpers.prepend_mod_with('LoginHelpers')
diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb
index 2c31a608b35..fa50b234bd5 100644
--- a/spec/support/helpers/migrations_helpers.rb
+++ b/spec/support/helpers/migrations_helpers.rb
@@ -172,4 +172,4 @@ module MigrationsHelpers
end
end
-MigrationsHelpers.prepend_if_ee('EE::MigrationsHelpers')
+MigrationsHelpers.prepend_mod_with('MigrationsHelpers')
diff --git a/spec/support/helpers/next_found_instance_of.rb b/spec/support/helpers/next_found_instance_of.rb
index feb63f90211..c8cdbaf2c5d 100644
--- a/spec/support/helpers/next_found_instance_of.rb
+++ b/spec/support/helpers/next_found_instance_of.rb
@@ -22,7 +22,7 @@ module NextFoundInstanceOf
private
def check_if_active_record!(klass)
- raise ArgumentError.new(ERROR_MESSAGE) unless klass < ActiveRecord::Base
+ raise ArgumentError, ERROR_MESSAGE unless klass < ActiveRecord::Base
end
def stub_allocate(target, klass)
diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index 2d880c7a8fe..05afbc336da 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -13,6 +13,10 @@ module ActiveRecord
@skip_cached = skip_cached
@query_recorder_debug = ENV['QUERY_RECORDER_DEBUG'] || query_recorder_debug
@log_file = log_file
+ record(&block) if block_given?
+ end
+
+ def record(&block)
# force replacement of bind parameters to give tests the ability to check for ids
ActiveRecord::Base.connection.unprepared_statement do
ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block)
diff --git a/spec/support/helpers/redis_without_keys.rb b/spec/support/helpers/redis_without_keys.rb
index e030f1028f7..ff64a3cf08e 100644
--- a/spec/support/helpers/redis_without_keys.rb
+++ b/spec/support/helpers/redis_without_keys.rb
@@ -4,7 +4,7 @@ class Redis
ForbiddenCommand = Class.new(StandardError)
def keys(*args)
- raise ForbiddenCommand.new("Don't use `Redis#keys` as it iterates over all "\
- "keys in redis. Use `Redis#scan_each` instead.")
+ raise ForbiddenCommand, "Don't use `Redis#keys` as it iterates over all "\
+ "keys in redis. Use `Redis#scan_each` instead."
end
end
diff --git a/spec/support/helpers/reload_helpers.rb b/spec/support/helpers/reload_helpers.rb
index 60811e4604f..368ebaaba8a 100644
--- a/spec/support/helpers/reload_helpers.rb
+++ b/spec/support/helpers/reload_helpers.rb
@@ -2,7 +2,7 @@
module ReloadHelpers
def reload_models(*models)
- models.map(&:reload)
+ models.compact.map(&:reload)
end
def subject_and_reload(*models)
diff --git a/spec/support/helpers/require_migration.rb b/spec/support/helpers/require_migration.rb
index c2902aa4ec7..8de71d3073f 100644
--- a/spec/support/helpers/require_migration.rb
+++ b/spec/support/helpers/require_migration.rb
@@ -20,7 +20,7 @@ class RequireMigration
class << self
def require_migration!(file_name)
file_paths = search_migration_file(file_name)
- raise AutoLoadError.new(file_name) unless file_paths.first
+ raise AutoLoadError, file_name unless file_paths.first
require file_paths.first
end
@@ -41,7 +41,7 @@ class RequireMigration
end
end
-RequireMigration.prepend_if_ee('EE::RequireMigration')
+RequireMigration.prepend_mod_with('RequireMigration')
def require_migration!(file_name = nil)
location_info = caller_locations.first.path.match(RequireMigration::SPEC_FILE_PATTERN)
diff --git a/spec/support/helpers/snowplow_helpers.rb b/spec/support/helpers/snowplow_helpers.rb
index 70a4eadd8de..553739b5d30 100644
--- a/spec/support/helpers/snowplow_helpers.rb
+++ b/spec/support/helpers/snowplow_helpers.rb
@@ -60,6 +60,10 @@ module SnowplowHelpers
.with(category, action, **kwargs).at_least(:once)
end
+ def match_snowplow_context_schema(schema_path:, context:)
+ expect(context).to match_snowplow_schema(schema_path)
+ end
+
# Asserts that no call to `Gitlab::Tracking#event` was made.
#
# Example:
@@ -71,7 +75,11 @@ module SnowplowHelpers
# expect_no_snowplow_event
# end
# end
- def expect_no_snowplow_event
- expect(Gitlab::Tracking).not_to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
+ def expect_no_snowplow_event(category: nil, action: nil, **kwargs)
+ if category && action
+ expect(Gitlab::Tracking).not_to have_received(:event).with(category, action, **kwargs) # rubocop:disable RSpec/ExpectGitlabTracking
+ else
+ expect(Gitlab::Tracking).not_to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
+ end
end
end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 9851a3de9e9..8c60dc30cdb 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -163,4 +163,4 @@ end
require_relative '../../../ee/spec/support/helpers/ee/stub_configuration' if
Dir.exist?("#{__dir__}/../../../ee")
-StubConfiguration.prepend_if_ee('EE::StubConfiguration')
+StubConfiguration.prepend_mod_with('StubConfiguration')
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index 4da8f760056..3824ff2b68d 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -153,4 +153,4 @@ module StubGitlabCalls
end
end
-StubGitlabCalls.prepend_if_ee('EE::StubGitlabCalls')
+StubGitlabCalls.prepend_mod_with('StubGitlabCalls')
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 7ba15a9c00b..40a3dbfbf25 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -52,7 +52,7 @@ module TestEnv
'wip' => 'b9238ee',
'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3',
- 'add-ipython-files' => '93ee732',
+ 'add-ipython-files' => 'f6b7a70',
'add-pdf-file' => 'e774ebd',
'squash-large-files' => '54cec52',
'add-pdf-text-binary' => '79faa7b',
@@ -266,7 +266,7 @@ module TestEnv
Integer(sleep_time / sleep_interval).times do
Socket.unix(socket)
return
- rescue
+ rescue StandardError
sleep sleep_interval
end
@@ -612,5 +612,5 @@ end
require_relative('../../../ee/spec/support/helpers/ee/test_env') if Gitlab.ee?
-::TestEnv.prepend_if_ee('::EE::TestEnv')
-::TestEnv.extend_if_ee('::EE::TestEnv')
+::TestEnv.prepend_mod_with('TestEnv')
+::TestEnv.extend_mod_with('TestEnv')
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index d05676a649e..c6176b5bcbc 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -98,7 +98,6 @@ module UsageDataHelpers
projects_prometheus_active
projects_with_repositories_enabled
projects_with_error_tracking_enabled
- projects_with_alerts_service_enabled
projects_with_enabled_alert_integrations
projects_with_prometheus_alerts
projects_with_tracing_enabled
diff --git a/spec/support/matchers/access_matchers_generic.rb b/spec/support/matchers/access_matchers_generic.rb
index 13955750f4f..a38a83a2547 100644
--- a/spec/support/matchers/access_matchers_generic.rb
+++ b/spec/support/matchers/access_matchers_generic.rb
@@ -35,7 +35,7 @@ module AccessMatchersGeneric
run_matcher(action, role, @membership, @owned_objects) do |action|
action.call
- rescue => e
+ rescue StandardError => e
@error = e
raise unless e.is_a?(ERROR_CLASS)
end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 47cffad8c41..dfdb5bc01ae 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -291,4 +291,4 @@ module RSpec::Matchers::DSL::Macros
end
end
-MarkdownMatchers.prepend_if_ee('EE::MarkdownMatchers')
+MarkdownMatchers.prepend_mod_with('MarkdownMatchers')
diff --git a/spec/support/matchers/schema_matcher.rb b/spec/support/matchers/schema_matcher.rb
index f0e7a52c51e..94e4359b1dd 100644
--- a/spec/support/matchers/schema_matcher.rb
+++ b/spec/support/matchers/schema_matcher.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module SchemaPath
+ @schema_cache = {}
+
def self.expand(schema, dir = nil)
return schema unless schema.is_a?(String)
@@ -12,38 +14,56 @@ module SchemaPath
Rails.root.join(dir.to_s, 'spec', "fixtures/api/schemas/#{schema}.json").to_s
end
+
+ def self.validator(schema_path)
+ unless @schema_cache.key?(schema_path)
+ @schema_cache[schema_path] = JSONSchemer.schema(schema_path, ref_resolver: SchemaPath.file_ref_resolver)
+ end
+
+ @schema_cache[schema_path]
+ end
+
+ def self.file_ref_resolver
+ proc do |uri|
+ file = Rails.root.join(uri.path)
+ raise StandardError, "Ref file #{uri.path} must be json" unless uri.path.ends_with?('.json')
+ raise StandardError, "File #{file.to_path} doesn't exists" unless file.exist?
+
+ Gitlab::Json.parse(File.read(file))
+ end
+ end
end
RSpec::Matchers.define :match_response_schema do |schema, dir: nil, **options|
match do |response|
- @errors = JSON::Validator.fully_validate(
- SchemaPath.expand(schema, dir), response.body, options)
+ schema_path = Pathname.new(SchemaPath.expand(schema, dir))
+ validator = SchemaPath.validator(schema_path)
- @errors.empty?
- end
+ data = Gitlab::Json.parse(response.body)
- failure_message do |response|
- "didn't match the schema defined by #{SchemaPath.expand(schema, dir)}" \
- " The validation errors were:\n#{@errors.join("\n")}"
+ validator.valid?(data)
end
end
-RSpec::Matchers.define :match_schema do |schema, dir: nil, **options|
+RSpec::Matchers.define :match_snowplow_schema do |schema, dir: nil, **options|
match do |data|
- @errors = JSON::Validator.fully_validate(
- SchemaPath.expand(schema, dir), data, options)
-
- @errors.empty?
- end
+ schema_path = Pathname.new(Rails.root.join(dir.to_s, 'spec', "fixtures/product_intelligence/#{schema}.json").to_s)
+ validator = SchemaPath.validator(schema_path)
- failure_message do |response|
- "didn't match the schema defined by #{schema_name(schema, dir)}" \
- " The validation errors were:\n#{@errors.join("\n")}"
+ validator.valid?(data.stringify_keys)
end
+end
- def schema_name(schema, dir)
- return 'provided schema' unless schema.is_a?(String)
+RSpec::Matchers.define :match_schema do |schema, dir: nil, **options|
+ match do |data|
+ schema = SchemaPath.expand(schema, dir)
+ schema = Pathname.new(schema) if schema.is_a?(String)
+ validator = SchemaPath.validator(schema)
- SchemaPath.expand(schema, dir)
+ if data.is_a?(String)
+ validator.valid?(Gitlab::Json.parse(data))
+ else
+ validator.valid?(data)
+ end
end
end
diff --git a/spec/support/renameable_upload.rb b/spec/support/renameable_upload.rb
index f7f00181605..08e8d07ed4d 100644
--- a/spec/support/renameable_upload.rb
+++ b/spec/support/renameable_upload.rb
@@ -5,7 +5,7 @@ class RenameableUpload < SimpleDelegator
# Get a fixture file with a new unique name, and the same extension
def self.unique_file(name)
- upload = new(fixture_file_upload("spec/fixtures/#{name}"))
+ upload = new(Rack::Test::UploadedFile.new("spec/fixtures/#{name}"))
ext = File.extname(name)
new_name = File.basename(FactoryBot.generate(:filename), '.*')
upload.original_filename = new_name + ext
diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb
index 8867a1e3a90..4d2843af1c4 100644
--- a/spec/support/services/issuable_update_service_shared_examples.rb
+++ b/spec/support/services/issuable_update_service_shared_examples.rb
@@ -12,13 +12,13 @@ RSpec.shared_examples 'issuable update service' do
context 'to reopened' do
it 'executes hooks only once' do
- described_class.new(project, user, state_event: 'reopen').execute(closed_issuable)
+ described_class.new(project: project, current_user: user, params: { state_event: 'reopen' }).execute(closed_issuable)
end
end
context 'to closed' do
it 'executes hooks only once' do
- described_class.new(project, user, state_event: 'close').execute(open_issuable)
+ described_class.new(project: project, current_user: user, params: { state_event: 'close' }).execute(open_issuable)
end
end
end
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 78d14ecb880..4f8e88ae9da 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -24,16 +24,56 @@ RSpec.shared_context 'project navbar structure' do
}
end
+ let(:monitor_nav_item) do
+ {
+ nav_item: _('Operations'),
+ nav_sub_items: monitor_menu_items
+ }
+ end
+
+ let(:monitor_menu_items) do
+ [
+ _('Metrics'),
+ _('Logs'),
+ _('Tracing'),
+ _('Error Tracking'),
+ _('Alerts'),
+ _('Incidents'),
+ _('Serverless'),
+ _('Terraform'),
+ _('Kubernetes'),
+ _('Environments'),
+ _('Feature Flags'),
+ _('Product Analytics')
+ ]
+ end
+
+ let(:project_information_nav_item) do
+ {
+ nav_item: _('Project overview'),
+ nav_sub_items: [
+ _('Details'),
+ _('Activity'),
+ _('Releases')
+ ]
+ }
+ end
+
+ let(:settings_menu_items) do
+ [
+ _('General'),
+ _('Integrations'),
+ _('Webhooks'),
+ _('Access Tokens'),
+ _('Repository'),
+ _('CI/CD'),
+ _('Operations')
+ ]
+ end
+
let(:structure) do
[
- {
- nav_item: _('Project overview'),
- nav_sub_items: [
- _('Details'),
- _('Activity'),
- _('Releases')
- ]
- },
+ project_information_nav_item,
{
nav_item: _('Repository'),
nav_sub_items: [
@@ -52,7 +92,6 @@ RSpec.shared_context 'project navbar structure' do
nav_sub_items: [
_('List'),
_('Boards'),
- _('Labels'),
_('Service Desk'),
_('Milestones'),
(_('Iterations') if Gitlab.ee?)
@@ -73,23 +112,7 @@ RSpec.shared_context 'project navbar structure' do
]
},
security_and_compliance_nav_item,
- {
- nav_item: _('Operations'),
- nav_sub_items: [
- _('Metrics'),
- _('Logs'),
- _('Tracing'),
- _('Error Tracking'),
- _('Alerts'),
- _('Incidents'),
- _('Serverless'),
- _('Terraform'),
- _('Kubernetes'),
- _('Environments'),
- _('Feature Flags'),
- _('Product Analytics')
- ]
- },
+ monitor_nav_item,
analytics_nav_item,
{
nav_item: _('Wiki'),
@@ -100,20 +123,8 @@ RSpec.shared_context 'project navbar structure' do
nav_sub_items: []
},
{
- nav_item: _('Members'),
- nav_sub_items: []
- },
- {
nav_item: _('Settings'),
- nav_sub_items: [
- _('General'),
- _('Integrations'),
- _('Webhooks'),
- _('Access Tokens'),
- _('Repository'),
- _('CI/CD'),
- _('Operations')
- ].compact
+ nav_sub_items: settings_menu_items
}
].compact
end
@@ -124,8 +135,7 @@ RSpec.shared_context 'group navbar structure' do
{
nav_item: _('Analytics'),
nav_sub_items: [
- _('Contribution'),
- _('DevOps Adoption')
+ _('Contribution')
]
}
end
@@ -171,23 +181,31 @@ RSpec.shared_context 'group navbar structure' do
}
end
+ let(:group_information_nav_item) do
+ {
+ nav_item: _('Group information'),
+ nav_sub_items: [
+ _('Activity'),
+ _('Labels'),
+ _('Members')
+ ]
+ }
+ end
+
+ let(:issues_nav_items) do
+ [
+ _('List'),
+ _('Board'),
+ _('Milestones')
+ ]
+ end
+
let(:structure) do
[
- {
- nav_item: _('Group overview'),
- nav_sub_items: [
- _('Details'),
- _('Activity')
- ]
- },
+ group_information_nav_item,
{
nav_item: _('Issues'),
- nav_sub_items: [
- _('List'),
- _('Board'),
- _('Labels'),
- _('Milestones')
- ]
+ nav_sub_items: issues_nav_items
},
{
nav_item: _('Merge requests'),
@@ -199,11 +217,7 @@ RSpec.shared_context 'group navbar structure' do
nav_item: _('Kubernetes'),
nav_sub_items: []
},
- (analytics_nav_item if Gitlab.ee?),
- {
- nav_item: _('Members'),
- nav_sub_items: []
- }
+ (analytics_nav_item if Gitlab.ee?)
]
end
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 266c8d5ee84..35dc709b5d9 100644
--- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -15,7 +15,7 @@ RSpec.shared_context 'ProjectPolicy context' do
let(:base_guest_permissions) do
%i[
- award_emoji create_issue create_merge_request_in create_note
+ award_emoji create_issue create_incident 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_project_for_iids read_project_member read_release read_snippet
diff --git a/spec/support/shared_contexts/project_service_shared_context.rb b/spec/support/shared_contexts/project_service_shared_context.rb
index a8e75c624e8..0e3540a3e15 100644
--- a/spec/support/shared_contexts/project_service_shared_context.rb
+++ b/spec/support/shared_contexts/project_service_shared_context.rb
@@ -22,7 +22,7 @@ RSpec.shared_context 'project service activation' do
end
def click_active_checkbox
- find('input[name="service[active]"]').click
+ find('label', text: 'Active').click
end
def click_save_integration
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index f250632ff51..34c92367efa 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-Service.available_services_names.each do |service|
+Integration.available_services_names.each do |service|
RSpec.shared_context service do
include JiraServiceHelper if service == 'jira'
let(:dashed_service) { service.dasherize }
let(:service_method) { "#{service}_service".to_sym }
- let(:service_klass) { "#{service}_service".classify.constantize }
+ let(:service_klass) { Integration.service_name_to_model(service) }
let(:service_instance) { service_klass.new }
let(:service_fields) { service_instance.fields }
let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
@@ -30,6 +30,8 @@ Service.available_services_names.each do |service|
hash.merge!(k => '1,2,3')
elsif service == 'emails_on_push' && k == :recipients
hash.merge!(k => 'foo@bar.com')
+ elsif service == 'slack' || service == 'mattermost' && k == :labels_to_be_notified_behavior
+ hash.merge!(k => "match_any")
else
hash.merge!(k => "someword")
end
@@ -47,8 +49,9 @@ Service.available_services_names.each do |service|
stub_jira_service_test if service == 'jira'
end
- def initialize_service(service)
+ def initialize_service(service, attrs = {})
service_item = project.find_or_initialize_service(service)
+ service_item.attributes = attrs
service_item.properties = service_attrs
service_item.save!
service_item
diff --git a/spec/support/shared_contexts/spam_constants.rb b/spec/support/shared_contexts/spam_constants.rb
index 813f9d00123..e88a7c1b0df 100644
--- a/spec/support/shared_contexts/spam_constants.rb
+++ b/spec/support/shared_contexts/spam_constants.rb
@@ -6,5 +6,6 @@ RSpec.shared_context 'includes Spam constants' do
stub_const('DISALLOW', Spam::SpamConstants::DISALLOW)
stub_const('ALLOW', Spam::SpamConstants::ALLOW)
stub_const('BLOCK_USER', Spam::SpamConstants::BLOCK_USER)
+ stub_const('NOOP', Spam::SpamConstants::NOOP)
end
end
diff --git a/spec/support/shared_examples/alert_notification_service_shared_examples.rb b/spec/support/shared_examples/alert_notification_service_shared_examples.rb
deleted file mode 100644
index fc935effe0e..00000000000
--- a/spec/support/shared_examples/alert_notification_service_shared_examples.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'Alert Notification Service sends notification email' do
- let(:notification_service) { spy }
-
- it 'sends a notification' do
- expect(NotificationService)
- .to receive(:new)
- .and_return(notification_service)
-
- expect(notification_service)
- .to receive_message_chain(:async, :prometheus_alerts_fired)
-
- expect(subject).to be_success
- end
-end
-
-RSpec.shared_examples 'Alert Notification Service sends no notifications' do |http_status: nil|
- it 'does not notify' do
- expect(NotificationService).not_to receive(:new)
-
- if http_status.present?
- expect(subject).to be_error
- expect(subject.http_status).to eq(http_status)
- else
- expect(subject).to be_success
- end
- end
-end
-
-RSpec.shared_examples 'creates status-change system note for an auto-resolved alert' do
- it 'has 2 new system notes' do
- expect { subject }.to change(Note, :count).by(2)
- expect(Note.last.note).to include('Resolved')
- end
-end
-
-# Requires `source` to be defined
-RSpec.shared_examples 'creates single system note based on the source of the alert' do
- it 'has one new system note' do
- expect { subject }.to change(Note, :count).by(1)
- expect(Note.last.note).to include(source)
- end
-end
diff --git a/spec/support/shared_examples/boards/lists/update_service_shared_examples.rb b/spec/support/shared_examples/boards/lists/update_service_shared_examples.rb
index d8a74f2582d..1fab31cd513 100644
--- a/spec/support/shared_examples/boards/lists/update_service_shared_examples.rb
+++ b/spec/support/shared_examples/boards/lists/update_service_shared_examples.rb
@@ -2,14 +2,30 @@
RSpec.shared_examples 'moving list' do
context 'when user can admin list' do
- it 'calls Lists::MoveService to update list position' do
+ before do
board.resource_parent.add_developer(user)
+ end
+
+ context 'when the new position is valid' do
+ it 'calls Lists::MoveService to update list position' do
+ expect_next_instance_of(Boards::Lists::MoveService, board.resource_parent, user, params) do |move_service|
+ expect(move_service).to receive(:execute).with(list).and_call_original
+ end
- expect_next_instance_of(Boards::Lists::MoveService, board.resource_parent, user, params) do |move_service|
- expect(move_service).to receive(:execute).with(list).and_call_original
+ service.execute(list)
end
- service.execute(list)
+ it 'returns a success response' do
+ expect(service.execute(list)).to be_success
+ end
+ end
+
+ context 'when the new position is invalid' do
+ let(:params) { { position: 10 } }
+
+ it 'returns error response' do
+ expect(service.execute(list)).to be_error
+ end
end
end
@@ -19,6 +35,10 @@ RSpec.shared_examples 'moving list' do
service.execute(list)
end
+
+ it 'returns an error response' do
+ expect(service.execute(list)).to be_error
+ end
end
end
diff --git a/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb
index dd71107455f..70a684c12bf 100644
--- a/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/access_tokens_controller_shared_examples.rb
@@ -40,7 +40,7 @@ RSpec.shared_examples 'project access tokens available #create' do
it 'returns success message' do
subject
- expect(response.flash[:notice]).to match('Your new project access token has been created.')
+ expect(controller).to set_flash[:notice].to match('Your new project access token has been created.')
end
it 'creates project access token' do
@@ -88,7 +88,7 @@ RSpec.shared_examples 'project access tokens available #create' do
it 'shows a failure alert' do
subject
- expect(response.flash[:alert]).to match("Failed to create new project access token: Failed!")
+ expect(controller).to set_flash[:alert].to match("Failed to create new project access token: Failed!")
end
end
end
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index 5ecc5c08bbd..a4eb6a839c0 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -35,7 +35,7 @@ RSpec.shared_examples 'issuable notes filter' do
get :discussions, params: params.merge(notes_filter: notes_filter)
end
- it 'does not set notes filter when database is in read only mode' do
+ it 'does not set notes filter when database is in read-only mode' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
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 0a040557ffe..cfee26a0d6a 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -130,8 +130,8 @@ RSpec.shared_examples 'wiki controller actions' do
it_behaves_like 'fetching history', :ok do
let(:allow_read_wiki) { true }
- it 'assigns @page_versions' do
- expect(assigns(:page_versions)).to be_present
+ it 'assigns @commits' do
+ expect(assigns(:commits)).to be_present
end
end
diff --git a/spec/support/shared_examples/features/board_sidebar_labels_examples.rb b/spec/support/shared_examples/features/board_sidebar_labels_examples.rb
new file mode 100644
index 00000000000..520980c2615
--- /dev/null
+++ b/spec/support/shared_examples/features/board_sidebar_labels_examples.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'labels from nested groups and projects' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:group_label) { create(:group_label, group: group, name: 'Group label') }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:project_label) { create(:label, project: project, name: 'Project label') }
+
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let_it_be(:subgroup_label) { create(:group_label, group: subgroup, name: 'Subgroup label') }
+ let_it_be(:subproject) { create(:project, group: subgroup) }
+ let_it_be(:subproject_label) { create(:label, project: subproject, name: 'Subproject label') }
+
+ let_it_be(:subgroup2) { create(:group, parent: group) }
+ let_it_be(:subgroup2_label) { create(:group_label, group: subgroup2, name: 'Subgroup2 label') }
+
+ let_it_be(:maintainer) { create(:user) }
+
+ let(:labels_select) { find("[data-testid='sidebar-labels']") }
+ let(:labels_dropdown) { labels_select.find('[data-testid="dropdown-content"]')}
+
+ before do
+ group.add_maintainer(maintainer)
+
+ sign_in(maintainer)
+ end
+end
+
+RSpec.shared_examples "an issue from a subgroup's project is selected" do
+ context 'when editing labels' do
+ before do
+ click_card_and_edit_label
+ end
+
+ it 'displays the label from the top-level group' do
+ expect(labels_dropdown).to have_content(group_label.name)
+ end
+
+ it 'displays the label from the subgroup' do
+ expect(labels_dropdown).to have_content(subgroup_label.name)
+ end
+
+ it 'displays the label from the project' do
+ expect(labels_dropdown).to have_content(subproject_label.name)
+ end
+
+ it "does not display labels from the subgroup's siblings (project or group)" do
+ aggregate_failures do
+ expect(labels_dropdown).not_to have_content(project_label.name)
+ expect(labels_dropdown).not_to have_content(subgroup2_label.name)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'an issue from a direct descendant project is selected' do
+ context 'when editing labels' do
+ before do
+ click_card_and_edit_label
+ end
+
+ it 'displays the label from the top-level group' do
+ expect(labels_dropdown).to have_content(group_label.name)
+ end
+
+ it 'displays the label from the project' do
+ expect(labels_dropdown).to have_content(project_label.name)
+ end
+
+ it "does not display labels from the project's siblings or their descendents" do
+ aggregate_failures do
+ expect(labels_dropdown).not_to have_content(subgroup_label.name)
+ expect(labels_dropdown).not_to have_content(subproject_label.name)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
index 49c3674277d..736c353c2aa 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
@@ -22,32 +22,7 @@ RSpec.shared_examples 'issuable invite members experiments' do
end
end
- context 'when invite_members_version_b experiment is enabled' do
- before do
- stub_experiment_for_subject(invite_members_version_b: true)
- end
-
- it 'shows a link for inviting members and follows through to modal' do
- project.add_developer(user)
- visit issuable_path
-
- find('.block.assignee .edit-link').click
-
- wait_for_requests
-
- page.within '.dropdown-menu-user' do
- expect(page).to have_link('Invite Members', href: '#')
- expect(page).to have_selector('[data-track-event="click_invite_members_version_b"]')
- expect(page).to have_selector('[data-track-label="edit_assignee"]')
- end
-
- click_link 'Invite Members'
-
- expect(page).to have_content("Oops, this feature isn't ready yet")
- end
- end
-
- context 'when invite_members_version_b experiment is disabled' do
+ context 'when user cannot invite members in assignee dropdown' do
it 'shows author in assignee dropdown and no invite link' do
project.add_developer(user)
visit issuable_path
diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb
index 429efbe6ba0..c9508818f74 100644
--- a/spec/support/shared_examples/features/sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb
@@ -44,22 +44,24 @@ RSpec.shared_examples 'issue boards sidebar' do
context 'in notifications subscription' do
it 'displays notifications toggle', :aggregate_failures do
page.within('[data-testid="sidebar-notifications"]') do
- expect(page).to have_selector('[data-testid="notification-subscribe-toggle"]')
+ expect(page).to have_selector('[data-testid="subscription-toggle"]')
expect(page).to have_content('Notifications')
- expect(page).not_to have_content('Notifications have been disabled by the project or group owner')
+ expect(page).not_to have_content('Disabled by project owner')
end
end
it 'shows toggle as on then as off as user toggles to subscribe and unsubscribe', :aggregate_failures do
- toggle = find('[data-testid="notification-subscribe-toggle"]')
+ wait_for_requests
- toggle.click
+ click_button 'Notifications'
- expect(toggle).to have_css("button.is-checked")
+ expect(page).to have_button('Notifications', class: 'is-checked')
- toggle.click
+ click_button 'Notifications'
- expect(toggle).not_to have_css("button.is-checked")
+ wait_for_requests
+
+ expect(page).not_to have_button('Notifications', class: 'is-checked')
end
context 'when notifications have been disabled' do
@@ -71,9 +73,28 @@ RSpec.shared_examples 'issue boards sidebar' do
it 'displays a message that notifications have been disabled' do
page.within('[data-testid="sidebar-notifications"]') do
- expect(page).not_to have_selector('[data-testid="notification-subscribe-toggle"]')
- expect(page).to have_content('Notifications have been disabled by the project or group owner')
+ expect(page).to have_button('Notifications', class: 'is-disabled')
+ expect(page).to have_content('Disabled by project owner')
+ end
+ end
+ end
+ end
+
+ context 'confidentiality' do
+ it 'make issue confidential' do
+ page.within('.confidentiality') do
+ expect(page).to have_content('Not confidential')
+
+ click_button 'Edit'
+ expect(page).to have_css('.sidebar-item-warning-message')
+
+ within('.sidebar-item-warning-message') do
+ click_button 'Turn on'
end
+
+ wait_for_requests
+
+ expect(page).to have_content('This issue is confidential')
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 2fd88b610e9..4b94411f009 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -8,7 +8,7 @@ RSpec.shared_examples 'variable list' do
end
it 'adds a new CI variable' do
- click_button('Add Variable')
+ click_button('Add variable')
fill_variable('key', 'key_value') do
click_button('Add variable')
@@ -22,7 +22,7 @@ RSpec.shared_examples 'variable list' do
end
it 'adds a new protected variable' do
- click_button('Add Variable')
+ click_button('Add variable')
fill_variable('key', 'key_value') do
click_button('Add variable')
@@ -37,7 +37,7 @@ RSpec.shared_examples 'variable list' do
end
it 'defaults to unmasked' do
- click_button('Add Variable')
+ click_button('Add variable')
fill_variable('key', 'key_value') do
click_button('Add variable')
@@ -149,7 +149,7 @@ RSpec.shared_examples 'variable list' do
end
it 'shows a validation error box about duplicate keys' do
- click_button('Add Variable')
+ click_button('Add variable')
fill_variable('key', 'key_value') do
click_button('Add variable')
@@ -157,7 +157,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
- click_button('Add Variable')
+ click_button('Add variable')
fill_variable('key', 'key_value') do
click_button('Add variable')
@@ -170,7 +170,7 @@ RSpec.shared_examples 'variable list' do
end
it 'prevents a variable to be added if no values are provided when a variable is set to masked' do
- click_button('Add Variable')
+ click_button('Add variable')
page.within('#add-ci-variable') do
find('[data-qa-selector="ci_variable_key_field"] input').set('empty_mask_key')
@@ -182,7 +182,7 @@ RSpec.shared_examples 'variable list' do
end
it 'shows validation error box about unmaskable values' do
- click_button('Add Variable')
+ click_button('Add variable')
fill_variable('empty_mask_key', '???', protected: true, masked: true) do
expect(page).to have_content('This variable can not be masked')
@@ -192,7 +192,7 @@ RSpec.shared_examples 'variable list' do
it 'handles multiple edits and a deletion' do
# Create two variables
- click_button('Add Variable')
+ click_button('Add variable')
fill_variable('akey', 'akeyvalue') do
click_button('Add variable')
@@ -200,7 +200,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
- click_button('Add Variable')
+ click_button('Add variable')
fill_variable('zkey', 'zkeyvalue') do
click_button('Add variable')
@@ -224,7 +224,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
# Add another variable
- click_button('Add Variable')
+ click_button('Add variable')
fill_variable('ckey', 'ckeyvalue') do
click_button('Add variable')
@@ -249,7 +249,7 @@ RSpec.shared_examples 'variable list' do
end
it 'defaults to protected' do
- click_button('Add Variable')
+ click_button('Add variable')
page.within('#add-ci-variable') do
expect(find('[data-testid="ci-variable-protected-checkbox"]')).to be_checked
@@ -269,7 +269,7 @@ RSpec.shared_examples 'variable list' do
end
it 'defaults to unprotected' do
- click_button('Add Variable')
+ click_button('Add variable')
page.within('#add-ci-variable') do
expect(find('[data-testid="ci-variable-protected-checkbox"]')).not_to be_checked
diff --git a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
index 8a6d5d88ca6..f2576931642 100644
--- a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
@@ -24,8 +24,8 @@ RSpec.shared_examples 'User creates wiki page' do
page.within(".wiki-form") do
fill_in(:wiki_content, with: "")
- page.execute_script("window.onbeforeunload = null")
page.execute_script("document.querySelector('.wiki-form').submit()")
+ page.accept_alert # manually force form submit
end
expect(page).to have_content("The form contains the following error:").and have_content("Content can't be blank")
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 d185e9dd81c..db2a96d9649 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
@@ -93,8 +93,8 @@ RSpec.shared_examples 'User updates wiki page' do
it 'shows a validation error message if the form is force submitted', :js do
fill_in(:wiki_content, with: '')
- page.execute_script("window.onbeforeunload = null")
page.execute_script("document.querySelector('.wiki-form').submit()")
+ page.accept_alert # manually force form submit
expect(page).to have_selector('.wiki-form')
expect(page).to have_content('Edit Page')
@@ -117,14 +117,6 @@ RSpec.shared_examples 'User updates wiki page' do
expect(page).to have_selector('.atwho-view')
end
- it 'shows the error message', :js do
- wiki_page.update(content: 'Update') # rubocop:disable Rails/SaveBang
-
- click_button('Save changes')
-
- expect(page).to have_content('Someone edited the page the same time you did.')
- end
-
it 'updates a page', :js do
fill_in('Content', with: 'Updated Wiki Content')
click_on('Save changes')
@@ -145,6 +137,18 @@ RSpec.shared_examples 'User updates wiki page' do
end
it_behaves_like 'wiki file attachments'
+
+ context 'when multiple people edit the page at the same time' do
+ it 'preserves user changes in the wiki editor', :js do
+ wiki_page.update(content: 'Some Other Updates') # rubocop:disable Rails/SaveBang
+
+ fill_in('Content', with: 'Updated Wiki Content')
+ click_on('Save changes')
+
+ expect(page).to have_content('Someone edited the page the same time you did.')
+ expect(find('textarea#wiki_content').value).to eq('Updated Wiki Content')
+ end
+ end
end
context 'when the page is in a subdir', :js do
diff --git a/spec/support/shared_examples/finders/packages_shared_examples.rb b/spec/support/shared_examples/finders/packages_shared_examples.rb
index 2d4e8d0df1f..b3ec2336cca 100644
--- a/spec/support/shared_examples/finders/packages_shared_examples.rb
+++ b/spec/support/shared_examples/finders/packages_shared_examples.rb
@@ -20,9 +20,11 @@ end
RSpec.shared_examples 'concerning package statuses' do
let_it_be(:hidden_package) { create(:maven_package, :hidden, project: project) }
+ let_it_be(:error_package) { create(:maven_package, :error, project: project) }
- context 'hidden packages' do
+ context 'displayable packages' do
it { is_expected.not_to include(hidden_package) }
+ it { is_expected.to include(error_package) }
end
context 'with status param' do
diff --git a/spec/support/shared_examples/graphql/mutations/boards/update_list_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/boards/update_list_shared_examples.rb
new file mode 100644
index 00000000000..4385cd519be
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/boards/update_list_shared_examples.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'update board list mutation' do
+ describe '#resolve' do
+ let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
+ let(:list_update_params) { { position: 1, collapsed: true } }
+
+ subject { mutation.resolve(list: list, **list_update_params) }
+
+ before_all do
+ group.add_reporter(reporter)
+ group.add_guest(guest)
+ list.update_preferences_for(reporter, collapsed: false)
+ end
+
+ context 'with permission to admin board lists' do
+ let(:current_user) { reporter }
+
+ it 'updates the list position and collapsed state as expected' do
+ subject
+
+ reloaded_list = list.reload
+ expect(reloaded_list.position).to eq(1)
+ expect(reloaded_list.collapsed?(current_user)).to eq(true)
+ end
+ end
+
+ context 'with permission to read board lists' do
+ let(:current_user) { guest }
+
+ it 'updates the list collapsed state but not the list position' do
+ subject
+
+ reloaded_list = list.reload
+ expect(reloaded_list.position).to eq(0)
+ expect(reloaded_list.collapsed?(current_user)).to eq(true)
+ end
+ end
+
+ context 'without permission to read board lists' do
+ let(:current_user) { create(:user) }
+
+ it 'raises Resource Not Found error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ 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
new file mode 100644
index 00000000000..2bb3d807aa7
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples_for 'graphql mutations security ci configuration' do
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ let(:branch) do
+ "set-secret-config"
+ end
+
+ let(:success_path) do
+ "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?"
+ end
+
+ let(:service_response) do
+ ServiceResponse.success(payload: { branch: branch, success_path: success_path })
+ end
+
+ let(:error) { "An error occured!" }
+
+ let(:service_error_response) do
+ ServiceResponse.error(message: error)
+ end
+
+ specify { expect(described_class).to require_graphql_authorizations(:push_code) }
+
+ describe '#resolve' do
+ let(:result) { subject }
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when user does not have enough permissions' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when user is a maintainer of a different project' do
+ before do
+ create(:project_empty_repo).add_maintainer(user)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when the user does not have permission to create a new branch' do
+ let(:error_message) { 'You are not allowed to create protected branches on this project.' }
+
+ before do
+ project.add_developer(user)
+
+ allow_next_instance_of(::Files::MultiService) do |multi_service|
+ allow(multi_service).to receive(:execute).and_raise(Gitlab::Git::PreReceiveError.new("GitLab: #{error_message}"))
+ end
+ end
+
+ it 'returns an array of errors' do
+ expect(result).to match(
+ branch: be_nil,
+ success_path: be_nil,
+ errors: match_array([error_message])
+ )
+ end
+ end
+
+ context 'when the user can create a merge request' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when service successfully generates a path to create a new merge request' do
+ before do
+ allow_next_instance_of(service) do |service|
+ allow(service).to receive(:execute).and_return(service_response)
+ end
+ end
+
+ it 'returns a success path' do
+ expect(result).to match(
+ branch: branch,
+ success_path: success_path,
+ errors: []
+ )
+ end
+ end
+
+ context 'when service can not generate any path to create a new merge request' do
+ before do
+ allow_next_instance_of(service) do |service|
+ allow(service).to receive(:execute).and_return(service_error_response)
+ end
+ end
+
+ it 'returns an array of errors' do
+ expect(result).to match(
+ branch: be_nil,
+ success_path: be_nil,
+ errors: match_array([error])
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb b/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
new file mode 100644
index 00000000000..3d6fec85490
--- /dev/null
+++ b/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'group and projects packages resolver' do
+ context 'without sort' do
+ let_it_be(:npm_package) { create(:package, project: project) }
+
+ it { is_expected.to contain_exactly(npm_package) }
+ end
+
+ context 'with sorting and filtering' do
+ let_it_be(:conan_package) do
+ create(:conan_package, name: 'bar', project: project, created_at: 1.day.ago, version: "1.0.0", status: 'default')
+ end
+
+ let_it_be(:maven_package) do
+ create(:maven_package, name: 'foo', project: project, created_at: 1.hour.ago, version: "2.0.0", status: 'error')
+ end
+
+ let_it_be(:repository3) do
+ create(:maven_package, name: 'baz', project: project, created_at: 1.minute.ago, version: nil)
+ end
+
+ [:created_desc, :name_desc, :version_desc, :type_asc].each do |order|
+ context "#{order}" do
+ let(:args) { { sort: order } }
+
+ it { is_expected.to eq([maven_package, conan_package]) }
+ end
+ end
+
+ [:created_asc, :name_asc, :version_asc, :type_desc].each do |order|
+ context "#{order}" do
+ let(:args) { { sort: order } }
+
+ it { is_expected.to eq([conan_package, maven_package]) }
+ end
+ end
+
+ context 'filter by package_name' do
+ let(:args) { { package_name: 'bar', sort: :created_desc } }
+
+ it { is_expected.to eq([conan_package]) }
+ end
+
+ context 'filter by package_type' do
+ let(:args) { { package_type: 'conan', sort: :created_desc } }
+
+ it { is_expected.to eq([conan_package]) }
+ end
+
+ context 'filter by status' do
+ let(:args) { { status: 'error', sort: :created_desc } }
+
+ it { is_expected.to eq([maven_package]) }
+ end
+
+ context 'include_versionless' do
+ let(:args) { { include_versionless: true, sort: :created_desc } }
+
+ it { is_expected.to include(repository3) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
index c9e03ced0dd..1f7325df11a 100644
--- a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
@@ -166,16 +166,6 @@ shared_examples_for 'sortable diff files' do
it 'returns sorted diff files' do
expect(raw_diff_files_paths).to eq(sorted_diff_files_paths)
end
-
- context 'when sort_diffs feature flag is disabled' do
- before do
- stub_feature_flags(sort_diffs: false)
- end
-
- it 'returns unsorted diff files' do
- expect(raw_diff_files_paths).to eq(unsorted_diff_files_paths)
- end
- end
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/jwt_token_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/jwt_token_shared_examples.rb
new file mode 100644
index 00000000000..5c92bb3b0d4
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/jwt_token_shared_examples.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a gitlab jwt token' do
+ let_it_be(:base_secret) { SecureRandom.base64(64) }
+
+ let(:jwt_secret) do
+ OpenSSL::HMAC.hexdigest(
+ 'SHA256',
+ base_secret,
+ described_class::HMAC_KEY
+ )
+ end
+
+ before do
+ allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret)
+ end
+
+ describe '#secret' do
+ subject { described_class.secret }
+
+ it { is_expected.to eq(jwt_secret) }
+ end
+
+ describe '#decode' do
+ let(:encoded_jwt_token) { jwt_token.encoded }
+
+ subject(:decoded_jwt_token) { described_class.decode(encoded_jwt_token) }
+
+ context 'with a custom payload' do
+ let(:personal_access_token) { create(:personal_access_token) }
+ let(:jwt_token) { described_class.new.tap { |jwt_token| jwt_token['token'] = personal_access_token.token } }
+
+ it 'returns the correct token' do
+ expect(decoded_jwt_token['token']).to eq jwt_token['token']
+ end
+
+ it 'returns nil and logs the exception after expiration' do
+ travel_to((described_class::HMAC_EXPIRES_IN + 1.minute).ago) do
+ encoded_jwt_token
+ end
+
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ .with(instance_of(JWT::ExpiredSignature))
+
+ expect(decoded_jwt_token).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb
index aa6a51c3646..8d758ed1655 100644
--- a/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb
@@ -21,7 +21,7 @@ RSpec.shared_examples 'SQL set operator' do |operator_keyword|
expect(set_operator.to_sql).to eq("(#{to_sql(relation_1)})\n#{operator_keyword}\n(#{to_sql(relation_2)})")
end
- it 'skips Model.none segements' do
+ it 'skips Model.none segments' do
empty_relation = User.none
set_operator = described_class.new([empty_relation, relation_1, relation_2])
diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb
index 59e249bb865..4a47aad0957 100644
--- a/spec/support/shared_examples/models/chat_service_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb
@@ -163,7 +163,7 @@ RSpec.shared_examples "chat service" do |service_name|
context "with issue events" do
let(:opts) { { title: "Awesome issue", description: "please fix" } }
let(:sample_data) do
- service = Issues::CreateService.new(project, user, opts)
+ service = Issues::CreateService.new(project: project, current_user: user, params: opts)
issue = service.execute
service.hook_data(issue, "open")
end
@@ -182,7 +182,7 @@ RSpec.shared_examples "chat service" do |service_name|
end
let(:sample_data) do
- service = MergeRequests::CreateService.new(project, user, opts)
+ service = MergeRequests::CreateService.new(project: project, current_user: user, params: opts)
merge_request = service.execute
service.hook_data(merge_request, "open")
end
diff --git a/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb
index 0ee24dd93d7..49729afce61 100644
--- a/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb
@@ -81,7 +81,7 @@ RSpec.shared_examples 'chat slash commands service' do
end
context 'when the user is authenticated' do
- let!(:chat_name) { create(:chat_name, service: subject) }
+ let!(:chat_name) { create(:chat_name, integration: subject) }
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/clusters/elastic_stack_client_shared.rb b/spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb
new file mode 100644
index 00000000000..d3ce916cd64
--- /dev/null
+++ b/spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+# Input
+# - factory: [:clusters_applications_elastic_stack, :clusters_integrations_elastic_stack]
+RSpec.shared_examples 'cluster-based #elasticsearch_client' do |factory|
+ describe '#elasticsearch_client' do
+ context 'cluster is nil' do
+ subject { build(factory, cluster: nil) }
+
+ it 'returns nil' do
+ expect(subject.cluster).to be_nil
+ expect(subject.elasticsearch_client).to be_nil
+ end
+ end
+
+ context "cluster doesn't have kubeclient" do
+ let(:cluster) { create(:cluster) }
+
+ subject { create(factory, cluster: cluster) }
+
+ it 'returns nil' do
+ expect(subject.elasticsearch_client).to be_nil
+ end
+ end
+
+ context 'cluster has kubeclient' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url }
+ let(:kube_client) { subject.cluster.kubeclient.core_client }
+
+ subject { create(factory, cluster: cluster) }
+
+ before 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)
+ end
+
+ it 'creates proxy elasticsearch_client' do
+ expect(subject.elasticsearch_client).to be_instance_of(Elasticsearch::Transport::Client)
+ end
+
+ it 'copies proxy_url, options and headers from kube client to elasticsearch_client' do
+ expect(Elasticsearch::Client)
+ .to(receive(:new))
+ .with(url: a_valid_url)
+ .and_call_original
+
+ client = subject.elasticsearch_client
+ faraday_connection = client.transport.connections.first.connection
+
+ expect(faraday_connection.headers["Authorization"]).to eq(kube_client.headers[:Authorization])
+ expect(faraday_connection.ssl.cert_store).to be_instance_of(OpenSSL::X509::Store)
+ expect(faraday_connection.ssl.verify).to eq(1)
+ expect(faraday_connection.options.timeout).to be_nil
+ end
+
+ context 'when cluster is not reachable' do
+ before do
+ allow(kube_client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
+ end
+
+ it 'returns nil' do
+ expect(subject.elasticsearch_client).to be_nil
+ end
+ end
+
+ context 'when timeout is provided' do
+ it 'sets timeout in elasticsearch_client' do
+ client = subject.elasticsearch_client(timeout: 123)
+ faraday_connection = client.transport.connections.first.connection
+
+ expect(faraday_connection.options.timeout).to eq(123)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb b/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
index 3db5d7a8d7d..ec9756007f1 100644
--- a/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
@@ -30,10 +30,6 @@ RSpec.shared_examples 'a BulkInsertSafe model' do |klass|
expect { target_class.set_callback(name) {} }.not_to raise_error
end
end
-
- it 'does not raise an error when the call is triggered by belongs_to' do
- expect { target_class.belongs_to(:other_record) }.not_to raise_error
- end
end
describe '.bulk_insert!' do
diff --git a/spec/support/shared_examples/models/concerns/cron_schedulable_shared_examples.rb b/spec/support/shared_examples/models/concerns/cron_schedulable_shared_examples.rb
new file mode 100644
index 00000000000..47a02a8fa49
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/cron_schedulable_shared_examples.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'handles set_next_run_at' do
+ context 'when schedule runs every minute' do
+ it "updates next_run_at to the worker's execution time" do
+ travel_to(1.day.ago) do
+ expect(schedule.next_run_at).to eq(cron_worker_next_run_at)
+ end
+ end
+ end
+
+ context 'when there are two different schedules in the same time zones' do
+ it 'sets the sames next_run_at' do
+ expect(schedule_1.next_run_at).to eq(schedule_2.next_run_at)
+ end
+ end
+
+ context 'when cron is updated for existing schedules' do
+ it 'updates next_run_at automatically' do
+ expect { schedule.update!(cron: new_cron) }.to change { schedule.next_run_at }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb b/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
index 819cf6018fe..3f1588c46b3 100644
--- a/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
@@ -36,7 +36,7 @@ RSpec.shared_examples 'handles repository moves' do
container.set_repository_read_only!
expect(subject).not_to be_valid
- expect(subject.errors[error_key].first).to match(/is read only/)
+ expect(subject.errors[error_key].first).to match(/is read-only/)
end
end
end
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 17948d648cb..d23f95b2e9e 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
@@ -58,6 +58,19 @@ RSpec.shared_examples 'value stream analytics stage' do
it { expect(stage).not_to be_valid }
end
+
+ # rubocop: disable Rails/SaveBang
+ describe '.by_value_stream' do
+ it 'finds stages by value stream' do
+ stage1 = create(factory)
+ create(factory) # other stage with different value stream
+
+ result = described_class.by_value_stream(stage1.value_stream)
+
+ expect(result).to eq([stage1])
+ end
+ end
+ # rubocop: enable Rails/SaveBang
end
describe '#subject_class' do
diff --git a/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb
index fbb94b4f5c1..33a04059491 100644
--- a/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb
+++ b/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.shared_examples 'Debian Distribution Architecture' do |factory, container, can_freeze|
- let_it_be_with_refind(:architecture) { create(factory) } # rubocop:disable Rails/SaveBang
- let_it_be(:architecture_same_distribution, freeze: can_freeze) { create(factory, distribution: architecture.distribution) }
+ let_it_be_with_refind(:architecture) { create(factory, name: 'name1') }
+ let_it_be(:architecture_same_distribution, freeze: can_freeze) { create(factory, distribution: architecture.distribution, name: 'name2') }
let_it_be(:architecture_same_name, freeze: can_freeze) { create(factory, name: architecture.name) }
subject { architecture }
@@ -30,20 +30,22 @@ RSpec.shared_examples 'Debian Distribution Architecture' do |factory, container,
end
describe 'scopes' do
+ describe '.ordered_by_name' do
+ subject { described_class.with_distribution(architecture.distribution).ordered_by_name }
+
+ it { expect(subject).to match_array([architecture, architecture_same_distribution]) }
+ end
+
describe '.with_distribution' do
subject { described_class.with_distribution(architecture.distribution) }
- it 'does not return other distributions' do
- expect(subject.to_a).to match_array([architecture, architecture_same_distribution])
- end
+ it { expect(subject).to match_array([architecture, architecture_same_distribution]) }
end
describe '.with_name' do
subject { described_class.with_name(architecture.name) }
- it 'does not return other distributions' do
- expect(subject.to_a).to match_array([architecture, architecture_same_name])
- end
+ it { expect(subject).to match_array([architecture, architecture_same_name]) }
end
end
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 02ced49ee94..e6b16d5881d 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
@@ -114,11 +114,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
subject { described_class.with_container(container2) }
it do
- queries = ActiveRecord::QueryRecorder.new do
- expect(subject.to_a).to contain_exactly(component_file_other_container)
- end
-
- expect(queries.count).to eq(1)
+ expect(subject.to_a).to contain_exactly(component_file_other_container)
end
end
@@ -126,11 +122,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
subject { described_class.with_codename_or_suite(distribution2.codename) }
it do
- queries = ActiveRecord::QueryRecorder.new do
- expect(subject.to_a).to contain_exactly(component_file_other_container)
- end
-
- expect(queries.count).to eq(1)
+ expect(subject.to_a).to contain_exactly(component_file_other_container)
end
end
@@ -138,11 +130,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
subject { described_class.with_component_name(component1_2.name) }
it do
- queries = ActiveRecord::QueryRecorder.new do
- expect(subject.to_a).to contain_exactly(component_file_other_component)
- end
-
- expect(queries.count).to eq(1)
+ expect(subject.to_a).to contain_exactly(component_file_other_component)
end
end
@@ -150,14 +138,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
subject { described_class.with_file_type(:source) }
it do
- # let_it_be_with_refind triggers a query
- component_file_with_file_type_source
-
- queries = ActiveRecord::QueryRecorder.new do
- expect(subject.to_a).to contain_exactly(component_file_with_file_type_source)
- end
-
- expect(queries.count).to eq(1)
+ expect(subject.to_a).to contain_exactly(component_file_with_file_type_source)
end
end
@@ -165,11 +146,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
subject { described_class.with_architecture_name(architecture1_2.name) }
it do
- queries = ActiveRecord::QueryRecorder.new do
- expect(subject.to_a).to contain_exactly(component_file_other_architecture)
- end
-
- expect(queries.count).to eq(1)
+ expect(subject.to_a).to contain_exactly(component_file_other_architecture)
end
end
@@ -177,11 +154,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
subject { described_class.with_compression_type(:xz) }
it do
- queries = ActiveRecord::QueryRecorder.new do
- expect(subject.to_a).to contain_exactly(component_file_other_compression_type)
- end
-
- expect(queries.count).to eq(1)
+ expect(subject.to_a).to contain_exactly(component_file_other_compression_type)
end
end
@@ -189,11 +162,19 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
subject { described_class.with_file_sha256('other_sha256') }
it do
- queries = ActiveRecord::QueryRecorder.new do
- expect(subject.to_a).to contain_exactly(component_file_other_file_sha256)
- end
+ expect(subject.to_a).to contain_exactly(component_file_other_file_sha256)
+ end
+ end
+
+ describe '.created_before' do
+ let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 4.hours.ago) }
+ let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 3.hours.ago) }
+ let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 1.hour.ago) }
- expect(queries.count).to eq(1)
+ subject { described_class.created_before(2.hours.ago) }
+
+ it do
+ expect(subject.to_a).to contain_exactly(component_file1, component_file2)
end
end
end
diff --git a/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb
index 23e76d32fb0..635d45f40e5 100644
--- a/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb
+++ b/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.shared_examples 'Debian Distribution Component' do |factory, container, can_freeze|
- let_it_be_with_refind(:component) { create(factory) } # rubocop:disable Rails/SaveBang
- let_it_be(:component_same_distribution, freeze: can_freeze) { create(factory, distribution: component.distribution) }
+ let_it_be_with_refind(:component) { create(factory, name: 'name1') }
+ let_it_be(:component_same_distribution, freeze: can_freeze) { create(factory, distribution: component.distribution, name: 'name2') }
let_it_be(:component_same_name, freeze: can_freeze) { create(factory, name: component.name) }
subject { component }
@@ -32,6 +32,14 @@ RSpec.shared_examples 'Debian Distribution Component' do |factory, container, ca
end
describe 'scopes' do
+ describe '.ordered_by_name' do
+ subject { described_class.with_distribution(component.distribution).ordered_by_name }
+
+ it 'sorts by name' do
+ expect(subject.to_a).to eq([component, component_same_distribution])
+ end
+ end
+
describe '.with_distribution' do
subject { described_class.with_distribution(component.distribution) }
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 9eacacf725f..8693d6868e9 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
@@ -19,11 +19,6 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze|
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) }
-
- if container != :group
- 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
end
describe 'validations' do
@@ -228,4 +223,44 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze|
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) }
+ it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile').through(:packages) }
+ end
+ 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)
+ 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
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb
index 71a76121d38..09b7d1be704 100644
--- a/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb
@@ -201,7 +201,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
context 'deployment events' do
let_it_be(:deployment) { create(:deployment) }
- let(:data) { Gitlab::DataBuilder::Deployment.build(deployment) }
+ let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.current) }
it_behaves_like 'calls the service API with the event message', /Deploy to (.*?) created/
end
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index 6b243aef3e6..2498bf35a09 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -469,34 +469,30 @@ RSpec.shared_examples 'wiki model' do
end
describe '#delete_page' do
- shared_examples 'delete_page operations' do
- let(:page) { create(:wiki_page, wiki: wiki) }
+ let(:page) { create(:wiki_page, wiki: wiki) }
- it 'deletes the page' do
- subject.delete_page(page)
+ it 'deletes the page' do
+ subject.delete_page(page)
- expect(subject.list_pages.count).to eq(0)
- end
+ expect(subject.list_pages.count).to eq(0)
+ end
- it 'sets the correct commit email' do
- subject.delete_page(page)
+ it 'sets the correct commit email' do
+ subject.delete_page(page)
- expect(user.commit_email).not_to eq(user.email)
- expect(commit.author_email).to eq(user.commit_email)
- expect(commit.committer_email).to eq(user.commit_email)
- end
+ expect(user.commit_email).not_to eq(user.email)
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ end
- it 'runs after_wiki_activity callbacks' do
- page
+ it 'runs after_wiki_activity callbacks' do
+ page
- expect(subject).to receive(:after_wiki_activity)
+ expect(subject).to receive(:after_wiki_activity)
- subject.delete_page(page)
- end
+ subject.delete_page(page)
end
- it_behaves_like 'delete_page operations'
-
context 'when an error is raised' do
it 'logs the error and returns false' do
page = build(:wiki_page, wiki: wiki)
@@ -509,14 +505,6 @@ RSpec.shared_examples 'wiki model' do
expect(subject.delete_page(page)).to be_falsey
end
end
-
- context 'when feature flag :gitaly_replace_wiki_delete_page is disabled' do
- before do
- stub_feature_flags(gitaly_replace_wiki_delete_page: false)
- end
-
- it_behaves_like 'delete_page operations'
- end
end
describe '#ensure_repository' do
diff --git a/spec/support/shared_examples/namespaces/namespace_traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb
index 36e5808fa28..77a1705627e 100644
--- a/spec/support/shared_examples/namespaces/namespace_traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_examples.rb
@@ -39,16 +39,17 @@ RSpec.shared_examples 'namespace traversal' do
end
describe '#ancestors' do
- let(:group) { create(:group) }
- let(:nested_group) { create(:group, parent: group) }
- let(:deep_nested_group) { create(:group, parent: nested_group) }
- let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:nested_group) { create(:group, parent: group) }
+ let_it_be(:deep_nested_group) { create(:group, parent: nested_group) }
+ let_it_be(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
it 'returns the correct ancestors' do
- expect(very_deep_nested_group.ancestors).to include(group, nested_group, deep_nested_group)
- expect(deep_nested_group.ancestors).to include(group, nested_group)
- expect(nested_group.ancestors).to include(group)
- expect(group.ancestors).to eq([])
+ # #reload is called to make sure traversal_ids are reloaded
+ expect(very_deep_nested_group.reload.ancestors).to contain_exactly(group, nested_group, deep_nested_group)
+ expect(deep_nested_group.reload.ancestors).to contain_exactly(group, nested_group)
+ expect(nested_group.reload.ancestors).to contain_exactly(group)
+ expect(group.reload.ancestors).to eq([])
end
describe '#recursive_ancestors' do
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 d05e5eb9120..013c9b61b99 100644
--- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb
+++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
@@ -57,7 +57,7 @@ RSpec.shared_examples 'project policies as anonymous' do
context 'when a project has pending invites' do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) }
- let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] }
+ let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji, :create_incident] }
let(:anonymous_permissions) { guest_permissions - user_permissions }
let(:current_user) { anonymous }
diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
index 87aaac673c1..c938c6432fe 100644
--- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
@@ -106,7 +106,7 @@ RSpec.shared_examples 'conan authenticate endpoint' do
expect(payload['user_id']).to eq(personal_access_token.user_id)
duration = payload['exp'] - payload['iat']
- expect(duration).to eq(1.hour)
+ expect(duration).to eq(::Gitlab::ConanToken::CONAN_TOKEN_EXPIRE_TIME)
end
end
end
@@ -661,7 +661,7 @@ RSpec.shared_examples 'workhorse package file upload endpoint' do
end
RSpec.shared_examples 'creates build_info when there is a job' do
- context 'with job token' do
+ context 'with job token', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/294047' do
let(:jwt) { build_jwt_from_job(job) }
it 'creates a build_info record' do
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 acaa0d8c2bc..dfd19167dcd 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
@@ -1,20 +1,16 @@
# frozen_string_literal: true
-RSpec.shared_context 'Debian repository shared context' do |object_type|
+RSpec.shared_context 'Debian repository shared context' do |container_type, can_freeze|
include_context 'workhorse headers'
before do
stub_feature_flags(debian_packages: true)
end
- if object_type == :project
- let(:project) { create(:project, :public) }
- elsif object_type == :group
- let(:group) { create(:group, :public) }
- end
-
- let(:user) { create(:user) }
- let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:private_container, freeze: can_freeze) { create(container_type, :private) }
+ let_it_be(:public_container, freeze: can_freeze) { create(container_type, :public) }
+ let_it_be(:user, freeze: true) { create(:user) }
+ let_it_be(:personal_access_token, freeze: true) { create(:personal_access_token, user: user) }
let(:distribution) { 'bullseye' }
let(:component) { 'main' }
@@ -36,7 +32,7 @@ RSpec.shared_context 'Debian repository shared context' do |object_type|
end
end
- let(:params) { workhorse_params }
+ let(:api_params) { workhorse_params }
let(:auth_headers) { {} }
let(:wh_headers) do
@@ -57,12 +53,12 @@ RSpec.shared_context 'Debian repository shared context' do |object_type|
api(url),
method: method,
file_key: :file,
- params: params,
+ params: api_params,
headers: headers,
send_rewritten_field: send_rewritten_field
)
else
- send method, api(url), headers: headers, params: params
+ send method, api(url), headers: headers, params: api_params
end
end
end
@@ -81,289 +77,190 @@ RSpec.shared_context 'Debian repository auth headers' do |user_role, user_token,
end
end
-RSpec.shared_context 'Debian repository project access' do |project_visibility_level, user_role, user_token, auth_method|
+RSpec.shared_context 'Debian repository access' do |visibility_level, user_role, add_member, user_token, auth_method|
include_context 'Debian repository auth headers', user_role, user_token, auth_method do
+ let(:containers) { { private: private_container, public: public_container } }
+ let(:container) { containers[visibility_level] }
+
before do
- project.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ container.send("add_#{user_role}", user) if add_member && user_role != :anonymous
end
end
end
-RSpec.shared_examples 'Debian project repository GET request' do |user_role, add_member, status, body|
- context "for user type #{user_role}" do
- before do
- project.send("add_#{user_role}", user) if add_member && user_role != :anonymous
- end
+RSpec.shared_examples 'Debian repository GET request' do |status, body = nil|
+ and_body = body.nil? ? '' : ' and expected body'
- and_body = body.nil? ? '' : ' and expected body'
+ it "returns #{status}#{and_body}" do
+ subject
- it "returns #{status}#{and_body}" do
- subject
+ expect(response).to have_gitlab_http_status(status)
- expect(response).to have_gitlab_http_status(status)
-
- unless body.nil?
- expect(response.body).to eq(body)
- end
+ unless body.nil?
+ expect(response.body).to eq(body)
end
end
end
-RSpec.shared_examples 'Debian project repository PUT request' do |user_role, add_member, status, body|
- context "for user type #{user_role}" do
- before do
- project.send("add_#{user_role}", user) if add_member && user_role != :anonymous
- end
+RSpec.shared_examples 'Debian repository upload request' do |status, body = nil|
+ and_body = body.nil? ? '' : ' and expected body'
- and_body = body.nil? ? '' : ' and expected body'
+ if status == :created
+ it 'creates package files', :aggregate_failures do
+ pending "Debian package creation not implemented"
- if status == :created
- it 'creates package files', :aggregate_failures do
- pending "Debian package creation not implemented"
- expect { subject }
- .to change { project.packages.debian.count }.by(1)
+ expect { subject }
+ .to change { container.packages.debian.count }.by(1)
- expect(response).to have_gitlab_http_status(status)
- expect(response.media_type).to eq('text/plain')
+ expect(response).to have_gitlab_http_status(status)
+ expect(response.media_type).to eq('text/plain')
- unless body.nil?
- expect(response.body).to eq(body)
- end
+ unless body.nil?
+ expect(response.body).to eq(body)
end
- it_behaves_like 'a package tracking event', described_class.name, 'push_package'
- else
- it "returns #{status}#{and_body}", :aggregate_failures do
- subject
+ end
+ it_behaves_like 'a package tracking event', described_class.name, 'push_package'
+ else
+ it "returns #{status}#{and_body}", :aggregate_failures do
+ subject
- expect(response).to have_gitlab_http_status(status)
+ expect(response).to have_gitlab_http_status(status)
- unless body.nil?
- expect(response.body).to eq(body)
- end
+ unless body.nil?
+ expect(response.body).to eq(body)
end
end
end
end
-RSpec.shared_examples 'Debian project repository PUT authorize request' do |user_role, add_member, status, body, is_authorize|
- context "for user type #{user_role}" do
- before do
- project.send("add_#{user_role}", user) if add_member && user_role != :anonymous
- end
-
- and_body = body.nil? ? '' : ' and expected body'
+RSpec.shared_examples 'Debian repository upload authorize request' do |status, body = nil|
+ and_body = body.nil? ? '' : ' and expected body'
- if status == :created
- it 'authorizes package file upload', :aggregate_failures do
- subject
+ if status == :created
+ it 'authorizes package file upload', :aggregate_failures do
+ subject
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response['TempPath']).to eq(Packages::PackageFileUploader.workhorse_local_upload_path)
- expect(json_response['RemoteObject']).to be_nil
- expect(json_response['MaximumSize']).to be_nil
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).to eq(Packages::PackageFileUploader.workhorse_local_upload_path)
+ expect(json_response['RemoteObject']).to be_nil
+ expect(json_response['MaximumSize']).to be_nil
+ end
- context 'without a valid token' do
- let(:workhorse_token) { 'invalid' }
+ context 'without a valid token' do
+ let(:workhorse_token) { 'invalid' }
- it 'rejects request' do
- subject
+ it 'rejects request' do
+ subject
- expect(response).to have_gitlab_http_status(:forbidden)
- end
+ expect(response).to have_gitlab_http_status(:forbidden)
end
+ end
- context 'bypassing gitlab-workhorse' do
- let(:workhorse_headers) { {} }
+ context 'bypassing gitlab-workhorse' do
+ let(:workhorse_headers) { {} }
- it 'rejects request' do
- subject
+ it 'rejects request' do
+ subject
- expect(response).to have_gitlab_http_status(:forbidden)
- end
+ expect(response).to have_gitlab_http_status(:forbidden)
end
- else
- it "returns #{status}#{and_body}", :aggregate_failures do
- subject
+ end
+ else
+ it "returns #{status}#{and_body}", :aggregate_failures do
+ subject
- expect(response).to have_gitlab_http_status(status)
+ expect(response).to have_gitlab_http_status(status)
- unless body.nil?
- expect(response.body).to eq(body)
- end
+ unless body.nil?
+ expect(response.body).to eq(body)
end
end
end
end
-RSpec.shared_examples 'rejects Debian access with unknown project id' do
- context 'with an unknown project' do
- let(:project) { double(id: non_existing_record_id) }
+RSpec.shared_examples 'rejects Debian access with unknown container id' do
+ context 'with an unknown container' do
+ let(:container) { double(id: non_existing_record_id) }
context 'as anonymous' do
- it_behaves_like 'Debian project repository GET request', :anonymous, true, :unauthorized, nil
+ it_behaves_like 'Debian repository GET request', :unauthorized, nil
end
context 'as authenticated user' do
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
- it_behaves_like 'Debian project repository GET request', :anonymous, true, :not_found, nil
+ it_behaves_like 'Debian repository GET request', :not_found, nil
end
end
end
-RSpec.shared_examples 'Debian project repository GET endpoint' do |success_status, success_body|
- context 'with valid project' do
+RSpec.shared_examples 'Debian repository read endpoint' do |desired_behavior, success_status, success_body|
+ context 'with valid container' do
using RSpec::Parameterized::TableSyntax
- where(:project_visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do
- 'PUBLIC' | :developer | true | true | success_status | success_body
- 'PUBLIC' | :guest | true | true | success_status | success_body
- 'PUBLIC' | :developer | true | false | success_status | success_body
- 'PUBLIC' | :guest | true | false | success_status | success_body
- 'PUBLIC' | :developer | false | true | success_status | success_body
- 'PUBLIC' | :guest | false | true | success_status | success_body
- 'PUBLIC' | :developer | false | false | success_status | success_body
- 'PUBLIC' | :guest | false | false | success_status | success_body
- 'PUBLIC' | :anonymous | false | true | success_status | success_body
- 'PRIVATE' | :developer | true | true | success_status | success_body
- 'PRIVATE' | :guest | true | true | :forbidden | nil
- 'PRIVATE' | :developer | true | false | :unauthorized | nil
- 'PRIVATE' | :guest | true | false | :unauthorized | nil
- 'PRIVATE' | :developer | false | true | :not_found | nil
- 'PRIVATE' | :guest | false | true | :not_found | nil
- 'PRIVATE' | :developer | false | false | :unauthorized | nil
- 'PRIVATE' | :guest | false | false | :unauthorized | nil
- 'PRIVATE' | :anonymous | false | true | :unauthorized | nil
+ where(:visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do
+ :public | :developer | true | true | success_status | success_body
+ :public | :guest | true | true | success_status | success_body
+ :public | :developer | true | false | success_status | success_body
+ :public | :guest | true | false | success_status | success_body
+ :public | :developer | false | true | success_status | success_body
+ :public | :guest | false | true | success_status | success_body
+ :public | :developer | false | false | success_status | success_body
+ :public | :guest | false | false | success_status | success_body
+ :public | :anonymous | false | true | success_status | success_body
+ :private | :developer | true | true | success_status | success_body
+ :private | :guest | true | true | :forbidden | nil
+ :private | :developer | true | false | :unauthorized | nil
+ :private | :guest | true | false | :unauthorized | nil
+ :private | :developer | false | true | :not_found | nil
+ :private | :guest | false | true | :not_found | nil
+ :private | :developer | false | false | :unauthorized | nil
+ :private | :guest | false | false | :unauthorized | nil
+ :private | :anonymous | false | true | :unauthorized | nil
end
with_them do
- include_context 'Debian repository project access', params[:project_visibility_level], params[:user_role], params[:user_token], :basic do
- it_behaves_like 'Debian project repository GET request', params[:user_role], params[:member], params[:expected_status], params[:expected_body]
+ include_context 'Debian repository access', params[:visibility_level], params[:user_role], params[:member], params[:user_token], :basic do
+ it_behaves_like "Debian repository #{desired_behavior}", params[:expected_status], params[:expected_body]
end
end
end
- it_behaves_like 'rejects Debian access with unknown project id'
-end
-
-RSpec.shared_examples 'Debian project repository PUT endpoint' do |success_status, success_body, is_authorize = false|
- context 'with valid project' do
- using RSpec::Parameterized::TableSyntax
-
- where(:project_visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do
- 'PUBLIC' | :developer | true | true | success_status | nil
- 'PUBLIC' | :guest | true | true | :forbidden | nil
- 'PUBLIC' | :developer | true | false | :unauthorized | nil
- 'PUBLIC' | :guest | true | false | :unauthorized | nil
- 'PUBLIC' | :developer | false | true | :forbidden | nil
- 'PUBLIC' | :guest | false | true | :forbidden | nil
- 'PUBLIC' | :developer | false | false | :unauthorized | nil
- 'PUBLIC' | :guest | false | false | :unauthorized | nil
- 'PUBLIC' | :anonymous | false | true | :unauthorized | nil
- 'PRIVATE' | :developer | true | true | success_status | nil
- 'PRIVATE' | :guest | true | true | :forbidden | nil
- 'PRIVATE' | :developer | true | false | :unauthorized | nil
- 'PRIVATE' | :guest | true | false | :unauthorized | nil
- 'PRIVATE' | :developer | false | true | :not_found | nil
- 'PRIVATE' | :guest | false | true | :not_found | nil
- 'PRIVATE' | :developer | false | false | :unauthorized | nil
- 'PRIVATE' | :guest | false | false | :unauthorized | nil
- 'PRIVATE' | :anonymous | false | true | :unauthorized | nil
- end
-
- with_them do
- include_context 'Debian repository project access', params[:project_visibility_level], params[:user_role], params[:user_token], :basic do
- desired_behavior = if is_authorize
- 'Debian project repository PUT authorize request'
- else
- 'Debian project repository PUT request'
- end
-
- it_behaves_like desired_behavior, params[:user_role], params[:member], params[:expected_status], params[:expected_body]
- end
- end
- end
-
- it_behaves_like 'rejects Debian access with unknown project id'
-end
-
-RSpec.shared_context 'Debian repository group access' do |group_visibility_level, user_role, user_token, auth_method|
- include_context 'Debian repository auth headers', user_role, user_token, auth_method do
- before do
- group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility_level, false))
- end
- end
-end
-
-RSpec.shared_examples 'Debian group repository GET request' do |user_role, add_member, status, body|
- context "for user type #{user_role}" do
- before do
- group.send("add_#{user_role}", user) if add_member && user_role != :anonymous
- end
-
- and_body = body.nil? ? '' : ' and expected body'
-
- it "returns #{status}#{and_body}" do
- subject
-
- expect(response).to have_gitlab_http_status(status)
-
- unless body.nil?
- expect(response.body).to eq(body)
- end
- end
- end
-end
-
-RSpec.shared_examples 'rejects Debian access with unknown group id' do
- context 'with an unknown group' do
- let(:group) { double(id: non_existing_record_id) }
-
- context 'as anonymous' do
- it_behaves_like 'Debian group repository GET request', :anonymous, true, :unauthorized, nil
- end
-
- context 'as authenticated user' do
- subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
-
- it_behaves_like 'Debian group repository GET request', :anonymous, true, :not_found, nil
- end
- end
+ it_behaves_like 'rejects Debian access with unknown container id'
end
-RSpec.shared_examples 'Debian group repository GET endpoint' do |success_status, success_body|
- context 'with valid group' do
+RSpec.shared_examples 'Debian repository write endpoint' do |desired_behavior, success_status, success_body|
+ context 'with valid container' do
using RSpec::Parameterized::TableSyntax
- where(:group_visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do
- 'PUBLIC' | :developer | true | true | success_status | success_body
- 'PUBLIC' | :guest | true | true | success_status | success_body
- 'PUBLIC' | :developer | true | false | success_status | success_body
- 'PUBLIC' | :guest | true | false | success_status | success_body
- 'PUBLIC' | :developer | false | true | success_status | success_body
- 'PUBLIC' | :guest | false | true | success_status | success_body
- 'PUBLIC' | :developer | false | false | success_status | success_body
- 'PUBLIC' | :guest | false | false | success_status | success_body
- 'PUBLIC' | :anonymous | false | true | success_status | success_body
- 'PRIVATE' | :developer | true | true | success_status | success_body
- 'PRIVATE' | :guest | true | true | :forbidden | nil
- 'PRIVATE' | :developer | true | false | :unauthorized | nil
- 'PRIVATE' | :guest | true | false | :unauthorized | nil
- 'PRIVATE' | :developer | false | true | :not_found | nil
- 'PRIVATE' | :guest | false | true | :not_found | nil
- 'PRIVATE' | :developer | false | false | :unauthorized | nil
- 'PRIVATE' | :guest | false | false | :unauthorized | nil
- 'PRIVATE' | :anonymous | false | true | :unauthorized | nil
+ where(:visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do
+ :public | :developer | true | true | success_status | success_body
+ :public | :guest | true | true | :forbidden | nil
+ :public | :developer | true | false | :unauthorized | nil
+ :public | :guest | true | false | :unauthorized | nil
+ :public | :developer | false | true | :forbidden | nil
+ :public | :guest | false | true | :forbidden | nil
+ :public | :developer | false | false | :unauthorized | nil
+ :public | :guest | false | false | :unauthorized | nil
+ :public | :anonymous | false | true | :unauthorized | nil
+ :private | :developer | true | true | success_status | success_body
+ :private | :guest | true | true | :forbidden | nil
+ :private | :developer | true | false | :unauthorized | nil
+ :private | :guest | true | false | :unauthorized | nil
+ :private | :developer | false | true | :not_found | nil
+ :private | :guest | false | true | :not_found | nil
+ :private | :developer | false | false | :unauthorized | nil
+ :private | :guest | false | false | :unauthorized | nil
+ :private | :anonymous | false | true | :unauthorized | nil
end
with_them do
- include_context 'Debian repository group access', params[:group_visibility_level], params[:user_role], params[:user_token], :basic do
- it_behaves_like 'Debian group repository GET request', params[:user_role], params[:member], params[:expected_status], params[:expected_body]
+ include_context 'Debian repository access', params[:visibility_level], params[:user_role], params[:member], params[:user_token], :basic do
+ it_behaves_like "Debian repository #{desired_behavior}", params[:expected_status], params[:expected_body]
end
end
end
- it_behaves_like 'rejects Debian access with unknown group id'
+ it_behaves_like 'rejects Debian access with unknown container id'
end
diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/boards/update_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/boards/update_list_shared_examples.rb
new file mode 100644
index 00000000000..9b55b0f061f
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/mutations/boards/update_list_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a GraphQL request to update board list' do
+ context 'the user is not allowed to read board lists' do
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ before do
+ list.update_preferences_for(current_user, collapsed: false)
+ end
+
+ context 'when user has permissions to admin board lists' do
+ before do
+ group.add_reporter(current_user)
+ end
+
+ it 'updates the list position and collapsed state' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['list']).to include(
+ 'position' => 1,
+ 'collapsed' => true
+ )
+ end
+ end
+
+ context 'when user has permissions to read board lists' do
+ before do
+ group.add_guest(current_user)
+ end
+
+ it 'updates the list collapsed state but not the list position' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['list']).to include(
+ 'position' => 0,
+ 'collapsed' => true
+ )
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/destroy_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/destroy_list_shared_examples.rb
new file mode 100644
index 00000000000..0cec67ff541
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/mutations/destroy_list_shared_examples.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples 'board lists destroy request' do
+ include GraphqlHelpers
+
+ subject { post_graphql_mutation(mutation, current_user: current_user) }
+
+ shared_examples 'does not destroy the list and returns an error' do
+ it 'does not destroy the list' do
+ expect { subject }.not_to change { klass.count }
+ end
+
+ it 'returns an error and not nil list' do
+ subject
+
+ expect(mutation_response['errors']).not_to be_empty
+ expect(mutation_response['list']).not_to be_nil
+ end
+ end
+
+ context 'when the user does not have permission' do
+ it 'does not destroy the list' do
+ expect { subject }.not_to change { klass.count }
+ end
+
+ it 'returns an error' do
+ subject
+
+ expect(graphql_errors.first['message']).to include("The resource that you are attempting to access does not exist or you don't have permission to perform this action")
+ end
+ end
+
+ context 'when the user has permission' do
+ before do
+ group.add_maintainer(current_user)
+ end
+
+ context 'when given id is not for a list' do
+ # could be any non-list thing
+ let_it_be(:list) { group }
+
+ it 'returns an error' do
+ subject
+
+ expect(graphql_errors.first['message']).to include('does not represent an instance of')
+ end
+ end
+
+ context 'when list does not exist' do
+ let(:variables) do
+ {
+ list_id: "gid://gitlab/#{klass}/#{non_existing_record_id}"
+ }
+ end
+
+ it 'returns a top level error' do
+ subject
+
+ expect(graphql_errors.first['message']).to include('No object found for')
+ end
+ end
+
+ context 'when everything is ok' do
+ it 'destroys the list' do
+ expect { subject }.to change { klass.count }.by(-1)
+ end
+
+ it 'returns an empty list' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('list')
+ expect(mutation_response['list']).to be_nil
+ expect(mutation_response['errors']).to be_empty
+ end
+ end
+
+ context 'when the list is not destroyable' do
+ before do
+ list.update!(list_type: :backlog)
+ end
+
+ it_behaves_like 'does not destroy the list and returns an error'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
index 66fbfa798b0..af4c9286e7c 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,6 +3,38 @@
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(:composer_metadatum) do
+ create(:composer_metadatum, package: composer_package,
+ target_sha: 'afdeh',
+ composer_json: { name: 'x', type: 'y', license: 'z', version: 1 })
+ end
+
+ let(:package_names) { graphql_data_at(resource_type, :packages, :nodes, :name) }
+ let(:target_shas) { graphql_data_at(resource_type, :packages, :nodes, :metadata, :target_sha) }
+ let(:packages) { graphql_data_at(resource_type, :packages, :nodes) }
+
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ #{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
+ metadata { #{query_graphql_fragment('ComposerMetadata')} }
+ }
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ resource_type,
+ { 'fullPath' => resource.full_path },
+ query_graphql_field('packages', {}, fields)
+ )
+ end
+
context 'when user has access to the resource' do
before do
resource.add_reporter(current_user)
@@ -48,4 +80,101 @@ RSpec.shared_examples 'group and project packages query' do
expect(packages).to be_nil
end
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(:data_path) { [resource_type, :packages] }
+
+ before do
+ resource.add_reporter(current_user)
+ end
+
+ [:CREATED_ASC, :NAME_ASC, :VERSION_ASC, :TYPE_ASC].each do |order|
+ context "#{order}" do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { order }
+ let(:first_param) { 4 }
+ let(:expected_results) { ascending_packages }
+ end
+ end
+ end
+
+ [: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(:expected_results) { ascending_packages.reverse }
+ end
+ end
+ end
+
+ context 'with an invalid sort' do
+ let(:query) do
+ graphql_query_for(
+ resource_type,
+ { 'fullPath' => resource.full_path },
+ query_nodes(:packages, :name, args: { sort: :WRONG_ORDER })
+ )
+ end
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'throws an error' do
+ expect_graphql_errors_to_include(/Argument \'sort\' on Field \'packages\' has an invalid value/)
+ end
+ end
+
+ def pagination_query(params)
+ graphql_query_for(resource_type, { 'fullPath' => resource.full_path },
+ query_nodes(:packages, :id, include_pagination_info: true, args: params)
+ )
+ end
+ end
+
+ describe 'filtering' do
+ subject { packages }
+
+ let(:query) do
+ graphql_query_for(
+ resource_type,
+ { 'fullPath' => resource.full_path },
+ query_nodes(:packages, :name, args: params)
+ )
+ end
+
+ before do
+ resource.add_reporter(current_user)
+ post_graphql(query, current_user: current_user)
+ end
+
+ context 'package_name' do
+ let(:params) { { package_name: maven_package.name } }
+
+ it { is_expected.to contain_exactly({ "name" => maven_package.name }) }
+ end
+
+ context 'package_type' do
+ let(:params) { { package_type: :COMPOSER } }
+
+ it { is_expected.to contain_exactly({ "name" => composer_package.name }) }
+ end
+
+ context 'status' do
+ let_it_be(:errored_package) { create(:maven_package, project: project1, status: 'error') }
+
+ let(:params) { { status: :ERROR } }
+
+ it { is_expected.to contain_exactly({ "name" => errored_package.name }) }
+ end
+
+ context 'include_versionless' do
+ let(:params) { { include_versionless: true } }
+
+ it { is_expected.to include({ "name" => versionaless_package.name }) }
+ 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 ded381fd402..a3378d4619b 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
@@ -3,7 +3,7 @@
RSpec.shared_examples 'issuable update endpoint' do
let(:area) { entity.class.name.underscore.pluralize }
- describe 'PUT /projects/:id/issues/:issue_id' do
+ describe 'PUT /projects/:id/issues/:issue_iid' do
let(:url) { "/projects/#{project.id}/#{area}/#{entity.iid}" }
it 'clears labels when labels param is nil' do
diff --git a/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
index 54aa9d47dd8..fa111ca5811 100644
--- a/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
@@ -14,7 +14,6 @@ RSpec.shared_examples 'multiple and scoped issue boards' do |route_definition|
post api(root_url, user), params: { name: "new board" }
expect(response).to have_gitlab_http_status(:created)
-
expect(response).to match_response_schema('public_api/v4/board', dir: "ee")
end
end
diff --git a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
new file mode 100644
index 00000000000..70cc9b1e6b5
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
@@ -0,0 +1,251 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'when package feature is disabled' do
+ before do
+ stub_config(packages: { enabled: false })
+ end
+
+ it_behaves_like 'returning response status', :not_found
+end
+
+RSpec.shared_examples 'without authentication' do
+ it_behaves_like 'returning response status', :unauthorized
+end
+
+RSpec.shared_examples 'with authentication' do
+ where(:user_role, :token_header, :token_type, :valid_token, :status) do
+ :guest | 'PRIVATE-TOKEN' | :personal_access_token | true | :not_found
+ :guest | 'PRIVATE-TOKEN' | :personal_access_token | false | :unauthorized
+ :guest | 'DEPLOY-TOKEN' | :deploy_token | true | :not_found
+ :guest | 'DEPLOY-TOKEN' | :deploy_token | false | :unauthorized
+ :guest | 'JOB-TOKEN' | :job_token | true | :not_found
+ :guest | 'JOB-TOKEN' | :job_token | false | :unauthorized
+ :reporter | 'PRIVATE-TOKEN' | :personal_access_token | true | :not_found
+ :reporter | 'PRIVATE-TOKEN' | :personal_access_token | false | :unauthorized
+ :reporter | 'DEPLOY-TOKEN' | :deploy_token | true | :not_found
+ :reporter | 'DEPLOY-TOKEN' | :deploy_token | false | :unauthorized
+ :reporter | 'JOB-TOKEN' | :job_token | true | :not_found
+ :reporter | 'JOB-TOKEN' | :job_token | false | :unauthorized
+ :developer | 'PRIVATE-TOKEN' | :personal_access_token | true | :not_found
+ :developer | 'PRIVATE-TOKEN' | :personal_access_token | false | :unauthorized
+ :developer | 'DEPLOY-TOKEN' | :deploy_token | true | :not_found
+ :developer | 'DEPLOY-TOKEN' | :deploy_token | false | :unauthorized
+ :developer | 'JOB-TOKEN' | :job_token | true | :not_found
+ :developer | 'JOB-TOKEN' | :job_token | false | :unauthorized
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", user) unless user_role == :anonymous
+ end
+
+ let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' }
+ let(:headers) { { token_header => token } }
+
+ it_behaves_like 'returning response status', params[:status]
+ end
+end
+
+RSpec.shared_examples 'an unimplemented route' do
+ it_behaves_like 'without authentication'
+ it_behaves_like 'with authentication'
+ it_behaves_like 'when package feature is disabled'
+end
+
+RSpec.shared_examples 'grants terraform module download' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.headers).to include 'X-Terraform-Get'
+ end
+ end
+end
+
+RSpec.shared_examples 'returns terraform module packages' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'returning a valid response' do
+ subject
+
+ expect(json_response).to match_schema('public_api/v4/packages/terraform/modules/v1/versions')
+ end
+ end
+end
+
+RSpec.shared_examples 'returns no terraform module packages' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'returns a response with no versions' do
+ subject
+
+ expect(json_response['modules'][0]['versions'].size).to eq(0)
+ end
+ end
+end
+
+RSpec.shared_examples 'grants terraform module packages access' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'grants terraform module package file access' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
+ end
+end
+
+RSpec.shared_examples 'rejects terraform module packages access' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'process terraform module workhorse authorization' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'has the proper content type' do
+ subject
+
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+
+ context 'with a request that bypassed gitlab-workhorse' do
+ let(:headers) do
+ { 'HTTP_PRIVATE_TOKEN' => personal_access_token.token }
+ .merge(workhorse_headers)
+ .tap { |h| h.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) }
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+end
+
+RSpec.shared_examples 'process terraform module upload' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'creates terraform module package files' do
+ it 'creates package files', :aggregate_failures do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ expect(response).to have_gitlab_http_status(status)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq('mymodule-mysystem-1.0.0.tgz')
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ context 'with object storage disabled' do
+ before do
+ stub_package_file_object_storage(enabled: false)
+ end
+
+ context 'without a file from workhorse' do
+ let(:send_rewritten_field) { false }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ context 'with correct params' do
+ it_behaves_like 'package workhorse uploads'
+ it_behaves_like 'creates terraform module package files'
+ it_behaves_like 'a package tracking event', described_class.name, 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create( # rubocop:disable Rails/SaveBang
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { { file: fog_file, 'file.remote_id' => file_name } }
+
+ context 'and direct upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'creates terraform module package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) do
+ {
+ file: fog_file,
+ 'file.remote_id' => remote_id
+ }
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+ end
+
+ context 'and direct upload disabled' do
+ context 'and background upload disabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: false)
+ end
+
+ it_behaves_like 'creates terraform module package files'
+ end
+
+ context 'and background upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: true)
+ end
+
+ it_behaves_like 'creates terraform module package files'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb b/spec/support/shared_examples/requests/api/time_tracking_shared_examples.rb
index fb6d6603beb..afc902dd184 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
@@ -125,6 +125,22 @@ RSpec.shared_examples 'time tracking endpoints' do |issuable_name|
expect(json_response['message']['base'].first).to eq(_('Time to subtract exceeds the total time spent'))
end
end
+
+ if issuable_name == 'merge_request'
+ it 'calls update service with :use_specialized_service param' do
+ expect(::MergeRequests::UpdateService).to receive(:new).with(project: project, current_user: user, params: hash_including(use_specialized_service: true))
+
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '2h' }
+ end
+ end
+
+ if issuable_name == 'issue'
+ it 'calls update service without :use_specialized_service param' do
+ expect(::Issues::UpdateService).to receive(:new).with(project: project, current_user: user, params: hash_not_including(use_specialized_service: true))
+
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '2h' }
+ end
+ end
end
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_spent_time" do
diff --git a/spec/support/shared_examples/requests/clusters/integrations_controller_shared_examples.rb b/spec/support/shared_examples/requests/clusters/integrations_controller_shared_examples.rb
index 490c7d12115..91fdcbd9b1d 100644
--- a/spec/support/shared_examples/requests/clusters/integrations_controller_shared_examples.rb
+++ b/spec/support/shared_examples/requests/clusters/integrations_controller_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples '#create_or_update action' do
let(:params) do
- { integration: { application_type: Clusters::Applications::Prometheus.application_name, enabled: true } }
+ { integration: { application_type: 'prometheus', enabled: true } }
end
let(:path) { raise NotImplementedError }
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 926da827e75..95817624658 100644
--- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#
# Requires let variables:
-# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths"
+# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_packages_api"
# * request_method
# * request_args
# * other_user_request_args
@@ -13,7 +13,8 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
{
"throttle_protected_paths" => "throttle_authenticated_protected_paths_api",
"throttle_authenticated_api" => "throttle_authenticated_api",
- "throttle_authenticated_web" => "throttle_authenticated_web"
+ "throttle_authenticated_web" => "throttle_authenticated_web",
+ "throttle_authenticated_packages_api" => "throttle_authenticated_packages_api"
}
end
diff --git a/spec/support/shared_examples/row_lock_shared_examples.rb b/spec/support/shared_examples/row_lock_shared_examples.rb
new file mode 100644
index 00000000000..5e003172215
--- /dev/null
+++ b/spec/support/shared_examples/row_lock_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# Ensure that a SQL command to lock this row(s) was requested.
+# Ensure a transaction also occurred.
+# Be careful! This form of spec is not foolproof, but better than nothing.
+
+RSpec.shared_examples 'locked row' do
+ it "has locked row" do
+ table_name = row.class.table_name
+ ids_regex = /SELECT.*FROM.*#{table_name}.*"#{table_name}"."id" = #{row.id}.+FOR UPDATE/m
+
+ expect(recorded_queries.log).to include a_string_matching 'SAVEPOINT'
+ expect(recorded_queries.log).to include a_string_matching ids_regex
+ end
+end
+
+RSpec.shared_examples 'locked rows' do
+ it "has locked rows" do
+ table_name = rows.first.class.table_name
+
+ row_ids = rows.map(&:id).join(', ')
+ ids_regex = /SELECT.+FROM.+"#{table_name}".+"#{table_name}"."id" IN \(#{row_ids}\).+FOR UPDATE/m
+
+ expect(recorded_queries.log).to include a_string_matching 'SAVEPOINT'
+ expect(recorded_queries.log).to include a_string_matching ids_regex
+ end
+end
diff --git a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
index 00146335ef7..9d7ae6bcb3d 100644
--- a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
+++ b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
@@ -20,9 +20,27 @@ RSpec.shared_examples 'avoid N+1 on environments serialization' do
expect { serialize(grouping: false) }.not_to exceed_query_limit(control.count)
end
- def serialize(grouping:)
+ it 'does not preload for environments that does not exist in the page', :request_store do
+ create_environment_with_associations(project)
+
+ first_page_query = ActiveRecord::QueryRecorder.new do
+ serialize(grouping: false, query: { page: 1, per_page: 1 } )
+ end
+
+ second_page_query = ActiveRecord::QueryRecorder.new do
+ serialize(grouping: false, query: { page: 2, per_page: 1 } )
+ end
+
+ expect(second_page_query.count).to be < first_page_query.count
+ end
+
+ def serialize(grouping:, query: nil)
+ query ||= { page: 1, per_page: 1 }
+ request = double(url: "#{Gitlab.config.gitlab.url}:8080/api/v4/projects?#{query.to_query}", query_parameters: query)
+
EnvironmentSerializer.new(current_user: user, project: project).yield_self do |serializer|
serializer.within_folders if grouping
+ serializer.with_pagination(request, spy('response'))
serializer.represent(Environment.where(project: project))
end
end
diff --git a/spec/support/shared_examples/serializers/pipeline_artifacts_shared_example.rb b/spec/support/shared_examples/serializers/pipeline_artifacts_shared_example.rb
deleted file mode 100644
index d5ffd5e7510..00000000000
--- a/spec/support/shared_examples/serializers/pipeline_artifacts_shared_example.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-RSpec.shared_examples 'public artifacts' do
- let_it_be(:project) { create(:project, :public) }
- let(:pipeline) { create(:ci_empty_pipeline, status: :success, project: project) }
-
- context 'that has artifacts' do
- let!(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
-
- it 'contains information about artifacts' do
- expect(subject[:details][:artifacts].length).to eq(1)
- end
- end
-
- context 'that has non public artifacts' do
- let!(:build) { create(:ci_build, :success, :artifacts, :non_public_artifacts, pipeline: pipeline) }
-
- it 'does not contain information about artifacts' do
- expect(subject[:details][:artifacts].length).to eq(0)
- end
- end
-end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
new file mode 100644
index 00000000000..218a3462c35
--- /dev/null
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+
+# This shared_example requires the following variables:
+# - `service`, the service which includes AlertManagement::AlertProcessing
+RSpec.shared_examples 'creates an alert management alert or errors' do
+ it { is_expected.to be_success }
+
+ it 'creates AlertManagement::Alert' do
+ expect(Gitlab::AppLogger).not_to receive(:warn)
+
+ expect { subject }.to change(AlertManagement::Alert, :count).by(1)
+ end
+
+ it 'executes the alert service hooks' do
+ expect_next_instance_of(AlertManagement::Alert) do |alert|
+ expect(alert).to receive(:execute_services)
+ end
+
+ subject
+ end
+
+ context 'and fails to save' do
+ let(:errors) { double(messages: { hosts: ['hosts array is over 255 chars'] })}
+
+ before do
+ allow(service).to receive(:alert).and_call_original
+ allow(service).to receive_message_chain(:alert, :save).and_return(false)
+ allow(service).to receive_message_chain(:alert, :errors).and_return(errors)
+ end
+
+ it_behaves_like 'alerts service responds with an error', :bad_request
+
+ it 'writes a warning to the log' do
+ expect(Gitlab::AppLogger).to receive(:warn).with(
+ message: "Unable to create AlertManagement::Alert from #{source}",
+ project_id: project.id,
+ alert_errors: { hosts: ['hosts array is over 255 chars'] }
+ )
+
+ subject
+ end
+ end
+end
+
+# This shared_example requires the following variables:
+# - last_alert_attributes, last created alert
+# - project, project that alert created
+# - payload_raw, hash representation of payload
+# - environment, project's environment
+# - fingerprint, fingerprint hash
+RSpec.shared_examples 'properly assigns the alert properties' do
+ specify do
+ subject
+
+ expect(last_alert_attributes).to match({
+ project_id: project.id,
+ title: payload_raw.fetch(:title),
+ started_at: Time.zone.parse(payload_raw.fetch(:start_time)),
+ severity: payload_raw.fetch(:severity, nil),
+ status: AlertManagement::Alert.status_value(:triggered),
+ events: 1,
+ domain: domain,
+ hosts: payload_raw.fetch(:hosts, nil),
+ payload: payload_raw.with_indifferent_access,
+ issue_id: nil,
+ description: payload_raw.fetch(:description, nil),
+ monitoring_tool: payload_raw.fetch(:monitoring_tool, nil),
+ service: payload_raw.fetch(:service, nil),
+ fingerprint: Digest::SHA1.hexdigest(fingerprint),
+ environment_id: environment.id,
+ ended_at: nil,
+ prometheus_alert_id: nil
+ }.with_indifferent_access)
+ end
+end
+
+RSpec.shared_examples 'does not create an alert management alert' do
+ specify do
+ expect { subject }.not_to change(AlertManagement::Alert, :count)
+ end
+end
+
+# This shared_example requires the following variables:
+# - `alert`, the alert for which events should be incremented
+RSpec.shared_examples 'adds an alert management alert event' do
+ specify do
+ expect(alert).not_to receive(:execute_services)
+
+ expect { subject }.to change { alert.reload.events }.by(1)
+
+ expect(subject).to be_success
+ end
+
+ it_behaves_like 'does not create an alert management alert'
+end
+
+# This shared_example requires the following variables:
+# - `alert`, the alert for which events should not be incremented
+RSpec.shared_examples 'does not add an alert management alert event' do
+ specify do
+ expect { subject }.not_to change { alert.reload.events }
+ end
+end
+
+RSpec.shared_examples 'processes new firing alert' do
+ include_examples 'processes never-before-seen alert'
+
+ context 'for an existing alert with the same fingerprint' do
+ let_it_be(:gitlab_fingerprint) { Digest::SHA1.hexdigest(fingerprint) }
+
+ context 'which is triggered' do
+ let_it_be(:alert) { create(:alert_management_alert, :triggered, fingerprint: gitlab_fingerprint, project: project) }
+
+ it_behaves_like 'adds an alert management alert event'
+ it_behaves_like 'sends alert notification emails if enabled'
+ it_behaves_like 'processes incident issues if enabled', with_issue: true
+
+ it_behaves_like 'does not create an alert management alert'
+ it_behaves_like 'does not create a system note for alert'
+
+ context 'with an existing resolved alert as well' do
+ let_it_be(:resolved_alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: gitlab_fingerprint) }
+
+ it_behaves_like 'adds an alert management alert event'
+ it_behaves_like 'sends alert notification emails if enabled'
+ it_behaves_like 'processes incident issues if enabled', with_issue: true
+
+ it_behaves_like 'does not create an alert management alert'
+ it_behaves_like 'does not create a system note for alert'
+ end
+ end
+
+ context 'which is acknowledged' do
+ let_it_be(:alert) { create(:alert_management_alert, :acknowledged, fingerprint: gitlab_fingerprint, project: project) }
+
+ it_behaves_like 'adds an alert management alert event'
+ it_behaves_like 'processes incident issues if enabled', with_issue: true
+
+ it_behaves_like 'does not create an alert management alert'
+ it_behaves_like 'does not create a system note for alert'
+ it_behaves_like 'does not send alert notification emails'
+ end
+
+ context 'which is ignored' do
+ let_it_be(:alert) { create(:alert_management_alert, :ignored, fingerprint: gitlab_fingerprint, project: project) }
+
+ it_behaves_like 'adds an alert management alert event'
+ it_behaves_like 'processes incident issues if enabled', with_issue: true
+
+ it_behaves_like 'does not create an alert management alert'
+ it_behaves_like 'does not create a system note for alert'
+ it_behaves_like 'does not send alert notification emails'
+ end
+
+ context 'which is resolved' do
+ let_it_be(:alert) { create(:alert_management_alert, :resolved, fingerprint: gitlab_fingerprint, project: project) }
+
+ include_examples 'processes never-before-seen alert'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
new file mode 100644
index 00000000000..86e7da5bcbe
--- /dev/null
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+# This shared_example requires the following variables:
+# - `alert`, the alert to be resolved
+RSpec.shared_examples 'resolves an existing alert management alert' do
+ it 'sets the end time and status' do
+ expect(Gitlab::AppLogger).not_to receive(:warn)
+
+ expect { subject }
+ .to change { alert.reload.resolved? }.to(true)
+ .and change { alert.ended_at.present? }.to(true)
+
+ expect(subject).to be_success
+ end
+end
+
+# This shared_example requires the following variables:
+# - `alert`, the alert not to be updated
+RSpec.shared_examples 'does not change the alert end time' do
+ specify do
+ expect { subject }.not_to change { alert.reload.ended_at }
+ end
+end
+
+# This shared_example requires the following variables:
+# - `project`, expected project for an incoming alert
+# - `service`, a service which includes AlertManagement::AlertProcessing
+# - `alert` (optional), the alert which should fail to resolve. If not
+# included, the log is expected to correspond to a new alert
+RSpec.shared_examples 'writes a warning to the log for a failed alert status update' do
+ before do
+ allow(service).to receive(:alert).and_call_original
+ allow(service).to receive_message_chain(:alert, :resolve).and_return(false)
+ end
+
+ specify do
+ expect(Gitlab::AppLogger).to receive(:warn).with(
+ message: 'Unable to update AlertManagement::Alert status to resolved',
+ project_id: project.id,
+ alert_id: alert ? alert.id : (last_alert_id + 1)
+ )
+
+ # Failure to resolve a recovery alert is not a critical failure
+ expect(subject).to be_success
+ end
+
+ private
+
+ def last_alert_id
+ AlertManagement::Alert.connection
+ .select_value("SELECT nextval('#{AlertManagement::Alert.sequence_name}')")
+ end
+end
+
+RSpec.shared_examples 'processes recovery alert' do
+ context 'seen for the first time' do
+ let(:alert) { AlertManagement::Alert.last }
+
+ include_examples 'processes never-before-seen recovery alert'
+ end
+
+ context 'for an existing alert with the same fingerprint' do
+ let_it_be(:gitlab_fingerprint) { Digest::SHA1.hexdigest(fingerprint) }
+
+ context 'which is triggered' do
+ let_it_be(:alert) { create(:alert_management_alert, :triggered, project: project, fingerprint: gitlab_fingerprint, monitoring_tool: source) }
+
+ it_behaves_like 'resolves an existing alert management alert'
+ it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert
+ it_behaves_like 'sends alert notification emails if enabled'
+ it_behaves_like 'closes related incident if enabled'
+ it_behaves_like 'writes a warning to the log for a failed alert status update'
+
+ it_behaves_like 'does not create an alert management alert'
+ it_behaves_like 'does not process incident issues'
+ it_behaves_like 'does not add an alert management alert event'
+ end
+
+ context 'which is ignored' do
+ let_it_be(:alert) { create(:alert_management_alert, :ignored, project: project, fingerprint: gitlab_fingerprint, monitoring_tool: source) }
+
+ it_behaves_like 'resolves an existing alert management alert'
+ it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert
+ it_behaves_like 'sends alert notification emails if enabled'
+ it_behaves_like 'closes related incident if enabled'
+ it_behaves_like 'writes a warning to the log for a failed alert status update'
+
+ it_behaves_like 'does not create an alert management alert'
+ it_behaves_like 'does not process incident issues'
+ it_behaves_like 'does not add an alert management alert event'
+ end
+
+ context 'which is acknowledged' do
+ let_it_be(:alert) { create(:alert_management_alert, :acknowledged, project: project, fingerprint: gitlab_fingerprint, monitoring_tool: source) }
+
+ it_behaves_like 'resolves an existing alert management alert'
+ it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert
+ it_behaves_like 'sends alert notification emails if enabled'
+ it_behaves_like 'closes related incident if enabled'
+ it_behaves_like 'writes a warning to the log for a failed alert status update'
+
+ it_behaves_like 'does not create an alert management alert'
+ it_behaves_like 'does not process incident issues'
+ it_behaves_like 'does not add an alert management alert event'
+ end
+
+ context 'which is resolved' do
+ let_it_be(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: gitlab_fingerprint, monitoring_tool: source) }
+
+ include_examples 'processes never-before-seen recovery alert'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb
new file mode 100644
index 00000000000..c6ac07b6dd5
--- /dev/null
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+# Expects usage of 'incident settings enabled' context.
+#
+# This shared_example includes the following option:
+# - with_issue: includes a test for when the defined `alert` has an associated issue
+#
+# This shared_example requires the following variables:
+# - `alert`, required if :with_issue is true
+RSpec.shared_examples 'processes incident issues if enabled' do |with_issue: false|
+ include_examples 'processes incident issues', with_issue
+
+ context 'with incident setting disabled' do
+ let(:create_issue) { false }
+
+ it_behaves_like 'does not process incident issues'
+ end
+end
+
+RSpec.shared_examples 'processes incident issues' do |with_issue: false|
+ before do
+ allow_next_instance_of(AlertManagement::Alert) do |alert|
+ allow(alert).to receive(:execute_services)
+ end
+ end
+
+ specify do
+ expect(IncidentManagement::ProcessAlertWorkerV2)
+ .to receive(:perform_async)
+ .with(kind_of(Integer))
+
+ Sidekiq::Testing.inline! do
+ expect(subject).to be_success
+ end
+ end
+
+ context 'with issue', if: with_issue do
+ before do
+ alert.update!(issue: create(:issue, project: project))
+ end
+
+ it_behaves_like 'does not process incident issues'
+ end
+end
+
+RSpec.shared_examples 'does not process incident issues' do
+ specify do
+ expect(IncidentManagement::ProcessAlertWorkerV2).not_to receive(:perform_async)
+
+ subject
+ end
+end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
new file mode 100644
index 00000000000..132f1e0422e
--- /dev/null
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+# Expects usage of 'incident settings enabled' context.
+#
+# This shared_example requires the following variables:
+# - `alert`, alert for which related incidents should be closed
+# - `project`, project of the alert
+RSpec.shared_examples 'closes related incident if enabled' do
+ context 'with issue' do
+ before do
+ alert.update!(issue: create(:issue, project: project))
+ end
+
+ it { expect { subject }.to change { alert.issue.reload.closed? }.from(false).to(true) }
+ it { expect { subject }.to change(ResourceStateEvent, :count).by(1) }
+ end
+
+ context 'without issue' do
+ it { expect { subject }.not_to change { alert.reload.issue } }
+ it { expect { subject }.not_to change(ResourceStateEvent, :count) }
+ end
+
+ context 'with incident setting disabled' do
+ let(:auto_close_incident) { false }
+
+ it_behaves_like 'does not close related incident'
+ end
+end
+
+RSpec.shared_examples 'does not close related incident' do
+ context 'with issue' do
+ before do
+ alert.update!(issue: create(:issue, project: project))
+ end
+
+ it { expect { subject }.not_to change { alert.issue.reload.state } }
+ it { expect { subject }.not_to change(ResourceStateEvent, :count) }
+ end
+
+ context 'without issue' do
+ it { expect { subject }.not_to change { alert.reload.issue } }
+ it { expect { subject }.not_to change(ResourceStateEvent, :count) }
+ end
+end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb
new file mode 100644
index 00000000000..5f30b58176b
--- /dev/null
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+# Expects usage of 'incident settings enabled' context.
+#
+# This shared_example includes the following option:
+# - count: number of notifications expected to be sent
+RSpec.shared_examples 'sends alert notification emails if enabled' do |count: 1|
+ include_examples 'sends alert notification emails', count
+
+ context 'with email setting disabled' do
+ let(:send_email) { false }
+
+ it_behaves_like 'does not send alert notification emails'
+ end
+end
+
+RSpec.shared_examples 'sends alert notification emails' do |count: 1|
+ let(:notification_async) { double(NotificationService::Async) }
+
+ specify do
+ allow(NotificationService).to receive_message_chain(:new, :async).and_return(notification_async)
+ expect(notification_async).to receive(:prometheus_alerts_fired).exactly(count).times
+
+ subject
+ end
+end
+
+RSpec.shared_examples 'does not send alert notification emails' do
+ specify do
+ expect(NotificationService).not_to receive(:new)
+
+ subject
+ end
+end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb
new file mode 100644
index 00000000000..57d598c0259
--- /dev/null
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+# This shared_example includes the following option:
+# - notes: any of [:new_alert, :recovery_alert, :resolve_alert].
+# Represents which notes are expected to be created.
+#
+# This shared_example requires the following variables:
+# - `source` (optional), the monitoring tool or integration name
+# expected in the applicable system notes
+RSpec.shared_examples 'creates expected system notes for alert' do |*notes|
+ let(:expected_note_count) { expected_notes.length }
+ let(:new_notes) { Note.last(expected_note_count).pluck(:note) }
+ let(:expected_notes) do
+ {
+ new_alert: source,
+ recovery_alert: source,
+ resolve_alert: 'Resolved'
+ }.slice(*notes)
+ end
+
+ it "for #{notes.join(', ')}" do
+ expect { subject }.to change(Note, :count).by(expected_note_count)
+
+ expected_notes.each_value.with_index do |value, index|
+ expect(new_notes[index]).to include(value)
+ end
+ end
+end
+
+RSpec.shared_examples 'does not create a system note for alert' do
+ specify do
+ expect { subject }.not_to change(Note, :count)
+ end
+end
diff --git a/spec/support/shared_examples/services/alert_management_shared_examples.rb b/spec/support/shared_examples/services/alert_management_shared_examples.rb
index d9f28a97a0f..827ae42f970 100644
--- a/spec/support/shared_examples/services/alert_management_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb
@@ -1,111 +1,77 @@
# frozen_string_literal: true
-RSpec.shared_examples 'creates an alert management alert' do
- it { is_expected.to be_success }
+RSpec.shared_examples 'alerts service responds with an error and takes no actions' do |http_status|
+ include_examples 'alerts service responds with an error', http_status
- it 'creates AlertManagement::Alert' do
- expect { subject }.to change(AlertManagement::Alert, :count).by(1)
- end
-
- it 'executes the alert service hooks' do
- expect_next_instance_of(AlertManagement::Alert) do |alert|
- expect(alert).to receive(:execute_services)
- end
+ it_behaves_like 'does not create an alert management alert'
+ it_behaves_like 'does not create a system note for alert'
+ it_behaves_like 'does not process incident issues'
+ it_behaves_like 'does not send alert notification emails'
+end
- subject
+RSpec.shared_examples 'alerts service responds with an error' do |http_status|
+ specify do
+ expect(subject).to be_error
+ expect(subject.http_status).to eq(http_status)
end
end
# This shared_example requires the following variables:
-# - last_alert_attributes, last created alert
-# - project, project that alert created
-# - payload_raw, hash representation of payload
-# - environment, project's environment
-# - fingerprint, fingerprint hash
-RSpec.shared_examples 'assigns the alert properties' do
- it 'ensures that created alert has all data properly assigned' do
- subject
-
- expect(last_alert_attributes).to match(
- project_id: project.id,
- title: payload_raw.fetch(:title),
- started_at: Time.zone.parse(payload_raw.fetch(:start_time)),
- severity: payload_raw.fetch(:severity),
- status: AlertManagement::Alert.status_value(:triggered),
- events: 1,
- domain: domain,
- hosts: payload_raw.fetch(:hosts),
- payload: payload_raw.with_indifferent_access,
- issue_id: nil,
- description: payload_raw.fetch(:description),
- monitoring_tool: payload_raw.fetch(:monitoring_tool),
- service: payload_raw.fetch(:service),
- fingerprint: Digest::SHA1.hexdigest(fingerprint),
- environment_id: environment.id,
- ended_at: nil,
- prometheus_alert_id: nil
+# - `service`, a service which includes ::IncidentManagement::Settings
+RSpec.shared_context 'incident management settings enabled' do
+ let(:auto_close_incident) { true }
+ let(:create_issue) { true }
+ let(:send_email) { true }
+
+ let(:incident_management_setting) do
+ double(
+ auto_close_incident?: auto_close_incident,
+ create_issue?: create_issue,
+ send_email?: send_email
)
end
-end
-RSpec.shared_examples 'does not an create alert management alert' do
- it 'does not create alert' do
- expect { subject }.not_to change(AlertManagement::Alert, :count)
+ before do
+ allow(ProjectServiceWorker).to receive(:perform_async)
+ allow(service)
+ .to receive(:incident_management_setting)
+ .and_return(incident_management_setting)
end
end
-RSpec.shared_examples 'adds an alert management alert event' do
- it { is_expected.to be_success }
-
- it 'does not create an alert' do
- expect { subject }.not_to change(AlertManagement::Alert, :count)
- end
-
- it 'increases alert events count' do
- expect { subject }.to change { alert.reload.events }.by(1)
- end
-
- it 'does not executes the alert service hooks' do
- expect(alert).not_to receive(:execute_services)
-
- subject
- end
+RSpec.shared_examples 'processes never-before-seen alert' do
+ it_behaves_like 'creates an alert management alert or errors'
+ it_behaves_like 'creates expected system notes for alert', :new_alert
+ it_behaves_like 'processes incident issues if enabled'
+ it_behaves_like 'sends alert notification emails if enabled'
end
-RSpec.shared_examples 'processes incident issues' do
- let(:create_incident_service) { spy }
-
- before do
- allow_any_instance_of(AlertManagement::Alert).to receive(:execute_services)
+RSpec.shared_examples 'processes never-before-seen recovery alert' do
+ it_behaves_like 'creates an alert management alert or errors'
+ it_behaves_like 'creates expected system notes for alert', :new_alert, :recovery_alert, :resolve_alert
+ it_behaves_like 'sends alert notification emails if enabled'
+ it_behaves_like 'does not process incident issues'
+ it_behaves_like 'writes a warning to the log for a failed alert status update' do
+ let(:alert) { nil } # Ensure the next alert id is used
end
- it 'processes issues' do
- expect(IncidentManagement::ProcessAlertWorker)
- .to receive(:perform_async)
- .with(nil, nil, kind_of(Integer))
- .once
+ it 'resolves the alert' do
+ subject
- Sidekiq::Testing.inline! do
- expect(subject).to be_success
- end
+ expect(AlertManagement::Alert.last.ended_at).to be_present
+ expect(AlertManagement::Alert.last.resolved?).to be(true)
end
end
-RSpec.shared_examples 'does not process incident issues' do
- it 'does not process issues' do
- expect(IncidentManagement::ProcessAlertWorker)
- .not_to receive(:perform_async)
+RSpec.shared_examples 'processes one firing and one resolved prometheus alerts' do
+ it 'creates AlertManagement::Alert' do
+ expect(Gitlab::AppLogger).not_to receive(:warn)
- expect(subject).to be_success
+ expect { subject }
+ .to change(AlertManagement::Alert, :count).by(2)
+ .and change(Note, :count).by(4)
end
-end
-
-RSpec.shared_examples 'does not process incident issues due to error' do |http_status:|
- it 'does not process issues' do
- expect(IncidentManagement::ProcessAlertWorker)
- .not_to receive(:perform_async)
- expect(subject).to be_error
- expect(subject.http_status).to eq(http_status)
- end
+ it_behaves_like 'processes incident issues'
+ it_behaves_like 'sends alert notification emails', count: 2
end
diff --git a/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb b/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb
new file mode 100644
index 00000000000..68ea460dabc
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'boards recent visit' do
+ let_it_be(:user) { create(:user) }
+
+ describe '#visited' do
+ it 'creates a visit if one does not exists' do
+ expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1)
+ end
+
+ shared_examples 'was visited previously' do
+ let_it_be(:visit) do
+ create(visit_relation,
+ board_parent_relation => board_parent,
+ board_relation => board,
+ user: user,
+ updated_at: 7.days.ago
+ )
+ end
+
+ it 'updates the timestamp' do
+ freeze_time do
+ described_class.visited!(user, board)
+
+ expect(described_class.count).to eq 1
+ expect(described_class.first.updated_at).to be_like_time(Time.zone.now)
+ end
+ end
+ end
+
+ it_behaves_like 'was visited previously'
+
+ context 'when we try to create a visit that is not unique' do
+ before do
+ expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
+ expect(described_class).to receive(:find_or_create_by).and_return(visit)
+ end
+
+ it_behaves_like 'was visited previously'
+ end
+ end
+
+ describe '#latest' do
+ def create_visit(time)
+ create(visit_relation, board_parent_relation => board_parent, user: user, updated_at: time)
+ end
+
+ it 'returns the most recent visited' do
+ create_visit(7.days.ago)
+ create_visit(5.days.ago)
+ recent = create_visit(1.day.ago)
+
+ expect(described_class.latest(user, board_parent)).to eq recent
+ end
+
+ it 'returns last 3 visited boards' do
+ create_visit(7.days.ago)
+ visit1 = create_visit(3.days.ago)
+ visit2 = create_visit(2.days.ago)
+ visit3 = create_visit(5.days.ago)
+
+ expect(described_class.latest(user, board_parent, count: 3)).to eq([visit2, visit1, visit3])
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/boards/create_service_shared_examples.rb b/spec/support/shared_examples/services/boards/create_service_shared_examples.rb
new file mode 100644
index 00000000000..63b5e3a5a84
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/create_service_shared_examples.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'boards recent visit create service' do
+ let_it_be(:user) { create(:user) }
+
+ subject(:service) { described_class.new(board.resource_parent, user) }
+
+ it 'returns nil when there is no user' do
+ service.current_user = nil
+
+ expect(service.execute(board)).to be_nil
+ end
+
+ it 'returns nil when database is read only' do
+ allow(Gitlab::Database).to receive(:read_only?) { true }
+
+ expect(service.execute(board)).to be_nil
+ end
+
+ it 'records the visit' do
+ expect(model).to receive(:visited!).once
+
+ service.execute(board)
+ 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 4aa5d7d890b..7d4fbeea0dc 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
@@ -146,7 +146,7 @@ RSpec.shared_examples 'issues move service' do |group|
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(issue.project, user, match_params).and_return(double(execute: build(:issue)))
+ 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
diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb b/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb
index 94da405e491..af88644ced7 100644
--- a/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb
@@ -3,30 +3,27 @@
RSpec.shared_examples 'lists destroy service' do
context 'when list type is label' do
it 'removes list from board' do
- list = create(:list, board: board)
service = described_class.new(parent, user)
expect { service.execute(list) }.to change(board.lists, :count).by(-1)
end
it 'decrements position of higher lists' do
- development = create(:list, board: board, position: 0)
- review = create(:list, board: board, position: 1)
- staging = create(:list, board: board, position: 2)
- closed = board.lists.closed.first
+ development = create(list_type, params.merge(position: 0))
+ review = create(list_type, params.merge(position: 1))
+ staging = create(list_type, params.merge(position: 2))
described_class.new(parent, user).execute(development)
expect(review.reload.position).to eq 0
expect(staging.reload.position).to eq 1
- expect(closed.reload.position).to be_nil
+ expect(closed_list.reload.position).to be_nil
end
end
it 'does not remove list from board when list type is closed' do
- list = board.lists.closed.first
service = described_class.new(parent, user)
- expect { service.execute(list) }.not_to change(board.lists, :count)
+ expect { service.execute(closed_list) }.not_to change(board.lists, :count)
end
end
diff --git a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
index 7b277d4bede..ce412ef55de 100644
--- a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
+++ b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.shared_examples 'system note creation' do |update_params, note_text|
- subject { described_class.new(project, user).execute(issuable, old_labels: []) }
+ subject { described_class.new(project: project, current_user: user).execute(issuable, old_labels: []) }
before do
issuable.assign_attributes(update_params)
@@ -18,7 +18,7 @@ RSpec.shared_examples 'system note creation' do |update_params, note_text|
end
RSpec.shared_examples 'draft notes creation' do |action|
- subject { described_class.new(project, user).execute(issuable, old_labels: []) }
+ subject { described_class.new(project: project, current_user: user).execute(issuable, old_labels: []) }
it 'creates Draft toggle and title change notes' do
expect { subject }.to change { Note.count }.from(0).to(2)
diff --git a/spec/support/shared_examples/services/destroy_label_links_shared_examples.rb b/spec/support/shared_examples/services/destroy_label_links_shared_examples.rb
new file mode 100644
index 00000000000..d2b52468c25
--- /dev/null
+++ b/spec/support/shared_examples/services/destroy_label_links_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'service deleting label links of an issuable' do
+ let_it_be(:label_link) { create(:label_link, target: target) }
+
+ def execute
+ described_class.new(target.id, target.class.name).execute
+ end
+
+ it 'deletes label links for specified target ID and type' do
+ control_count = ActiveRecord::QueryRecorder.new { execute }.count
+
+ # Create more label links for the target
+ create(:label_link, target: target)
+ create(:label_link, target: target)
+
+ expect { execute }.not_to exceed_query_limit(control_count)
+ expect(target.reload.label_links.count).to eq(0)
+ end
+end
diff --git a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
index ccc287c10de..e776c098fa0 100644
--- a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
@@ -1,10 +1,6 @@
# frozen_string_literal: true
shared_examples_for 'service deleting todos' do
- before do
- stub_feature_flags(destroy_issuable_todos_async: group)
- end
-
it 'destroys associated todos asynchronously' do
expect(TodosDestroyer::DestroyedIssuableWorker)
.to receive(:perform_async)
@@ -12,20 +8,14 @@ shared_examples_for 'service deleting todos' do
subject.execute(issuable)
end
+end
- context 'when destroy_issuable_todos_async feature is disabled for group' do
- before do
- stub_feature_flags(destroy_issuable_todos_async: false)
- end
-
- it 'destroy associated todos synchronously' do
- expect_next_instance_of(TodosDestroyer::DestroyedIssuableWorker) do |worker|
- expect(worker)
- .to receive(:perform)
- .with(issuable.id, issuable.class.name)
- end
+shared_examples_for 'service deleting label links' do
+ it 'destroys associated label links asynchronously' do
+ expect(Issuable::LabelLinksDestroyWorker)
+ .to receive(:perform_async)
+ .with(issuable.id, issuable.class.name)
- subject.execute(issuable)
- end
+ subject.execute(issuable)
end
end
diff --git a/spec/support/shared_examples/services/issuable_shared_examples.rb b/spec/support/shared_examples/services/issuable_shared_examples.rb
index 5b3e0f9e0b9..a50a386afe1 100644
--- a/spec/support/shared_examples/services/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable_shared_examples.rb
@@ -4,14 +4,14 @@ RSpec.shared_examples 'cache counters invalidator' do
it 'invalidates counter cache for assignees' do
expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts)
- described_class.new(project, user, {}).execute(merge_request)
+ described_class.new(project: project, current_user: user).execute(merge_request)
end
end
RSpec.shared_examples 'updating a single task' do
def update_issuable(opts)
issuable = try(:issue) || try(:merge_request)
- described_class.new(project, user, opts).execute(issuable)
+ described_class.new(project: project, current_user: user, params: opts).execute(issuable)
end
before do
diff --git a/spec/support/shared_examples/services/merge_request_shared_examples.rb b/spec/support/shared_examples/services/merge_request_shared_examples.rb
index 178b6bc47e1..d2595b92cbc 100644
--- a/spec/support/shared_examples/services/merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/services/merge_request_shared_examples.rb
@@ -70,7 +70,7 @@ RSpec.shared_examples 'merge request reviewers cache counters invalidator' do
it 'invalidates counter cache for reviewers' do
expect(merge_request.reviewers).to all(receive(:invalidate_merge_request_cache_counts))
- described_class.new(project, user, {}).execute(merge_request)
+ described_class.new(project: project, current_user: user).execute(merge_request)
end
end
@@ -86,7 +86,7 @@ RSpec.shared_examples_for 'a service that can create a merge request' do
context 'when project has been forked', :sidekiq_might_not_need_inline do
let(:forked_project) { fork_project(project, user1, repository: true) }
- let(:service) { described_class.new(forked_project, user1, changes, push_options) }
+ let(:service) { described_class.new(project: forked_project, current_user: user1, changes: changes, push_options: push_options) }
before do
allow(forked_project).to receive(:empty_repo?).and_return(false)
diff --git a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
index b6c33eac7b4..4df12f7849b 100644
--- a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
+++ b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
@@ -12,13 +12,22 @@ RSpec.shared_examples 'misconfigured dashboard service response' do |status_code
end
RSpec.shared_examples 'valid dashboard service response for schema' do
+ file_ref_resolver = proc do |uri|
+ file = Rails.root.join(uri.path)
+ raise StandardError, "Ref file #{uri.path} must be json" unless uri.path.ends_with?('.json')
+ raise StandardError, "File #{file.to_path} doesn't exists" unless file.exist?
+
+ Gitlab::Json.parse(File.read(file))
+ end
+
it 'returns a json representation of the dashboard' do
result = service_call
expect(result.keys).to contain_exactly(:dashboard, :status)
expect(result[:status]).to eq(:success)
- expect(JSON::Validator.fully_validate(dashboard_schema, result[:dashboard])).to be_empty
+ validator = JSONSchemer.schema(dashboard_schema, ref_resolver: file_ref_resolver)
+ expect(validator.valid?(result[:dashboard].with_indifferent_access)).to be true
end
end
diff --git a/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb b/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb
index 8398dd3c453..f7a6bd3676a 100644
--- a/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb
+++ b/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb
@@ -7,6 +7,8 @@ RSpec.shared_examples 'updating the namespace package setting attributes' do |fr
expect { subject }
.to change { namespace.package_settings.reload.maven_duplicates_allowed }.from(from[:maven_duplicates_allowed]).to(to[:maven_duplicates_allowed])
.and change { namespace.package_settings.reload.maven_duplicate_exception_regex }.from(from[:maven_duplicate_exception_regex]).to(to[:maven_duplicate_exception_regex])
+ .and change { namespace.package_settings.reload.generic_duplicates_allowed }.from(from[:generic_duplicates_allowed]).to(to[:generic_duplicates_allowed])
+ .and change { namespace.package_settings.reload.generic_duplicate_exception_regex }.from(from[:generic_duplicate_exception_regex]).to(to[:generic_duplicate_exception_regex])
end
end
@@ -26,6 +28,8 @@ RSpec.shared_examples 'creating the namespace package setting' do
expect(namespace.package_setting_relation.maven_duplicates_allowed).to eq(package_settings[:maven_duplicates_allowed])
expect(namespace.package_setting_relation.maven_duplicate_exception_regex).to eq(package_settings[:maven_duplicate_exception_regex])
+ expect(namespace.package_setting_relation.generic_duplicates_allowed).to eq(package_settings[:generic_duplicates_allowed])
+ expect(namespace.package_setting_relation.generic_duplicate_exception_regex).to eq(package_settings[:generic_duplicate_exception_regex])
end
it_behaves_like 'returning a success'
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
index 4e34c191306..72878e925dc 100644
--- a/spec/support/shared_examples/services/packages_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -203,7 +203,9 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false|
let_it_be(:package7) { create(:generic_package, project: project) }
let_it_be(:package8) { create(:golang_package, project: project) }
let_it_be(:package9) { create(:debian_package, project: project) }
- let_it_be(:package9) { create(:rubygems_package, project: project) }
+ let_it_be(:package10) { create(:rubygems_package, project: project) }
+ let_it_be(:package11) { create(:helm_package, project: project) }
+ let_it_be(:package12) { create(:terraform_module_package, project: project) }
Packages::Package.package_types.keys.each do |package_type|
context "for package type #{package_type}" do
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 1fb1b9f79b2..275ddebc18c 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
@@ -47,7 +47,7 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
expect(original_repository_double).to receive(:remove)
end
- it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read only" do
+ it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read-only" do
old_project_repository_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
project.repository.path_to_repo
end
diff --git a/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb b/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb
index e67fc4ab04a..97304680316 100644
--- a/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb
+++ b/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb
@@ -27,7 +27,7 @@ RSpec.shared_examples 'moves repository shard in bulk' do
container.set_repository_read_only!
expect(subject).to receive(:log_info)
- .with(/Container #{container.full_path} \(#{container.id}\) was skipped: #{container.class} is read only/)
+ .with(/Container #{container.full_path} \(#{container.id}\) was skipped: #{container.class} is read-only/)
expect { subject.execute(source_storage_name, destination_storage_name) }
.to change(move_service_klass, :count).by(0)
end
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
new file mode 100644
index 00000000000..538fd2bb513
--- /dev/null
+++ b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples_for 'services security ci configuration create service' do |skip_w_params|
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ describe '#execute' do
+ let(:params) { {} }
+
+ context 'user does not belong to project' do
+ it 'returns an error status' do
+ expect(result.status).to eq(:error)
+ expect(result.payload[:success_path]).to be_nil
+ end
+
+ it 'does not track a snowplow event' do
+ subject
+
+ expect_no_snowplow_event
+ end
+ end
+
+ context 'user belongs to project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'does track the snowplow event' do
+ subject
+
+ expect_snowplow_event(**snowplow_event)
+ end
+
+ it 'raises exception if the user does not have permission to create a new branch' do
+ allow(project).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "You are not allowed to create protected branches on this project.")
+
+ expect { subject }.to raise_error(Gitlab::Git::PreReceiveError)
+ end
+
+ context 'when exception is raised' do
+ let_it_be(:project) { create(:project, :repository) }
+
+ before do
+ allow(project.repository).to receive(:add_branch).and_raise(StandardError, "The unexpected happened!")
+ end
+
+ context 'when branch was created' do
+ before do
+ allow(project.repository).to receive(:branch_exists?).and_return(true)
+ end
+
+ it 'tries to rm branch' do
+ expect(project.repository).to receive(:rm_branch).with(user, branch_name)
+ expect { subject }.to raise_error(StandardError)
+ end
+ end
+
+ context 'when branch was not created' do
+ before do
+ allow(project.repository).to receive(:branch_exists?).and_return(false)
+ end
+
+ it 'does not try to rm branch' do
+ expect(project.repository).not_to receive(:rm_branch)
+ expect { subject }.to raise_error(StandardError)
+ end
+ end
+ end
+
+ context 'with no parameters' do
+ it 'returns the path to create a new merge request' do
+ expect(result.status).to eq(:success)
+ expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
+ end
+ end
+
+ unless skip_w_params
+ context 'with parameters' do
+ let(:params) { non_empty_params }
+
+ it 'returns the path to create a new merge request' do
+ expect(result.status).to eq(:success)
+ expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/updating_mentions_shared_examples.rb b/spec/support/shared_examples/services/updating_mentions_shared_examples.rb
index 84f6c4d136a..13a2aa9ddac 100644
--- a/spec/support/shared_examples/services/updating_mentions_shared_examples.rb
+++ b/spec/support/shared_examples/services/updating_mentions_shared_examples.rb
@@ -15,7 +15,7 @@ RSpec.shared_examples 'updating mentions' do |service_class|
def update_mentionable(opts)
perform_enqueued_jobs do
- service_class.new(project, user, opts).execute(mentionable)
+ service_class.new(project: project, current_user: user, params: opts).execute(mentionable)
end
mentionable.reload
diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb
index 95f0f971787..84396c675b9 100644
--- a/spec/support/stored_repositories.rb
+++ b/spec/support/stored_repositories.rb
@@ -3,7 +3,7 @@
RSpec.configure do |config|
config.before(:each, :broken_storage) do
allow(Gitlab::GitalyClient).to receive(:call) do
- raise GRPC::Unavailable.new('Gitaly broken in this spec')
+ raise GRPC::Unavailable, 'Gitaly broken in this spec'
end
end
end
diff --git a/spec/support/stub_languages_translation_percentage.rb b/spec/support/stub_languages_translation_percentage.rb
new file mode 100644
index 00000000000..a93316288b9
--- /dev/null
+++ b/spec/support/stub_languages_translation_percentage.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module StubLanguagesTranslationPercentage
+ # Stubs the translation percentage of the i18n languages
+ # - When a `blank?` list is given no stubbing is done;
+ # - When the list is not empty, the languages in the list
+ # are stubbed with the given values, any other language
+ # will have the translation percent set to 0;
+ def stub_languages_translation_percentage(list = {})
+ return if list.blank?
+
+ expect(Gitlab::I18n)
+ .to receive(:percentage_translated_for)
+ .at_least(:once)
+ .and_wrap_original do |_original, code|
+ list.with_indifferent_access[code].to_i
+ end
+ end
+end