summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-08-18 08:17:02 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-18 08:17:02 +0000
commitb39512ed755239198a9c294b6a45e65c05900235 (patch)
treed234a3efade1de67c46b9e5a38ce813627726aa7 /spec/support
parentd31474cf3b17ece37939d20082b07f6657cc79a9 (diff)
downloadgitlab-ce-b39512ed755239198a9c294b6a45e65c05900235.tar.gz
Add latest changes from gitlab-org/gitlab@15-3-stable-eev15.3.0-rc42
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/database/cross-join-allowlist.yml7
-rw-r--r--spec/support/database/gitlab_schemas_validate_connection.rb17
-rw-r--r--spec/support/database/multiple_databases.rb20
-rw-r--r--spec/support/finder_collection_allowlist.yml2
-rw-r--r--spec/support/helpers/api_helpers.rb13
-rw-r--r--spec/support/helpers/ci/template_helpers.rb4
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb25
-rw-r--r--spec/support/helpers/dns_helpers.rb25
-rw-r--r--spec/support/helpers/features/blob_spec_helpers.rb8
-rw-r--r--spec/support/helpers/features/invite_members_modal_helper.rb43
-rw-r--r--spec/support/helpers/features/runners_helpers.rb (renamed from spec/support/helpers/features/runner_helpers.rb)0
-rw-r--r--spec/support/helpers/features/source_editor_spec_helpers.rb5
-rw-r--r--spec/support/helpers/gitaly_setup.rb14
-rw-r--r--spec/support/helpers/global_id_deprecation_helpers.rb8
-rw-r--r--spec/support/helpers/graphql_helpers.rb8
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb21
-rw-r--r--spec/support/helpers/lfs_http_helpers.rb4
-rw-r--r--spec/support/helpers/login_helpers.rb4
-rw-r--r--spec/support/helpers/query_recorder.rb2
-rw-r--r--spec/support/helpers/rack_attack_spec_helpers.rb6
-rw-r--r--spec/support/helpers/redis_commands/recorder.rb34
-rw-r--r--spec/support/helpers/runner_releases_helper.rb22
-rw-r--r--spec/support/helpers/stub_member.rb8
-rw-r--r--spec/support/helpers/stub_method_calls.rb2
-rw-r--r--spec/support/helpers/stubbed_member.rb28
-rw-r--r--spec/support/helpers/type_name_deprecation_helpers.rb15
-rw-r--r--spec/support/matchers/event_store.rb4
-rw-r--r--spec/support/matchers/markdown_matchers.rb4
-rw-r--r--spec/support/shared_contexts/bulk_imports_requests_shared_context.rb14
-rw-r--r--spec/support/shared_contexts/features/integrations/integrations_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/fixtures/analytics_shared_context.rb1
-rw-r--r--spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb8
-rw-r--r--spec/support/shared_contexts/markdown_snapshot_shared_examples.rb5
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb1
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb1
-rw-r--r--spec/support/shared_contexts/policies/project_policy_table_shared_context.rb57
-rw-r--r--spec/support/shared_contexts/upload_type_check_shared_context.rb14
-rw-r--r--spec/support/shared_examples/attention_request_cache_invalidation_examples.rb15
-rw-r--r--spec/support/shared_examples/boards/destroy_service_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/components/pajamas_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb21
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb80
-rw-r--r--spec/support/shared_examples/features/access_tokens_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/features/inviting_members_shared_examples.rb110
-rw-r--r--spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/trial_email_validation_shared_example.rb59
-rw-r--r--spec/support/shared_examples/features/user_views_tag_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/features/variable_list_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb97
-rw-r--r--spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/graphql/notes_creation_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb64
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/issuable_link_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/models/project_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/models/taskable_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/namespaces/traversal_scope_examples.rb16
-rw-r--r--spec/support/shared_examples/policies/group_project_namespace_policy_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/requests/api/discussions_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb47
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/boards/lists_move_service_shared_examples.rb120
-rw-r--r--spec/support/shared_examples/services/issuable_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb89
-rw-r--r--spec/support/shared_examples/services/work_items/create_task_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb31
87 files changed, 1310 insertions, 427 deletions
diff --git a/spec/support/database/cross-join-allowlist.yml b/spec/support/database/cross-join-allowlist.yml
index 19b1ce30d5f..fe51488c706 100644
--- a/spec/support/database/cross-join-allowlist.yml
+++ b/spec/support/database/cross-join-allowlist.yml
@@ -1,6 +1 @@
-- "./spec/lib/gitlab/background_migration/copy_ci_builds_columns_to_security_scans_spec.rb"
-- "./spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb"
-- "./spec/migrations/20210907211557_finalize_ci_builds_bigint_conversion_spec.rb"
-- "./spec/migrations/associate_existing_dast_builds_with_variables_spec.rb"
-- "./spec/migrations/disable_job_token_scope_when_unused_spec.rb"
-- "./spec/migrations/schedule_copy_ci_builds_columns_to_security_scans2_spec.rb"
+[]
diff --git a/spec/support/database/gitlab_schemas_validate_connection.rb b/spec/support/database/gitlab_schemas_validate_connection.rb
new file mode 100644
index 00000000000..118c6ea5001
--- /dev/null
+++ b/spec/support/database/gitlab_schemas_validate_connection.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ def with_gitlab_schemas_validate_connection_prevented
+ Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection.with_suppressed do
+ yield
+ end
+ end
+
+ config.around(:each, :suppress_gitlab_schemas_validate_connection) do |example|
+ with_gitlab_schemas_validate_connection_prevented(&example)
+ end
+
+ config.around(:each, query_analyzers: false) do |example|
+ with_gitlab_schemas_validate_connection_prevented(&example)
+ end
+end
diff --git a/spec/support/database/multiple_databases.rb b/spec/support/database/multiple_databases.rb
index 94857b47127..05f26e57e9c 100644
--- a/spec/support/database/multiple_databases.rb
+++ b/spec/support/database/multiple_databases.rb
@@ -98,6 +98,26 @@ RSpec.configure do |config|
example.run
end
end
+
+ config.around(:each, :migration) do |example|
+ migration_schema = example.metadata[:migration]
+ migration_schema = :gitlab_main if migration_schema == true
+ base_model = Gitlab::Database.schemas_to_base_models.fetch(migration_schema).first
+
+ # Migration require an `ActiveRecord::Base` to point to desired database
+ if base_model != ActiveRecord::Base
+ with_reestablished_active_record_base do
+ reconfigure_db_connection(
+ model: ActiveRecord::Base,
+ config_model: base_model
+ )
+
+ example.run
+ end
+ else
+ example.run
+ end
+ end
end
ActiveRecord::Base.singleton_class.prepend(::Database::ActiveRecordBaseEstablishConnection) # rubocop:disable Database/MultipleDatabases
diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml
index 8f09153afec..1ac8e49fb45 100644
--- a/spec/support/finder_collection_allowlist.yml
+++ b/spec/support/finder_collection_allowlist.yml
@@ -57,6 +57,8 @@
- Security::ScanExecutionPoliciesFinder
- Security::TrainingProviders::BaseUrlFinder
- Security::TrainingUrlsFinder
+- Security::TrainingProviders::KontraUrlFinder
+- Security::TrainingProviders::SecureCodeWarriorUrlFinder
- SentryIssueFinder
- ServerlessDomainFinder
- TagsFinder
diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb
index fd85071cca3..62bb9576695 100644
--- a/spec/support/helpers/api_helpers.rb
+++ b/spec/support/helpers/api_helpers.rb
@@ -19,15 +19,17 @@ module ApiHelpers
# => "/api/v2/issues?foo=bar&private_token=..."
#
# Returns the relative path to the requested API resource
- def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil, job_token: nil)
+ def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil, job_token: nil, access_token: nil)
full_path = "/api/#{version}#{path}"
if oauth_access_token
- query_string = "access_token=#{oauth_access_token.token}"
+ query_string = "access_token=#{oauth_access_token.plaintext_token}"
elsif personal_access_token
query_string = "private_token=#{personal_access_token.token}"
elsif job_token
query_string = "job_token=#{job_token}"
+ elsif access_token
+ query_string = "access_token=#{access_token.token}"
elsif user
personal_access_token = create(:personal_access_token, user: user)
query_string = "private_token=#{personal_access_token.token}"
@@ -66,6 +68,13 @@ module ApiHelpers
expect(json_response.map { |item| item['id'] }).to contain_exactly(*items)
end
+ def expect_paginated_array_response_contain_exactly(*items)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |item| item['id'] }).to contain_exactly(*items)
+ end
+
def stub_last_activity_update
allow_any_instance_of(Users::ActivityService).to receive(:execute)
end
diff --git a/spec/support/helpers/ci/template_helpers.rb b/spec/support/helpers/ci/template_helpers.rb
index 598a5a0becc..119f8d001a1 100644
--- a/spec/support/helpers/ci/template_helpers.rb
+++ b/spec/support/helpers/ci/template_helpers.rb
@@ -5,6 +5,10 @@ module Ci
def secure_analyzers_prefix
'registry.gitlab.com/security-products'
end
+
+ def template_registry_host
+ 'registry.gitlab.com'
+ end
end
end
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index 044ec56b1cc..05e9a099a2b 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
module CycleAnalyticsHelpers
- include GitHelpers
-
def toggle_value_stream_dropdown
page.find('[data-testid="dropdown-value-streams"]').click
end
@@ -129,10 +127,6 @@ module CycleAnalyticsHelpers
repository = project.repository
oldrev = repository.commit(branch_name)&.sha || Gitlab::Git::BLANK_SHA
- if Timecop.frozen?
- mock_gitaly_multi_action_dates(repository, commit_time)
- end
-
commit_shas = Array.new(count) do |index|
commit_sha = repository.create_file(user, generate(:branch), "content", message: message, branch_name: branch_name)
repository.commit(commit_sha)
@@ -241,23 +235,4 @@ module CycleAnalyticsHelpers
pipeline: dummy_pipeline(project),
protected: false)
end
-
- def mock_gitaly_multi_action_dates(repository, commit_time)
- allow(repository.raw).to receive(:multi_action).and_wrap_original do |m, user, kargs|
- new_date = commit_time || Time.now
- branch_update = m.call(user, **kargs)
-
- if branch_update.newrev
- commit = rugged_repo(repository).rev_parse(branch_update.newrev)
-
- branch_update.newrev = commit.amend(
- update_ref: "#{Gitlab::Git::BRANCH_REF_PREFIX}#{kargs[:branch_name]}",
- author: commit.author.merge(time: new_date),
- committer: commit.committer.merge(time: new_date)
- )
- end
-
- branch_update
- end
- end
end
diff --git a/spec/support/helpers/dns_helpers.rb b/spec/support/helpers/dns_helpers.rb
index b941e7c4808..c60c14f10a3 100644
--- a/spec/support/helpers/dns_helpers.rb
+++ b/spec/support/helpers/dns_helpers.rb
@@ -5,6 +5,7 @@ module DnsHelpers
stub_all_dns!
stub_invalid_dns!
permit_local_dns!
+ permit_postgresql!
end
def permit_dns!
@@ -25,14 +26,30 @@ module DnsHelpers
def permit_local_dns!
local_addresses = %r{
\A
- ::1? | # IPV6
- (127|10)\.0\.0\.\d{1,3} | # 127.0.0.x or 10.0.0.x local network
- (192\.168|172\.16)\.\d{1,3}\.\d{1,3} | # 192.168.x.x or 172.16.x.x local network
- 0\.0\.0\.0 | # loopback
+ ::1? | # IPV6
+ (127|10)\.0\.0\.\d{1,3} | # 127.0.0.x or 10.0.0.x local network
+ 192\.168\.\d{1,3}\.\d{1,3} | # 192.168.x.x local network
+ 172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3} | # 172.16.x.x - 172.31.x.x local network
+ 0\.0\.0\.0 | # loopback
localhost
\z
}xi
allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM).and_call_original
allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM, anything, anything, any_args).and_call_original
end
+
+ # pg v1.4.0, unlike v1.3.5, uses AddrInfo.getaddrinfo to resolve IPv4 and IPv6 addresses:
+ # https://github.com/ged/ruby-pg/pull/459
+ def permit_postgresql!
+ db_hosts.each do |host|
+ next if host.start_with?('/') # Exclude UNIX sockets
+
+ # https://github.com/ged/ruby-pg/blob/252512608a814de16bbad55911f9bbcef0e73cb9/lib/pg/connection.rb#L720
+ allow(Addrinfo).to receive(:getaddrinfo).with(host, anything, nil, :STREAM).and_call_original
+ end
+ end
+
+ def db_hosts
+ ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).map(&:host).compact.uniq
+ end
end
diff --git a/spec/support/helpers/features/blob_spec_helpers.rb b/spec/support/helpers/features/blob_spec_helpers.rb
index 880a7249284..7ccfc9be7e2 100644
--- a/spec/support/helpers/features/blob_spec_helpers.rb
+++ b/spec/support/helpers/features/blob_spec_helpers.rb
@@ -11,12 +11,4 @@ module BlobSpecHelpers
def unset_default_button
set_default_button('')
end
-
- def editor_value
- evaluate_script('monaco.editor.getModels()[0].getValue()')
- end
-
- def set_editor_value(value)
- execute_script("monaco.editor.getModels()[0].setValue('#{value}')")
- end
end
diff --git a/spec/support/helpers/features/invite_members_modal_helper.rb b/spec/support/helpers/features/invite_members_modal_helper.rb
index b56ac5b32c6..d02ec06d886 100644
--- a/spec/support/helpers/features/invite_members_modal_helper.rb
+++ b/spec/support/helpers/features/invite_members_modal_helper.rb
@@ -11,7 +11,7 @@ module Spec
page.within invite_modal_selector do
select_members(names)
choose_options(role, expires_at)
- click_button 'Invite'
+ submit_invites
end
page.refresh if refresh
@@ -42,11 +42,15 @@ module Spec
click_button name
choose_options(role, expires_at)
- click_button 'Invite'
+ submit_invites
page.refresh
end
+ def submit_invites
+ click_button 'Invite'
+ end
+
def choose_options(role, expires_at)
unless role == 'Guest'
click_button 'Guest'
@@ -86,12 +90,47 @@ module Spec
"[data-token-id='#{id}']"
end
+ def more_invite_errors_button_selector
+ "[data-testid='accordion-button']"
+ end
+
+ def limited_invite_error_selector
+ "[data-testid='errors-limited-item']"
+ end
+
+ def expanded_invite_error_selector
+ "[data-testid='errors-expanded-item']"
+ end
+
def remove_token(id)
page.within member_token_selector(id) do
find('[data-testid="close-icon"]').click
end
end
+ def expect_to_have_successful_invite_indicator(page, user)
+ expect(page).to have_selector("#{member_token_selector(user.id)} .gl-bg-green-100")
+ expect(page).not_to have_text("#{user.name}: ")
+ end
+
+ def expect_to_have_invalid_invite_indicator(page, user, message: true)
+ expect(page).to have_selector("#{member_token_selector(user.id)} .gl-bg-red-100")
+ expect(page).to have_selector(member_token_error_selector(user.id))
+ expect(page).to have_text("#{user.name}: Access level should be greater than or equal to") if message
+ end
+
+ def expect_to_have_normal_invite_indicator(page, user)
+ expect(page).to have_selector(member_token_selector(user.id))
+ expect(page).not_to have_selector("#{member_token_selector(user.id)} .gl-bg-red-100")
+ expect(page).not_to have_selector("#{member_token_selector(user.id)} .gl-bg-green-100")
+ expect(page).not_to have_text("#{user.name}: ")
+ end
+
+ def expect_to_have_invite_removed(page, user)
+ expect(page).not_to have_selector(member_token_selector(user.id))
+ expect(page).not_to have_text("#{user.name}: Access level should be greater than or equal to")
+ end
+
def expect_to_have_group(group)
expect(page).to have_selector("[entity-id='#{group.id}']")
end
diff --git a/spec/support/helpers/features/runner_helpers.rb b/spec/support/helpers/features/runners_helpers.rb
index 63fc628358c..63fc628358c 100644
--- a/spec/support/helpers/features/runner_helpers.rb
+++ b/spec/support/helpers/features/runners_helpers.rb
diff --git a/spec/support/helpers/features/source_editor_spec_helpers.rb b/spec/support/helpers/features/source_editor_spec_helpers.rb
index cdc59f9cbe1..f7eb2a52507 100644
--- a/spec/support/helpers/features/source_editor_spec_helpers.rb
+++ b/spec/support/helpers/features/source_editor_spec_helpers.rb
@@ -12,8 +12,11 @@ module Spec
def editor_set_value(value)
editor = find('.monaco-editor')
uri = editor['data-uri']
+ execute_script("localMonaco.getModel('#{uri}').setValue('#{escape_javascript(value)}')")
- execute_script("monaco.editor.getModel('#{uri}').setValue('#{escape_javascript(value)}')")
+ # We only check that the first line is present because when the content is long,
+ # only a part of the text will be rendered in the DOM due to scrolling
+ page.has_selector?('.gl-source-editor .view-lines', text: value.lines.first)
end
end
end
diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb
index 56993fc27b7..278dc79e1d0 100644
--- a/spec/support/helpers/gitaly_setup.rb
+++ b/spec/support/helpers/gitaly_setup.rb
@@ -12,6 +12,8 @@ require 'logger'
require 'fileutils'
require 'bundler'
+require_relative '../../../lib/gitlab/utils'
+
module GitalySetup
extend self
@@ -139,7 +141,7 @@ module GitalySetup
end
def start_praefect
- if ENV['GITALY_PRAEFECT_WITH_DB']
+ if praefect_with_db?
LOGGER.debug 'Starting Praefect with database election strategy'
start(:praefect, File.join(tmp_tests_gitaly_dir, 'praefect-db.config.toml'))
else
@@ -290,7 +292,7 @@ module GitalySetup
# In CI we need to pre-generate both config files.
# For local testing we'll create the correct file on-demand.
- if ENV['CI'] || ENV['GITALY_PRAEFECT_WITH_DB'].nil?
+ if ENV['CI'] || !praefect_with_db?
Gitlab::SetupHelper::Praefect.create_configuration(
gitaly_dir,
{ 'praefect' => repos_path },
@@ -298,7 +300,7 @@ module GitalySetup
)
end
- if ENV['CI'] || ENV['GITALY_PRAEFECT_WITH_DB']
+ if ENV['CI'] || praefect_with_db?
Gitlab::SetupHelper::Praefect.create_configuration(
gitaly_dir,
{ 'praefect' => repos_path },
@@ -319,7 +321,7 @@ module GitalySetup
end
def setup_praefect
- return unless ENV['GITALY_PRAEFECT_WITH_DB']
+ return unless praefect_with_db?
migrate_cmd = service_cmd(:praefect, File.join(tmp_tests_gitaly_dir, 'praefect-db.config.toml')) + ['sql-migrate']
system(env, *migrate_cmd, [:out, :err] => 'log/praefect-test.log')
@@ -396,4 +398,8 @@ module GitalySetup
def praefect_binary
File.join(tmp_tests_gitaly_dir, "_build", "bin", "praefect")
end
+
+ def praefect_with_db?
+ Gitlab::Utils.to_boolean(ENV['GITALY_PRAEFECT_WITH_DB'], default: false)
+ end
end
diff --git a/spec/support/helpers/global_id_deprecation_helpers.rb b/spec/support/helpers/global_id_deprecation_helpers.rb
index 37ba1420fb3..5c6862ca84a 100644
--- a/spec/support/helpers/global_id_deprecation_helpers.rb
+++ b/spec/support/helpers/global_id_deprecation_helpers.rb
@@ -2,9 +2,11 @@
module GlobalIDDeprecationHelpers
def stub_global_id_deprecations(*deprecations)
- old_name_map = deprecations.index_by(&:old_model_name)
- new_name_map = deprecations.index_by(&:new_model_name)
- old_graphql_name_map = deprecations.index_by { |d| Types::GlobalIDType.model_name_to_graphql_name(d.old_model_name) }
+ old_name_map = deprecations.index_by(&:old_name)
+ new_name_map = deprecations.index_by(&:new_name)
+ old_graphql_name_map = deprecations.index_by do |d|
+ Gitlab::GlobalId::Deprecations.map_graphql_name(d.old_name)
+ end
stub_const('Gitlab::GlobalId::Deprecations::OLD_NAME_MAP', old_name_map)
stub_const('Gitlab::GlobalId::Deprecations::NEW_NAME_MAP', new_name_map)
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index d0a1941817a..d78c523decd 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -170,7 +170,7 @@ module GraphqlHelpers
# or `prepare` in app/graphql/types/range_input_type.rb, used by Types::TimeframeInputType
def args_internal(field, args:, query_ctx:, parent:, extras:, query:)
arguments = GraphqlHelpers.deep_transform_args(args, field)
- arguments.merge!(extras.reject {|k, v| v == :not_given})
+ arguments.merge!(extras.reject { |k, v| v == :not_given })
end
# Pros:
@@ -185,7 +185,7 @@ module GraphqlHelpers
# take internal style args, and force them into client style args
def args_internal_prepared(field, args:, query_ctx:, parent:, extras:, query:)
arguments = GraphqlHelpers.as_graphql_argument_literals(args)
- arguments.merge!(extras.reject {|k, v| v == :not_given})
+ arguments.merge!(extras.reject { |k, v| v == :not_given })
# Use public API to properly prepare the args for use by the resolver.
# It uses `coerce_arguments` under the covers
@@ -307,14 +307,14 @@ module GraphqlHelpers
end
def graphql_mutation(name, input, fields = nil, &block)
- raise ArgumentError, 'Please pass either `fields` parameter or a block to `#graphql_mutation`, but not both.' if fields.present? && block_given?
+ raise ArgumentError, 'Please pass either `fields` parameter or a block to `#graphql_mutation`, but not both.' if fields.present? && block
name = name.graphql_name if name.respond_to?(:graphql_name)
mutation_name = GraphqlHelpers.fieldnamerize(name)
input_variable_name = "$#{input_variable_name_for_mutation(name)}"
mutation_field = GitlabSchema.mutation.fields[mutation_name]
- fields = yield if block_given?
+ fields = yield if block
fields ||= all_graphql_fields_for(mutation_field.type.to_type_signature)
query = <<~MUTATION
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index 84cd0181533..32e6e8d50bd 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -2,6 +2,7 @@
require 'action_dispatch/testing/test_request'
require 'fileutils'
+require 'graphlyte'
require_relative '../../../lib/gitlab/popen'
@@ -47,7 +48,8 @@ module JavaScriptFixturesHelpers
path = Rails.root / base / query_path
queries = Gitlab::Graphql::Queries.find(path)
if queries.length == 1
- queries.first.text(mode: Gitlab.ee? ? :ee : :ce )
+ query = queries.first.text(mode: Gitlab.ee? ? :ee : :ce )
+ inflate_query_with_typenames(query)
else
raise "Could not find query file at #{path}, please check your query_path" % path
end
@@ -55,6 +57,23 @@ module JavaScriptFixturesHelpers
private
+ # Private: Parse a GraphQL query and inflate the fields with a __typename
+ #
+ # query - the GraqhQL query to parse
+ def inflate_query_with_typenames(query, doc: Graphlyte.parse(query))
+ typename_editor.edit(doc)
+
+ doc.to_s
+ end
+
+ def typename_editor
+ typename = Graphlyte::Syntax::Field.new(name: '__typename')
+
+ @editor ||= Graphlyte::Editor.new.on_field do |field|
+ field.selection << typename unless field.selection.empty? || field.selection.map(&:name).include?('__typename')
+ end
+ end
+
# Private: Store a response object as fixture file
#
# response - string or response object to store
diff --git a/spec/support/helpers/lfs_http_helpers.rb b/spec/support/helpers/lfs_http_helpers.rb
index 199d5e70e32..91ed56b4d13 100644
--- a/spec/support/helpers/lfs_http_helpers.rb
+++ b/spec/support/helpers/lfs_http_helpers.rb
@@ -52,11 +52,9 @@ module LfsHttpHelpers
end
def request_body(operation, objects)
- objects = [objects] unless objects.is_a?(Array)
-
{
'operation' => operation,
- 'objects' => objects
+ 'objects' => Array.wrap(objects)
}
end
end
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index c93ef8b0ead..f83f5c7bfde 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -91,12 +91,12 @@ module LoginHelpers
# user - User instance to login with
# remember - Whether or not to check "Remember me" (default: false)
# two_factor_auth - If two-factor authentication is enabled (default: false)
- # password - password to attempt to login with
+ # password - password to attempt to login with (default: user.password)
def gitlab_sign_in_with(user, remember: false, two_factor_auth: false, password: nil)
visit new_user_session_path
fill_in "user_login", with: user.email
- fill_in "user_password", with: (password || "12345678")
+ fill_in "user_password", with: (password || user.password)
check 'user_remember_me' if remember
find('[data-testid="sign-in-button"]:enabled').click
diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index 01839a74e65..dd124ed9c7f 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -14,7 +14,7 @@ module ActiveRecord
@skip_schema_queries = skip_schema_queries
@query_recorder_debug = ENV['QUERY_RECORDER_DEBUG'] || query_recorder_debug
@log_file = log_file
- record(&block) if block_given?
+ record(&block) if block
end
def record(&block)
diff --git a/spec/support/helpers/rack_attack_spec_helpers.rb b/spec/support/helpers/rack_attack_spec_helpers.rb
index 6c06781df03..2502889e17c 100644
--- a/spec/support/helpers/rack_attack_spec_helpers.rb
+++ b/spec/support/helpers/rack_attack_spec_helpers.rb
@@ -17,8 +17,12 @@ module RackAttackSpecHelpers
{ Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER => personal_access_token.token }
end
+ def bearer_headers(token)
+ { 'AUTHORIZATION' => "Bearer #{token.token}" }
+ end
+
def oauth_token_headers(oauth_access_token)
- { 'AUTHORIZATION' => "Bearer #{oauth_access_token.token}" }
+ { 'AUTHORIZATION' => "Bearer #{oauth_access_token.plaintext_token}" }
end
def basic_auth_headers(user, personal_access_token)
diff --git a/spec/support/helpers/redis_commands/recorder.rb b/spec/support/helpers/redis_commands/recorder.rb
new file mode 100644
index 00000000000..05a1aa67853
--- /dev/null
+++ b/spec/support/helpers/redis_commands/recorder.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module RedisCommands
+ class Recorder
+ def initialize(pattern: nil, &block)
+ @log = []
+ @pattern = pattern
+
+ record(&block) if block
+ end
+
+ attr_reader :log
+
+ def record(&block)
+ ActiveSupport::Notifications.subscribed(method(:callback), 'redis.process_commands', &block)
+ end
+
+ def by_command(command)
+ @log.select { |record| record.include?(command) }
+ end
+
+ def count
+ @count ||= @log.count
+ end
+
+ private
+
+ def callback(name, start, finish, message_id, values)
+ commands = values[:commands]
+
+ @log << commands.flatten if @pattern.nil? || commands.to_s.include?(@pattern)
+ end
+ end
+end
diff --git a/spec/support/helpers/runner_releases_helper.rb b/spec/support/helpers/runner_releases_helper.rb
new file mode 100644
index 00000000000..ab16a705425
--- /dev/null
+++ b/spec/support/helpers/runner_releases_helper.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module RunnerReleasesHelper
+ def stub_runner_releases(available_runner_releases, gitlab_version: nil)
+ # We stub the behavior of RunnerReleases so that we don't need to rely on flaky global settings
+ available_runner_releases = available_runner_releases
+ .map { |v| ::Gitlab::VersionInfo.parse(v, parse_suffix: true) }
+ .sort
+ releases_by_minor = available_runner_releases
+ .group_by(&:without_patch)
+ .transform_values(&:max)
+
+ runner_releases_double = instance_double(Gitlab::Ci::RunnerReleases)
+ allow(::Gitlab::Ci::RunnerUpgradeCheck).to receive(:new).and_wrap_original do |method, *_original_args|
+ gitlab_version ||= available_runner_releases.max
+ method.call(gitlab_version, runner_releases_double)
+ end
+
+ allow(runner_releases_double).to receive(:releases).and_return(available_runner_releases)
+ allow(runner_releases_double).to receive(:releases_by_minor).and_return(releases_by_minor)
+ end
+end
diff --git a/spec/support/helpers/stub_member.rb b/spec/support/helpers/stub_member.rb
new file mode 100644
index 00000000000..bcd0b675041
--- /dev/null
+++ b/spec/support/helpers/stub_member.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module StubMember
+ def self.included(base)
+ Member.prepend(StubbedMember::Member)
+ ProjectMember.prepend(StubbedMember::ProjectMember)
+ end
+end
diff --git a/spec/support/helpers/stub_method_calls.rb b/spec/support/helpers/stub_method_calls.rb
index 45d704958ca..ccbede16563 100644
--- a/spec/support/helpers/stub_method_calls.rb
+++ b/spec/support/helpers/stub_method_calls.rb
@@ -44,7 +44,7 @@ module StubMethodCalls
end
def self.stub_method(object, method, &block)
- raise ArgumentError, "Block is required" unless block_given?
+ raise ArgumentError, "Block is required" unless block
backup_method(object, method) unless backed_up_method?(object, method)
object.define_singleton_method(method, &block)
diff --git a/spec/support/helpers/stubbed_member.rb b/spec/support/helpers/stubbed_member.rb
new file mode 100644
index 00000000000..27420c9b709
--- /dev/null
+++ b/spec/support/helpers/stubbed_member.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+# Extend the ProjectMember & GroupMember class with the ability to
+# to run project_authorizations refresh jobs inline.
+
+# This is needed so that calls like `group.add_member(user, access_level)` or `create(:project_member)`
+# in the specs can be run without including `:sidekiq_inline` trait.
+module StubbedMember
+ extend ActiveSupport::Concern
+
+ module Member
+ private
+
+ def refresh_member_authorized_projects(blocking:)
+ return super unless blocking
+
+ AuthorizedProjectsWorker.new.perform(user_id)
+ end
+ end
+
+ module ProjectMember
+ private
+
+ def blocking_project_authorizations_refresh
+ AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker.new.perform(project.id, user.id)
+ end
+ end
+end
diff --git a/spec/support/helpers/type_name_deprecation_helpers.rb b/spec/support/helpers/type_name_deprecation_helpers.rb
new file mode 100644
index 00000000000..591737ab532
--- /dev/null
+++ b/spec/support/helpers/type_name_deprecation_helpers.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module TypeNameDeprecationHelpers
+ def stub_type_name_deprecations(*deprecations)
+ old_name_map = deprecations.index_by(&:old_name)
+ new_name_map = deprecations.index_by(&:new_name)
+ old_graphql_name_map = deprecations.index_by do |d|
+ Gitlab::Graphql::TypeNameDeprecations.map_graphql_name(d.old_name)
+ end
+
+ stub_const('Gitlab::Graphql::TypeNameDeprecations::OLD_NAME_MAP', old_name_map)
+ stub_const('Gitlab::Graphql::TypeNameDeprecations::NEW_NAME_MAP', new_name_map)
+ stub_const('Gitlab::Graphql::TypeNameDeprecations::OLD_GRAPHQL_NAME_MAP', old_graphql_name_map)
+ end
+end
diff --git a/spec/support/matchers/event_store.rb b/spec/support/matchers/event_store.rb
index 14f6a42d7f4..4ecb924b3ed 100644
--- a/spec/support/matchers/event_store.rb
+++ b/spec/support/matchers/event_store.rb
@@ -23,8 +23,8 @@ RSpec::Matchers.define :publish_event do |expected_event_class|
def match_data?(actual, expected)
values_match?(actual.keys, expected.keys) &&
- actual.keys.each do |key|
- values_match?(actual[key], expected[key])
+ actual.keys.all? do |key|
+ values_match?(expected[key], actual[key])
end
end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 1932f78506f..8bec3be2535 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -189,8 +189,10 @@ module MarkdownMatchers
match do |actual|
expect(actual).to have_selector('ul.task-list', count: 2)
- expect(actual).to have_selector('li.task-list-item', count: 7)
+ expect(actual).to have_selector('li.task-list-item', count: 9)
+ expect(actual).to have_selector('li.task-list-item.inapplicable > s', count: 2)
expect(actual).to have_selector('input[checked]', count: 3)
+ expect(actual).to have_selector('input[data-inapplicable]', count: 2)
end
end
diff --git a/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb b/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb
index 62d708420c3..5fcb14e075a 100644
--- a/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb
+++ b/spec/support/shared_contexts/bulk_imports_requests_shared_context.rb
@@ -12,17 +12,17 @@ RSpec.shared_context 'bulk imports requests context' do |url|
}
end
- let(:request_headers) { { 'Authorization' => 'Bearer demo-pat', 'Content-Type' => 'application/json' } }
+ let(:request_headers) { { 'Content-Type' => 'application/json' } }
before do
- stub_request(:get, "#{url}/api/v4/version")
+ stub_request(:get, "#{url}/api/v4/version?page=1&per_page=20&private_token=demo-pat")
.with(headers: request_headers)
.to_return(
status: 200,
body: { version: ::BulkImport.min_gl_version_for_project_migration.to_s }.to_json,
headers: { 'Content-Type' => 'application/json' })
- stub_request(:get, "https://gitlab.example.com/api/v4/groups?min_access_level=50&page=1&per_page=20&search=test&top_level_only=true")
+ stub_request(:get, "https://gitlab.example.com/api/v4/groups?min_access_level=50&page=1&per_page=20&private_token=demo-pat&search=test&top_level_only=true")
.with(headers: request_headers)
.to_return(status: 200,
body: [{
@@ -33,10 +33,9 @@ RSpec.shared_context 'bulk imports requests context' do |url|
full_name: 'Test',
full_path: 'stub-test-group'
}].to_json,
- headers: page_response_headers
- )
+ headers: page_response_headers)
- stub_request(:get, "%{url}/api/v4/groups?page=1&per_page=20&top_level_only=true&min_access_level=50&search=" % { url: url })
+ stub_request(:get, "%{url}/api/v4/groups?min_access_level=50&page=1&per_page=20&private_token=demo-pat&search=&top_level_only=true" % { url: url })
.to_return(
body: [{
id: 2595438,
@@ -46,7 +45,6 @@ RSpec.shared_context 'bulk imports requests context' do |url|
full_name: 'Stub',
full_path: 'stub-group'
}].to_json,
- headers: page_response_headers
- )
+ headers: page_response_headers)
end
end
diff --git a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
index 255c4e6f882..ca2fe8a6c54 100644
--- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
+++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
@@ -66,7 +66,7 @@ Integration.available_integration_names.each do |integration|
hash.merge!(k => 'foo@bar.com')
elsif (integration == 'slack' || integration == 'mattermost') && k == :labels_to_be_notified_behavior
hash.merge!(k => "match_any")
- elsif integration == 'campfire' && k = :room
+ elsif integration == 'campfire' && k == :room
hash.merge!(k => '1234')
else
hash.merge!(k => "someword")
diff --git a/spec/support/shared_contexts/fixtures/analytics_shared_context.rb b/spec/support/shared_contexts/fixtures/analytics_shared_context.rb
index 13d3697a378..8e09cccee3e 100644
--- a/spec/support/shared_contexts/fixtures/analytics_shared_context.rb
+++ b/spec/support/shared_contexts/fixtures/analytics_shared_context.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
RSpec.shared_context 'Analytics fixtures shared context' do
+ include CycleAnalyticsHelpers
include JavaScriptFixturesHelpers
let_it_be(:group) { create(:group) }
diff --git a/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb
index 449db59e35d..b6c54e902a2 100644
--- a/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb
+++ b/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb
@@ -17,6 +17,7 @@ RSpec.shared_context 'server metrics with mocked prometheus' do
let(:elasticsearch_seconds_metric) { double('elasticsearch seconds metric') }
let(:elasticsearch_requests_total) { double('elasticsearch calls total metric') }
let(:load_balancing_metric) { double('load balancing metric') }
+ let(:sidekiq_mem_total_bytes) { double('sidekiq mem total bytes') }
before do
allow(Gitlab::Metrics).to receive(:histogram).and_call_original
@@ -37,6 +38,7 @@ RSpec.shared_context 'server metrics with mocked prometheus' do
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_load_balancing_count, anything).and_return(load_balancing_metric)
allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :all).and_return(running_jobs_metric)
allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_concurrency, anything, {}, :all).and_return(concurrency_metric)
+ allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_mem_total_bytes, anything, {}, :all).and_return(sidekiq_mem_total_bytes)
allow(concurrency_metric).to receive(:set)
end
@@ -61,13 +63,16 @@ RSpec.shared_context 'server metrics call' do
let(:elasticsearch_calls) { 8 }
let(:elasticsearch_duration) { 0.54 }
+
+ let(:mem_total_bytes) { 1000000000 }
let(:instrumentation) do
{
gitaly_duration_s: gitaly_duration,
redis_calls: redis_calls,
redis_duration_s: redis_duration,
elasticsearch_calls: elasticsearch_calls,
- elasticsearch_duration_s: elasticsearch_duration
+ elasticsearch_duration_s: elasticsearch_duration,
+ mem_total_bytes: mem_total_bytes
}
end
@@ -95,5 +100,6 @@ RSpec.shared_context 'server metrics call' do
allow(completion_seconds_metric).to receive(:observe)
allow(redis_seconds_metric).to receive(:observe)
allow(elasticsearch_seconds_metric).to receive(:observe)
+ allow(sidekiq_mem_total_bytes).to receive(:set)
end
end
diff --git a/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb b/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb
index a90fe9e1723..040b2da9f37 100644
--- a/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb
+++ b/spec/support/shared_contexts/markdown_snapshot_shared_examples.rb
@@ -9,6 +9,9 @@ RSpec.shared_context 'with API::Markdown Snapshot shared context' do |glfm_speci
# rubocop:enable Layout/LineLength
include ApiHelpers
+ let_it_be(:user) { create(:user) }
+ let_it_be(:api_url) { api('/markdown', user) }
+
markdown_examples, html_examples = %w[markdown.yml html.yml].map do |file_name|
yaml = File.read("#{glfm_specification_dir}/example_snapshots/#{file_name}")
YAML.safe_load(yaml, symbolize_names: true, aliases: true)
@@ -29,8 +32,6 @@ RSpec.shared_context 'with API::Markdown Snapshot shared context' do |glfm_speci
let(:normalizations) { normalizations_by_example_name.dig(name, :html, :static, :snapshot) }
it "verifies conversion of GLFM to HTML", :unlimited_max_formatted_output_length do
- api_url = api "/markdown"
-
# noinspection RubyResolve
normalized_html = normalize_html(html, normalizations)
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index eec6e92c5fe..893d3702407 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -56,6 +56,7 @@ RSpec.shared_context 'GroupPolicy context' do
admin_package
create_projects
create_cluster update_cluster admin_cluster add_cluster
+ destroy_upload
]
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 789b385c435..1d4731d9b39 100644
--- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -62,6 +62,7 @@ RSpec.shared_context 'ProjectPolicy context' do
admin_project admin_project_member admin_snippet admin_terraform_state
admin_wiki create_deploy_token destroy_deploy_token
push_to_delete_protected_branch read_deploy_token update_snippet
+ destroy_upload
]
end
diff --git a/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
index fbd82fbbe31..b18ce14eba6 100644
--- a/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_table_shared_context.rb
@@ -545,5 +545,62 @@ RSpec.shared_context 'ProjectPolicyTable context' do
:private | :non_member | nil | 0
:private | :anonymous | nil | 0
end
+
+ # Based on the permission_table_for_reporter_feature_access table, but for issue
+ # features where public and internal projects with issues enabled only allow
+ # access to reporters and above (excluding admins if admin mode is disabled)
+ #
+ # project_level, :feature_access_level, :membership, :admin_mode, :expected_count
+ def permission_table_for_reporter_issue_access
+ :public | :enabled | :admin | true | 1
+ :public | :enabled | :admin | false | 0
+ :public | :enabled | :reporter | nil | 1
+ :public | :enabled | :guest | nil | 0
+ :public | :enabled | :non_member | nil | 0
+ :public | :enabled | :anonymous | nil | 0
+
+ :public | :private | :admin | true | 1
+ :public | :private | :admin | false | 0
+ :public | :private | :reporter | nil | 1
+ :public | :private | :guest | nil | 0
+ :public | :private | :non_member | nil | 0
+ :public | :private | :anonymous | nil | 0
+
+ :public | :disabled | :reporter | nil | 0
+ :public | :disabled | :guest | nil | 0
+ :public | :disabled | :non_member | nil | 0
+ :public | :disabled | :anonymous | nil | 0
+
+ :internal | :enabled | :admin | true | 1
+ :internal | :enabled | :admin | false | 0
+ :internal | :enabled | :reporter | nil | 1
+ :internal | :enabled | :guest | nil | 0
+ :internal | :enabled | :non_member | nil | 0
+ :internal | :enabled | :anonymous | nil | 0
+
+ :internal | :private | :admin | true | 1
+ :internal | :private | :admin | false | 0
+ :internal | :private | :reporter | nil | 1
+ :internal | :private | :guest | nil | 0
+ :internal | :private | :non_member | nil | 0
+ :internal | :private | :anonymous | nil | 0
+
+ :internal | :disabled | :reporter | nil | 0
+ :internal | :disabled | :guest | nil | 0
+ :internal | :disabled | :non_member | nil | 0
+ :internal | :disabled | :anonymous | nil | 0
+
+ :private | :private | :admin | true | 1
+ :private | :private | :admin | false | 0
+ :private | :private | :reporter | nil | 1
+ :private | :private | :guest | nil | 0
+ :private | :private | :non_member | nil | 0
+ :private | :private | :anonymous | nil | 0
+
+ :private | :disabled | :reporter | nil | 0
+ :private | :disabled | :guest | nil | 0
+ :private | :disabled | :non_member | nil | 0
+ :private | :disabled | :anonymous | nil | 0
+ end
# rubocop:enable Metrics/AbcSize
end
diff --git a/spec/support/shared_contexts/upload_type_check_shared_context.rb b/spec/support/shared_contexts/upload_type_check_shared_context.rb
index 5fce31b4a15..57b8d7472df 100644
--- a/spec/support/shared_contexts/upload_type_check_shared_context.rb
+++ b/spec/support/shared_contexts/upload_type_check_shared_context.rb
@@ -3,7 +3,7 @@
# Construct an `uploader` variable that is configured to `check_upload_type`
# with `mime_types` and `extensions`.
# @param uploader [CarrierWave::Uploader::Base] uploader with extension_whitelist method.
-RSpec.shared_context 'ignore extension whitelist check' do
+RSpec.shared_context 'ignore extension allowlist check' do
before do
allow(uploader).to receive(:extension_whitelist).and_return(nil)
end
@@ -16,3 +16,15 @@ RSpec.shared_context 'force content type detection to mime_type' do
allow(Gitlab::Utils::MimeType).to receive(:from_io).and_return(mime_type)
end
end
+
+def mock_upload(success = true)
+ allow(UploadService).to receive(:new).with(project, file).and_return(upload_service)
+
+ if success
+ allow(upload_service).to receive(:execute).and_return(uploader)
+ allow(uploader).to receive(:upload).and_return(upload)
+ allow(upload).to receive(:id).and_return(upload_id)
+ else
+ allow(upload_service).to receive(:execute).and_return(nil)
+ end
+end
diff --git a/spec/support/shared_examples/attention_request_cache_invalidation_examples.rb b/spec/support/shared_examples/attention_request_cache_invalidation_examples.rb
deleted file mode 100644
index 7fe696abc69..00000000000
--- a/spec/support/shared_examples/attention_request_cache_invalidation_examples.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'invalidates attention request cache' do
- it 'invalidates the merge requests requiring attention count' do
- cache_mock = double
-
- users.each do |user|
- expect(cache_mock).to receive(:delete).with(['users', user.id, 'attention_requested_open_merge_requests_count'])
- end
-
- allow(Rails).to receive(:cache).and_return(cache_mock)
-
- service.execute
- end
-end
diff --git a/spec/support/shared_examples/boards/destroy_service_shared_examples.rb b/spec/support/shared_examples/boards/destroy_service_shared_examples.rb
index 33bae3da44b..b1cb58a736f 100644
--- a/spec/support/shared_examples/boards/destroy_service_shared_examples.rb
+++ b/spec/support/shared_examples/boards/destroy_service_shared_examples.rb
@@ -20,10 +20,10 @@ RSpec.shared_examples 'board destroy service' do
end
context 'when there is only one board' do
- it 'does not remove board' do
+ it 'does remove board' do
expect do
- expect(service.execute(board)).to be_error
- end.not_to change(boards, :count)
+ service.execute(board)
+ end.to change(boards, :count).by(-1)
end
end
end
diff --git a/spec/support/shared_examples/components/pajamas_shared_examples.rb b/spec/support/shared_examples/components/pajamas_shared_examples.rb
index 5c0ad1a1bc9..bcf7df24fd9 100644
--- a/spec/support/shared_examples/components/pajamas_shared_examples.rb
+++ b/spec/support/shared_examples/components/pajamas_shared_examples.rb
@@ -2,12 +2,18 @@
RSpec.shared_examples 'it renders help text' do
it 'renders help text' do
- expect(rendered_component).to have_selector('[data-testid="pajamas-component-help-text"]', text: help_text)
+ expect(page).to have_css('[data-testid="pajamas-component-help-text"]', text: help_text)
end
end
RSpec.shared_examples 'it does not render help text' do
it 'does not render help text' do
- expect(rendered_component).not_to have_selector('[data-testid="pajamas-component-help-text"]')
+ expect(page).not_to have_css('[data-testid="pajamas-component-help-text"]')
+ end
+end
+
+RSpec.shared_examples 'it renders unchecked checkbox with value of `1`' do
+ it 'renders unchecked checkbox with value of `1`' do
+ expect(page).to have_unchecked_field(label, with: '1')
end
end
diff --git a/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb b/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb
new file mode 100644
index 00000000000..9421561aea4
--- /dev/null
+++ b/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'when the user cannot read cross project' do |action, params|
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(false)
+ end
+
+ it 'blocks access without a project_id' do
+ get action, params: params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'allows access with a project_id' do
+ get action, params: params.merge(project_id: create(:project, :public).id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb
new file mode 100644
index 00000000000..6b72988b3e6
--- /dev/null
+++ b/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'with external authorization service enabled' do |action, params|
+ include ExternalAuthorizationServiceHelpers
+
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:note) { create(:note_on_issue, project: project) }
+
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'renders a 403 when no project is given' do
+ get action, params: params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'renders a 200 when a project was set' do
+ get action, params: params.merge(project_id: project.id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
index 98fc52add51..2e691d1b36f 100644
--- a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
+++ b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
@@ -2,22 +2,26 @@
#
# Requires a context containing:
# - subject
-# - project
# - feature_flag_name
# - category
# - action
# - namespace
+# Optionaly, the context can contain:
+# - project
+# - property
# - user
+# - label
+# - **extra
-shared_examples 'Snowplow event tracking' do
- let(:label) { nil }
+shared_examples 'Snowplow event tracking' do |overrides: {}|
+ let(:extra) { {} }
it 'is not emitted if FF is disabled' do
stub_feature_flags(feature_flag_name => false)
subject
- expect_no_snowplow_event
+ expect_no_snowplow_event(category: category, action: action)
end
it 'is emitted' do
@@ -25,10 +29,11 @@ shared_examples 'Snowplow event tracking' do
category: category,
action: action,
namespace: namespace,
- user: user,
- project: project,
- label: label
- }.compact
+ user: try(:user),
+ project: try(:project),
+ label: try(:label),
+ property: try(:property)
+ }.merge(overrides).compact.merge(extra)
subject
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 6dca94ecf0a..0792ac14e47 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -205,41 +205,13 @@ RSpec.shared_examples 'handle uploads' do
allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
end
- context "enforce_auth_checks_on_uploads feature flag" do
- context "with flag enabled" do
- before do
- stub_feature_flags(enforce_auth_checks_on_uploads: true)
- end
+ it "responds with the appropriate status code" do
+ show_upload
- it "responds with appropriate status" do
- show_upload
-
- # We're switching here based on the class due to the feature
- # flag :enforce_auth_checks_on_uploads switching on project.
- # When it is enabled fully, we will apply the code it guards
- # to both Projects::UploadsController as well as
- # Groups::UploadsController.
- #
- # https://gitlab.com/gitlab-org/gitlab/-/issues/352291
- #
- if model.instance_of?(Group)
- expect(response).to have_gitlab_http_status(:ok)
- else
- expect(response).to have_gitlab_http_status(:redirect)
- end
- end
- end
-
- context "with flag disabled" do
- before do
- stub_feature_flags(enforce_auth_checks_on_uploads: false)
- end
-
- it "responds with status 200" do
- show_upload
-
- expect(response).to have_gitlab_http_status(:ok)
- end
+ if model.instance_of?(Group)
+ expect(response).to have_gitlab_http_status(:ok)
+ else
+ expect(response).to have_gitlab_http_status(:redirect)
end
end
end
@@ -308,41 +280,13 @@ RSpec.shared_examples 'handle uploads' do
allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
end
- context "enforce_auth_checks_on_uploads feature flag" do
- context "with flag enabled" do
- before do
- stub_feature_flags(enforce_auth_checks_on_uploads: true)
- end
-
- it "responds with status 404" do
- show_upload
-
- # We're switching here based on the class due to the feature
- # flag :enforce_auth_checks_on_uploads switching on
- # project. When it is enabled fully, we will apply the
- # code it guards to both Projects::UploadsController as
- # well as Groups::UploadsController.
- #
- # https://gitlab.com/gitlab-org/gitlab/-/issues/352291
- #
- if model.instance_of?(Group)
- expect(response).to have_gitlab_http_status(:ok)
- else
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context "with flag disabled" do
- before do
- stub_feature_flags(enforce_auth_checks_on_uploads: false)
- end
-
- it "responds with status 200" do
- show_upload
+ it "responds with the appropriate status code" do
+ show_upload
- expect(response).to have_gitlab_http_status(:ok)
- end
+ if model.instance_of?(Group)
+ expect(response).to have_gitlab_http_status(:ok)
+ else
+ expect(response).to have_gitlab_http_status(:not_found)
end
end
end
diff --git a/spec/support/shared_examples/features/access_tokens_shared_examples.rb b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
index c162ed36881..0fc45b154d8 100644
--- a/spec/support/shared_examples/features/access_tokens_shared_examples.rb
+++ b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
@@ -38,7 +38,7 @@ RSpec.shared_examples 'resource access tokens creation' do |resource_type|
expect(active_resource_access_tokens).to have_text('in')
expect(active_resource_access_tokens).to have_text('read_api')
expect(active_resource_access_tokens).to have_text('read_repository')
- expect(active_resource_access_tokens).to have_text('Maintainer')
+ expect(active_resource_access_tokens).to have_text('Guest')
expect(created_resource_access_token).not_to be_empty
end
end
diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb
index 0ea82f37db0..3fa7beea97e 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -13,9 +13,8 @@ RSpec.shared_examples 'edits content using the content editor' do
expect(page).to have_css('[data-testid="formatting-bubble-menu"]')
end
- it 'does not show a formatting bubble menu for code' do
- find(content_editor_testid).send_keys 'This is a `code`'
- find(content_editor_testid).send_keys [:shift, :left]
+ it 'does not show a formatting bubble menu for code blocks' do
+ find(content_editor_testid).send_keys '```js '
expect(page).not_to have_css('[data-testid="formatting-bubble-menu"]')
end
diff --git a/spec/support/shared_examples/features/inviting_members_shared_examples.rb b/spec/support/shared_examples/features/inviting_members_shared_examples.rb
index bca0e02fcdd..277ec6a7fa7 100644
--- a/spec/support/shared_examples/features/inviting_members_shared_examples.rb
+++ b/spec/support/shared_examples/features/inviting_members_shared_examples.rb
@@ -147,9 +147,9 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
invite_member(user2.name, role: role, refresh: false)
- expect(page).to have_selector(invite_modal_selector)
- expect(page).to have_content "#{user2.name}: Access level should be greater than or equal to Developer " \
- "inherited membership from group #{group.name}"
+ invite_modal = page.find(invite_modal_selector)
+ expect(invite_modal).to have_content "#{user2.name}: Access level should be greater than or equal to " \
+ "Developer inherited membership from group #{group.name}"
page.refresh
@@ -166,31 +166,85 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
group.add_maintainer(user3)
end
- it 'shows the user errors and then removes them from the form', :js do
- visit subentity_members_page_path
+ it 'shows the partial user error and success and then removes them from the form', :js do
+ user4 = create(:user)
+ user5 = create(:user)
+ user6 = create(:user)
+ user7 = create(:user)
+
+ group.add_maintainer(user6)
+ group.add_maintainer(user7)
- invite_member([user2.name, user3.name], role: role, refresh: false)
+ visit subentity_members_page_path
- expect(page).to have_selector(invite_modal_selector)
- expect(page).to have_selector(member_token_error_selector(user2.id))
- expect(page).to have_selector(member_token_error_selector(user3.id))
- expect(page).to have_text("The following 2 members couldn't be invited")
- expect(page).to have_text("#{user2.name}: Access level should be greater than or equal to")
- expect(page).to have_text("#{user3.name}: Access level should be greater than or equal to")
+ invite_member([user2.name, user3.name, user4.name, user6.name, user7.name], role: role, refresh: false)
+
+ # we have more than 2 errors, so one will be hidden
+ invite_modal = page.find(invite_modal_selector)
+ expect(invite_modal).to have_text("The following 4 members couldn't be invited")
+ expect(invite_modal).to have_selector(limited_invite_error_selector, count: 2, visible: :visible)
+ expect(invite_modal).to have_selector(expanded_invite_error_selector, count: 2, visible: :hidden)
+ # unpredictability of return order means we can't rely on message showing in any order here
+ # so we will not expect on the message
+ expect_to_have_invalid_invite_indicator(invite_modal, user2, message: false)
+ expect_to_have_invalid_invite_indicator(invite_modal, user3, message: false)
+ expect_to_have_invalid_invite_indicator(invite_modal, user6, message: false)
+ expect_to_have_invalid_invite_indicator(invite_modal, user7, message: false)
+ expect_to_have_successful_invite_indicator(invite_modal, user4)
+ expect(invite_modal).to have_button('Show more (2)')
+
+ # now we want to test the show more errors count logic
+ remove_token(user7.id)
+
+ # count decreases from 4 to 3 and 2 to 1
+ expect(invite_modal).to have_text("The following 3 members couldn't be invited")
+ expect(invite_modal).to have_button('Show more (1)')
+
+ # we want to show this error now for user6
+ invite_modal.find(more_invite_errors_button_selector).click
+
+ # now we should see the error for all users and our collapse button text
+ expect(invite_modal).to have_selector(limited_invite_error_selector, count: 2, visible: :visible)
+ expect(invite_modal).to have_selector(expanded_invite_error_selector, count: 1, visible: :visible)
+ expect_to_have_invalid_invite_indicator(invite_modal, user2, message: true)
+ expect_to_have_invalid_invite_indicator(invite_modal, user3, message: true)
+ expect_to_have_invalid_invite_indicator(invite_modal, user6, message: true)
+ expect(invite_modal).to have_button('Show less')
+
+ # adds new token, but doesn't submit
+ select_members(user5.name)
+
+ expect_to_have_normal_invite_indicator(invite_modal, user5)
remove_token(user2.id)
- expect(page).not_to have_selector(member_token_error_selector(user2.id))
- expect(page).to have_selector(member_token_error_selector(user3.id))
- expect(page).to have_text("The following member couldn't be invited")
- expect(page).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
+ expect(invite_modal).to have_text("The following 2 members couldn't be invited")
+ expect(invite_modal).not_to have_selector(more_invite_errors_button_selector)
+ expect_to_have_invite_removed(invite_modal, user2)
+ expect_to_have_invalid_invite_indicator(invite_modal, user3)
+ expect_to_have_invalid_invite_indicator(invite_modal, user6)
+ expect_to_have_successful_invite_indicator(invite_modal, user4)
+ expect_to_have_normal_invite_indicator(invite_modal, user5)
+
+ remove_token(user6.id)
+
+ expect(invite_modal).to have_text("The following member couldn't be invited")
+ expect_to_have_invite_removed(invite_modal, user6)
+ expect_to_have_invalid_invite_indicator(invite_modal, user3)
+ expect_to_have_successful_invite_indicator(invite_modal, user4)
+ expect_to_have_normal_invite_indicator(invite_modal, user5)
remove_token(user3.id)
- expect(page).not_to have_selector(member_token_error_selector(user3.id))
- expect(page).not_to have_text("The following member couldn't be invited")
- expect(page).not_to have_text("Review the invite errors and try again")
- expect(page).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
+ expect(invite_modal).not_to have_text("The following member couldn't be invited")
+ expect(invite_modal).not_to have_text("Review the invite errors and try again")
+ expect_to_have_invite_removed(invite_modal, user3)
+ expect_to_have_successful_invite_indicator(invite_modal, user4)
+ expect_to_have_normal_invite_indicator(invite_modal, user5)
+
+ submit_invites
+
+ expect(page).not_to have_selector(invite_modal_selector)
page.refresh
@@ -203,6 +257,10 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
expect(page).to have_content('Maintainer')
expect(page).not_to have_button('Maintainer')
end
+
+ page.within find_invited_member_row(user4.name) do
+ expect(page).to have_button(role)
+ end
end
it 'only shows the error for an invalid formatted email and does not display other member errors', :js do
@@ -210,12 +268,12 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
invite_member([user2.name, user3.name, 'bad@email'], role: role, refresh: false)
- expect(page).to have_selector(invite_modal_selector)
- expect(page).to have_text('email contains an invalid email address')
- expect(page).not_to have_text("The following 2 members couldn't be invited")
- expect(page).not_to have_text("Review the invite errors and try again")
- expect(page).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
- expect(page).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
+ invite_modal = page.find(invite_modal_selector)
+ expect(invite_modal).to have_text('email contains an invalid email address')
+ expect(invite_modal).not_to have_text("The following 2 members couldn't be invited")
+ expect(invite_modal).not_to have_text("Review the invite errors and try again")
+ expect(invite_modal).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
+ expect(invite_modal).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
end
end
end
diff --git a/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb b/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
index bbde448a1a1..ef2683d6424 100644
--- a/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
+++ b/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
@@ -32,7 +32,7 @@ RSpec.shared_examples 'multiple assignees widget merge request' do |action, save
end
page.within '.dropdown-menu-user' do
- click_link user.name
+ click_button user.name
end
page.within '.issuable-sidebar' do
diff --git a/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
index 345dfbce423..95c0a76d726 100644
--- a/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
@@ -16,7 +16,9 @@ RSpec.shared_examples 'date sidebar widget' do
page.within('[data-testid="sidebar-due-date"]') do
today = Date.today.day
- click_button 'Edit'
+ button = find_button('Edit')
+ scroll_to(button)
+ button.click
click_button today.to_s
diff --git a/spec/support/shared_examples/features/trial_email_validation_shared_example.rb b/spec/support/shared_examples/features/trial_email_validation_shared_example.rb
new file mode 100644
index 00000000000..8304a91af86
--- /dev/null
+++ b/spec/support/shared_examples/features/trial_email_validation_shared_example.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'user email validation' do
+ let(:email_hint_message) { 'We recommend a work email address.' }
+ let(:email_error_message) { 'Please provide a valid email address.' }
+
+ let(:email_warning_message) do
+ 'This email address does not look right, are you sure you typed it correctly?'
+ end
+
+ context 'with trial_email_validation flag enabled' do
+ it 'shows an error message until a correct email is entered' do
+ visit path
+ expect(page).to have_content(email_hint_message)
+ expect(page).not_to have_content(email_error_message)
+ expect(page).not_to have_content(email_warning_message)
+
+ fill_in 'new_user_email', with: 'foo@'
+ fill_in 'new_user_first_name', with: ''
+
+ expect(page).not_to have_content(email_hint_message)
+ expect(page).to have_content(email_error_message)
+ expect(page).not_to have_content(email_warning_message)
+
+ fill_in 'new_user_email', with: 'foo@bar'
+ fill_in 'new_user_first_name', with: ''
+
+ expect(page).not_to have_content(email_hint_message)
+ expect(page).not_to have_content(email_error_message)
+ expect(page).to have_content(email_warning_message)
+
+ fill_in 'new_user_email', with: 'foo@gitlab.com'
+ fill_in 'new_user_first_name', with: ''
+
+ expect(page).not_to have_content(email_hint_message)
+ expect(page).not_to have_content(email_error_message)
+ expect(page).not_to have_content(email_warning_message)
+ end
+ end
+
+ context 'when trial_email_validation flag disabled' do
+ before do
+ stub_feature_flags trial_email_validation: false
+ end
+
+ it 'does not show an error message' do
+ visit path
+ expect(page).to have_content(email_hint_message)
+ expect(page).not_to have_content(email_error_message)
+ expect(page).not_to have_content(email_warning_message)
+
+ fill_in 'new_user_email', with: 'foo@'
+
+ expect(page).to have_content(email_hint_message)
+ expect(page).not_to have_content(email_error_message)
+ expect(page).not_to have_content(email_warning_message)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/user_views_tag_shared_examples.rb b/spec/support/shared_examples/features/user_views_tag_shared_examples.rb
new file mode 100644
index 00000000000..989de1dbfbb
--- /dev/null
+++ b/spec/support/shared_examples/features/user_views_tag_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'user views tag' do
+ context 'when user views with the tag' do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+ let(:tag_name) { "stable" }
+ let!(:release) { create(:release, project: project, tag: tag_name, name: "ReleaseName") }
+
+ before do
+ project.add_developer(user)
+ project.repository.add_tag(user, tag_name, project.default_branch_or_main)
+
+ sign_in(user)
+ end
+
+ shared_examples 'shows tag' do
+ it do
+ visit tag_page
+
+ expect(page).to have_content tag_name
+ expect(page).to have_link("ReleaseName", href: project_release_path(project, release))
+ end
+ end
+
+ it_behaves_like 'shows tag'
+
+ context 'when tag name contains a slash' do
+ let(:tag_name) { "stable/v0.1" }
+
+ it_behaves_like 'shows tag'
+ 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 c63faace6b2..9d81c0e9a3e 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'variable list' do
+RSpec.shared_examples 'variable list' do |is_admin|
it 'shows a list of variables' do
page.within('[data-testid="ci-variable-table"]') do
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key)
@@ -166,7 +166,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
expect(find('.flash-container')).to be_present
- expect(find('[data-testid="alert-danger"]').text).to have_content('Variables key (key) has already been taken')
+ expect(find('[data-testid="alert-danger"]').text).to have_content('(key) has already been taken')
end
it 'prevents a variable to be added if no values are provided when a variable is set to masked' do
@@ -257,7 +257,11 @@ RSpec.shared_examples 'variable list' do
end
it 'shows a message regarding the changed default' do
- expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default'
+ if is_admin
+ expect(page).to have_content 'Environment variables on this GitLab instance are configured to be protected by default'
+ else
+ expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default'
+ end
end
end
diff --git a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb
index 0ef1ccdfe57..8d1502bed84 100644
--- a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb
@@ -12,8 +12,8 @@ RSpec.shared_examples 'wiki file attachments' do
end
context 'before uploading' do
- it 'shows "Attach a file" button' do
- expect(page).to have_button('Attach a file')
+ it 'shows "Attach a file or image" button' do
+ expect(page).to have_selector('[data-testid="button-attach-file"]')
expect(page).not_to have_selector('.uploading-progress-container', visible: true)
end
end
@@ -26,7 +26,7 @@ RSpec.shared_examples 'wiki file attachments' do
click_button 'Cancel'
end
- expect(page).to have_button('Attach a file')
+ expect(page).to have_selector('[data-testid="button-attach-file"]')
expect(page).not_to have_button('Cancel')
expect(page).not_to have_selector('.uploading-progress-container', visible: true)
end
@@ -41,11 +41,11 @@ RSpec.shared_examples 'wiki file attachments' do
end
context 'uploading is complete' do
- it 'shows "Attach a file" button on uploading complete' do
+ it 'shows "Attach a file or image" button on uploading complete' do
attach_with_dropzone
wait_for_requests
- expect(page).to have_button('Attach a file')
+ expect(page).to have_selector('[data-testid="button-attach-file"]')
expect(page).not_to have_selector('.uploading-progress-container', visible: true)
end
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 79c7c1891ac..87067336a36 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
@@ -140,7 +140,7 @@ RSpec.shared_examples 'User updates wiki page' do
context 'when using the content editor' do
context 'with feature flag on' do
before do
- click_button 'Edit rich text'
+ find('[data-testid="toggle-editing-mode-button"] label', text: 'Rich text').click
end
it_behaves_like 'edits content using the content editor'
diff --git a/spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb
new file mode 100644
index 00000000000..f28348fb945
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'issuable supports timelog creation mutation' do
+ context 'when the user is anonymous' do
+ before do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when the user is a guest member of the namespace' do
+ let(:current_user) { create(:user) }
+
+ before do
+ users_container.add_guest(current_user)
+
+ post_graphql_mutation(mutation, current_user: current_user)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when user has permissions to create a timelog' do
+ let(:current_user) { author }
+
+ before do
+ users_container.add_reporter(current_user)
+ end
+
+ context 'with valid data' do
+ it 'creates the timelog' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change(Timelog, :count).by(1)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to be_empty
+ expect(mutation_response['timelog']).to include(
+ 'timeSpent' => 3600,
+ 'spentAt' => '2022-07-08T00:00:00Z',
+ 'summary' => 'Test summary'
+ )
+ end
+ end
+
+ context 'with invalid time_spent' do
+ let(:time_spent) { '3h e' }
+
+ it 'returns an error' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change(Timelog, :count).by(0)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to match_array(['Time spent can\'t be blank'])
+ expect(mutation_response['timelog']).to be_nil
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'issuable does not support timelog creation mutation' do
+ context 'when the user is anonymous' do
+ before do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when the user is a guest member of the namespace' do
+ let(:current_user) { create(:user) }
+
+ before do
+ users_container.add_guest(current_user)
+
+ post_graphql_mutation(mutation, current_user: current_user)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors' do
+ let(:match_errors) { contain_exactly(include('is not a valid ID for')) }
+ end
+ end
+
+ context 'when user has permissions to create a timelog' do
+ let(:current_user) { author }
+
+ before do
+ users_container.add_reporter(current_user)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors' do
+ let(:match_errors) { contain_exactly(include('is not a valid ID for')) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb
deleted file mode 100644
index 3c32b7e0310..00000000000
--- a/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'update work item weight widget' do
- it 'updates the weight widget' do
- expect do
- post_graphql_mutation(mutation, current_user: current_user)
- work_item.reload
- end.to change(work_item, :weight).from(nil).to(new_weight)
-
- expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['workItem']['widgets']).to include(
- {
- 'weight' => new_weight,
- 'type' => 'WEIGHT'
- }
- )
- end
-
- context 'when the updated work item is not valid' do
- it 'returns validation errors without the work item' do
- errors = ActiveModel::Errors.new(work_item).tap { |e| e.add(:weight, 'error message') }
-
- allow_next_found_instance_of(::WorkItem) do |instance|
- allow(instance).to receive(:valid?).and_return(false)
- allow(instance).to receive(:errors).and_return(errors)
- end
-
- post_graphql_mutation(mutation, current_user: current_user)
-
- expect(mutation_response['workItem']).to be_nil
- expect(mutation_response['errors']).to match_array(['Weight error message'])
- end
- end
-end
diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
index 2c6118779e6..0aa3bf8944f 100644
--- a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
@@ -94,5 +94,6 @@ RSpec.shared_examples 'a Note mutation with confidential notes' do
expect(mutation_response).to have_key('note')
expect(mutation_response['note']['confidential']).to eq(true)
+ expect(mutation_response['note']['internal']).to eq(true)
end
end
diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
index 7fd54408b11..2d7da9f9f00 100644
--- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
@@ -69,4 +69,21 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
'This feature is in Alpha. It can be changed or removed at any time. Introduced in 1.10.'
)
end
+
+ it 'supports :alpha' do
+ deprecable = subject(alpha: { milestone: '1.10' })
+
+ expect(deprecable.deprecation_reason).to eq(
+ 'This feature is in Alpha. It can be changed or removed at any time. Introduced in 1.10.'
+ )
+ end
+
+ it 'does not allow :alpha and :deprecated together' do
+ expect do
+ subject(alpha: { milestone: '1.10' }, deprecated: { milestone: '1.10', reason: 'my reason' } )
+ end.to raise_error(
+ ArgumentError,
+ eq("`alpha` and `deprecated` arguments cannot be passed at the same time")
+ )
+ end
end
diff --git a/spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb b/spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb
index c2c27fb65ca..61c8a3f47df 100644
--- a/spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb
+++ b/spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'wiki endpoint helpers' do
let(:resource_path) { page.wiki.container.class.to_s.pluralize.downcase }
- let(:url) { "/api/v4/#{resource_path}/#{page.wiki.container.id}/wikis/#{page.slug}?version=#{page.version.id}"}
+ let(:url) { "/api/v4/#{resource_path}/#{page.wiki.container.id}/wikis/#{page.slug}?version=#{page.version.id}" }
it 'returns the full endpoint url' do
expect(helper.wiki_page_render_api_endpoint(page)).to end_with(url)
diff --git a/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb
index 95772b1774a..5eae8777a20 100644
--- a/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb
@@ -86,7 +86,10 @@ RSpec.shared_examples 'with inheritable CI config' do
expect do
# we ignore exceptions as `#overwrite_entry`
# can raise exception on duplicates
- entry.send(:inherit!, deps) rescue described_class::InheritError
+
+ entry.send(:inherit!, deps)
+ rescue described_class::InheritError
+ nil
end.not_to change { entry[entry_key] }
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
index 9d280d9404a..481e11bcf0e 100644
--- a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
@@ -33,7 +33,7 @@ RSpec.shared_examples 'does not track when feature flag is disabled' do |feature
end
end
-RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events' do
+RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events for given event params' do
before do
stub_application_setting(usage_ping_enabled: true)
end
@@ -44,22 +44,21 @@ RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events
specify do
aggregate_failures do
- expect(track_action(author: user1, project: project)).to be_truthy
- expect(track_action(author: user1, project: project)).to be_truthy
- expect(track_action(author: user2, project: project)).to be_truthy
+ expect(track_action({ author: user1 }.merge(track_params))).to be_truthy
+ expect(track_action({ author: user1 }.merge(track_params))).to be_truthy
+ expect(track_action({ author: user2 }.merge(track_params))).to be_truthy
expect(count_unique).to eq(2)
end
end
it 'does not track edit actions if author is not present' do
- expect(track_action(author: nil, project: project)).to be_nil
+ expect(track_action({ author: nil }.merge(track_params))).to be_nil
end
it 'emits snowplow event' do
- track_action(author: user1, project: project)
+ track_action({ author: user1 }.merge(track_params))
- expect_snowplow_event(category: 'issues_edit', action: action, user: user1,
- namespace: project.namespace, project: project)
+ expect_snowplow_event(**{ category: category, action: event_action, user: user1 }.merge(event_params))
end
context 'with route_hll_to_snowplow_phase2 disabled' do
@@ -68,9 +67,33 @@ RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events
end
it 'does not emit snowplow event' do
- track_action(author: user1, project: project)
+ track_action({ author: user1 }.merge(track_params))
expect_no_snowplow_event
end
end
end
+
+RSpec.shared_examples 'daily tracked issuable snowplow and service ping events with project' do
+ it_behaves_like 'a daily tracked issuable snowplow and service ping events for given event params' do
+ let(:track_params) { { project: project } }
+ let(:event_params) { track_params.merge(label: event_label, property: event_property, namespace: project.namespace) }
+ end
+end
+
+RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events with namespace' do
+ it_behaves_like 'a daily tracked issuable snowplow and service ping events for given event params' do
+ let(:track_params) { { namespace: namespace } }
+ let(:event_params) { track_params.merge(label: event_label, property: event_property) }
+ end
+end
+
+RSpec.shared_examples 'does not track with namespace when feature flag is disabled' do |feature_flag|
+ context "when feature flag #{feature_flag} is disabled" do
+ it 'does not track action' do
+ stub_feature_flags(feature_flag => false)
+
+ expect(track_action(author: user1, namespace: namespace)).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
index d189e91effd..fb08784f34f 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -3,7 +3,6 @@
RSpec.shared_examples "chat integration" do |integration_name|
describe "Associations" do
it { is_expected.to belong_to :project }
- it { is_expected.to have_one :service_hook }
end
describe "Validations" do
@@ -13,6 +12,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
end
it { is_expected.to validate_presence_of(:webhook) }
+
it_behaves_like "issue tracker integration URL attribute", :webhook
end
diff --git a/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb b/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb
new file mode 100644
index 00000000000..0c71ebe7a4d
--- /dev/null
+++ b/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'has ID tokens' do |ci_type|
+ subject(:ci) { FactoryBot.build(ci_type) }
+
+ describe 'delegations' do
+ it { is_expected.to delegate_method(:id_tokens).to(:metadata).allow_nil }
+ end
+
+ describe '#id_tokens?' do
+ subject { ci.id_tokens? }
+
+ context 'without metadata' do
+ let(:ci) { FactoryBot.build(ci_type) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with metadata' do
+ let(:ci) { FactoryBot.build(ci_type, metadata: FactoryBot.build(:ci_build_metadata, id_tokens: id_tokens)) }
+
+ context 'when ID tokens exist' do
+ let(:id_tokens) { { TEST_JOB_JWT: { id_token: { aud: 'developers ' } } } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when ID tokens do not exist' do
+ let(:id_tokens) { {} }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+ end
+
+ describe '#id_tokens=' do
+ it 'assigns the ID tokens to the CI job' do
+ id_tokens = [{ 'JOB_ID_TOKEN' => { 'id_token' => { 'aud' => 'https://gitlab.test ' } } }]
+ ci.id_tokens = id_tokens
+
+ expect(ci.id_tokens).to match_array(id_tokens)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
index f92ed3d7396..f4d5ab3d5c6 100644
--- a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
@@ -2,6 +2,10 @@
require 'spec_helper'
RSpec.shared_examples_for CounterAttribute do |counter_attributes|
+ before do
+ Gitlab::ApplicationContext.push(feature_category: 'test', caller_id: 'caller')
+ end
+
it 'defines a Redis counter_key' do
expect(model.counter_key(:counter_name))
.to eq("project:{#{model.project_id}}:counters:CounterAttributeModel:#{model.id}:counter_name")
@@ -22,7 +26,21 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
where(:increment) { [10, -3] }
with_them do
- it 'increments the counter in Redis' do
+ it 'increments the counter in Redis and logs it' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Increment counter attribute',
+ attribute: attribute,
+ project_id: model.project_id,
+ increment: increment,
+ new_counter_value: 0 + increment,
+ current_db_value: model.read_attribute(attribute),
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
+ )
+
subject
Gitlab::Redis::SharedState.with do |redis|
@@ -86,7 +104,21 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
model.delayed_increment_counter(incremented_attribute, -3)
end
- it 'updates the record' do
+ it 'updates the record and logs it' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Flush counter attribute to database',
+ attribute: incremented_attribute,
+ project_id: model.project_id,
+ increment: 7,
+ previous_db_value: 0,
+ new_db_value: 7,
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
+ )
+
expect { subject }.to change { model.reset.read_attribute(incremented_attribute) }.by(7)
end
@@ -153,4 +185,32 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
end
end
end
+
+ describe '#clear_counter!' do
+ let(:attribute) { counter_attributes.first }
+
+ before do
+ model.increment_counter(attribute, 10)
+ end
+
+ it 'deletes the counter key for the given attribute and logs it' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Clear counter attribute',
+ attribute: attribute,
+ project_id: model.project_id,
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
+ )
+
+ model.clear_counter!(attribute)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ key_exists = redis.exists(model.counter_key(attribute))
+ expect(key_exists).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
index d80be5be3b3..7512a9f2855 100644
--- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
@@ -13,7 +13,6 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
describe "Associations" do
it { is_expected.to belong_to :project }
- it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
@@ -23,6 +22,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
it { is_expected.to validate_presence_of(:webhook) }
+
it_behaves_like 'issue tracker integration URL attribute', :webhook
end
diff --git a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
index ae72cb6ec5d..2f693edeb53 100644
--- a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
+++ b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
@@ -3,6 +3,10 @@
RSpec.shared_examples Integrations::HasWebHook do
include AfterNextHelpers
+ describe 'associations' do
+ it { is_expected.to have_one(:service_hook).inverse_of(:integration).with_foreign_key(:service_id) }
+ end
+
describe 'callbacks' do
it 'calls #update_web_hook! when enabled' do
expect(integration).to receive(:update_web_hook!)
diff --git a/spec/support/shared_examples/models/issuable_link_shared_examples.rb b/spec/support/shared_examples/models/issuable_link_shared_examples.rb
index 9892e66b582..42c7be5ddc3 100644
--- a/spec/support/shared_examples/models/issuable_link_shared_examples.rb
+++ b/spec/support/shared_examples/models/issuable_link_shared_examples.rb
@@ -16,6 +16,7 @@ RSpec.shared_examples 'issuable link' do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:target) }
+
it do
is_expected.to validate_uniqueness_of(:source)
.scoped_to(:target_id)
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index aa40a2c7135..287b046cbec 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -63,16 +63,23 @@ RSpec.shared_examples '#valid_level_roles' do |entity_name|
let(:entity) { create(entity_name) } # rubocop:disable Rails/SaveBang
let(:entity_member) { create("#{entity_name}_member", :developer, source: entity, user: member_user) }
let(:presenter) { described_class.new(entity_member, current_user: member_user) }
- let(:expected_roles) { { 'Developer' => 30, 'Maintainer' => 40, 'Reporter' => 20 } }
- it 'returns all roles when no parent member is present' do
- expect(presenter.valid_level_roles).to eq(entity_member.class.access_level_roles)
+ context 'when no parent member is present' do
+ let(:all_permissible_roles) { entity_member.class.permissible_access_level_roles(member_user, entity) }
+
+ it 'returns all permissible roles' do
+ expect(presenter.valid_level_roles).to eq(all_permissible_roles)
+ end
end
- it 'returns higher roles when a parent member is present' do
- group.add_reporter(member_user)
+ context 'when parent member is present' do
+ before do
+ group.add_reporter(member_user)
+ end
- expect(presenter.valid_level_roles).to eq(expected_roles)
+ it 'returns higher roles when a parent member is present' do
+ expect(presenter.valid_level_roles).to eq(expected_roles)
+ end
end
end
diff --git a/spec/support/shared_examples/models/project_shared_examples.rb b/spec/support/shared_examples/models/project_shared_examples.rb
index 475ac1da04b..0b880f00a22 100644
--- a/spec/support/shared_examples/models/project_shared_examples.rb
+++ b/spec/support/shared_examples/models/project_shared_examples.rb
@@ -25,3 +25,38 @@ RSpec.shared_examples 'returns true if project is inactive' do
end
end
end
+
+RSpec.shared_examples 'checks parent group feature flag' do
+ let(:group) { subject_project.group }
+ let(:root_group) { group.parent }
+
+ subject { subject_project.public_send(feature_flag_method) }
+
+ context 'when feature flag is disabled globally' do
+ before do
+ stub_feature_flags(feature_flag => false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when feature flag is enabled globally' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when feature flag is enabled for the root group' do
+ before do
+ stub_feature_flags(feature_flag => root_group)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when feature flag is enabled for the group' do
+ before do
+ stub_feature_flags(feature_flag => group)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+end
diff --git a/spec/support/shared_examples/models/taskable_shared_examples.rb b/spec/support/shared_examples/models/taskable_shared_examples.rb
index 34b1d735bcd..3ae240c8da8 100644
--- a/spec/support/shared_examples/models/taskable_shared_examples.rb
+++ b/spec/support/shared_examples/models/taskable_shared_examples.rb
@@ -18,9 +18,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('2 of')
- expect(subject.task_status).to match('5 tasks completed')
+ expect(subject.task_status).to match('5 checklist items completed')
expect(subject.task_status_short).to match('2/')
- expect(subject.task_status_short).to match('5 tasks')
+ expect(subject.task_status_short).to match('5 checklist items')
end
describe '#tasks?' do
@@ -53,9 +53,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('3 of')
- expect(subject.task_status).to match('9 tasks completed')
+ expect(subject.task_status).to match('9 checklist items completed')
expect(subject.task_status_short).to match('3/')
- expect(subject.task_status_short).to match('9 tasks')
+ expect(subject.task_status_short).to match('9 checklist items')
end
end
@@ -68,9 +68,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('0 of')
- expect(subject.task_status).to match('1 task completed')
+ expect(subject.task_status).to match('1 checklist item completed')
expect(subject.task_status_short).to match('0/')
- expect(subject.task_status_short).to match('1 task')
+ expect(subject.task_status_short).to match('1 checklist item')
end
end
@@ -87,9 +87,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('0 of')
- expect(subject.task_status).to match('0 tasks completed')
+ expect(subject.task_status).to match('0 checklist items completed')
expect(subject.task_status_short).to match('0/')
- expect(subject.task_status_short).to match('0 task')
+ expect(subject.task_status_short).to match('0 checklist items')
end
end
@@ -102,9 +102,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('1 of')
- expect(subject.task_status).to match('1 task completed')
+ expect(subject.task_status).to match('1 checklist item completed')
expect(subject.task_status_short).to match('1/')
- expect(subject.task_status_short).to match('1 task')
+ expect(subject.task_status_short).to match('1 checklist item')
end
end
@@ -123,9 +123,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('2 of')
- expect(subject.task_status).to match('4 tasks completed')
+ expect(subject.task_status).to match('4 checklist items completed')
expect(subject.task_status_short).to match('2/')
- expect(subject.task_status_short).to match('4 tasks')
+ expect(subject.task_status_short).to match('4 checklist items')
end
end
end
diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
index 45da1d382c1..807295f8442 100644
--- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
@@ -273,14 +273,6 @@ RSpec.shared_examples 'namespace traversal scopes' do
include_examples '.self_and_descendants'
end
-
- context 'with linear_scopes_superset feature flag disabled' do
- before do
- stub_feature_flags(linear_scopes_superset: false)
- end
-
- include_examples '.self_and_descendants'
- end
end
shared_examples '.self_and_descendant_ids' do
@@ -324,14 +316,6 @@ RSpec.shared_examples 'namespace traversal scopes' do
include_examples '.self_and_descendant_ids'
end
-
- context 'with linear_scopes_superset feature flag disabled' do
- before do
- stub_feature_flags(linear_scopes_superset: false)
- end
-
- include_examples '.self_and_descendant_ids'
- end
end
shared_examples '.self_and_hierarchy' do
diff --git a/spec/support/shared_examples/policies/group_project_namespace_policy_shared_examples.rb b/spec/support/shared_examples/policies/group_project_namespace_policy_shared_examples.rb
new file mode 100644
index 00000000000..9a1f0e685be
--- /dev/null
+++ b/spec/support/shared_examples/policies/group_project_namespace_policy_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'checks timelog categories permissions' do
+ context 'with no user' do
+ let_it_be(:current_user) { nil }
+
+ it { is_expected.to be_disallowed(:read_timelog_category) }
+ end
+
+ context 'with a regular user' do
+ let_it_be(:current_user) { create(:user) }
+
+ it { is_expected.to be_disallowed(:read_timelog_category) }
+ end
+
+ context 'with a reporter user' do
+ let_it_be(:current_user) { create(:user) }
+
+ before do
+ users_container.add_reporter(current_user)
+ end
+
+ context 'when timelog_categories is enabled' do
+ it { is_expected.to be_allowed(:read_timelog_category) }
+ end
+
+ context 'when timelog_categories is disabled' do
+ before do
+ stub_feature_flags(timelog_categories: false)
+ end
+
+ it { is_expected.to be_disallowed(:read_timelog_category) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
index a12cb24a513..32562aef8d2 100644
--- a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
@@ -128,10 +128,10 @@ RSpec.shared_examples 'discussions API' do |parent_type, noteable_type, id_name,
stub_feature_flags(notes_create_service_tracking: false)
end
- it 'does not track any events', :snowplow do
+ it 'does not track Notes::CreateService events', :snowplow do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), params: { body: 'hi!' }
- expect_no_snowplow_event
+ expect_no_snowplow_event(category: 'Notes::CreateService', action: 'execute')
end
end
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index a59235486ec..8479493911b 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -376,13 +376,28 @@ RSpec.shared_examples 'noteable API with confidential notes' do |parent_type, no
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params
end
- it "creates a confidential note if confidential is set to true" do
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(confidential: true)
+ context 'with internal param' do
+ it "creates a confidential note if internal is set to true" do
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(internal: true)
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['body']).to eq('hi!')
- expect(json_response['confidential']).to be_truthy
- expect(json_response['author']['username']).to eq(user.username)
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_truthy
+ expect(json_response['internal']).to be_truthy
+ expect(json_response['author']['username']).to eq(user.username)
+ end
+ end
+
+ context 'with deprecated confidential param' do
+ it "creates a confidential note if confidential is set to true" do
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(confidential: true)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_truthy
+ expect(json_response['internal']).to be_truthy
+ expect(json_response['author']['username']).to eq(user.username)
+ end
end
end
end
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index 8d6d85732be..b651ffc8996 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -244,7 +244,7 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
let(:headers) do
case auth
when :oauth
- build_token_auth_header(token.token)
+ build_token_auth_header(token.plaintext_token)
when :personal_access_token
build_token_auth_header(personal_access_token.token)
when :job_token
@@ -404,7 +404,7 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project|
shared_examples 'handling all conditions' do
context 'with oauth token' do
- let(:headers) { build_token_auth_header(token.token) }
+ let(:headers) { build_token_auth_header(token.plaintext_token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
@@ -514,7 +514,7 @@ RSpec.shared_examples 'handling create dist tag requests' do |scope: :project|
shared_examples 'handling all conditions' do
context 'with oauth token' do
- let(:headers) { build_token_auth_header(token.token) }
+ let(:headers) { build_token_auth_header(token.plaintext_token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
@@ -622,7 +622,7 @@ RSpec.shared_examples 'handling delete dist tag requests' do |scope: :project|
shared_examples 'handling all conditions' do
context 'with oauth token' do
- let(:headers) { build_token_auth_header(token.token) }
+ let(:headers) { build_token_auth_header(token.plaintext_token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
index ca86cb082a7..6cae7d8e00f 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
@@ -23,7 +23,7 @@ RSpec.shared_examples 'creates an alert management alert or errors' do
end
context 'and fails to save' do
- let(:errors) { double(messages: { hosts: ['hosts array is over 255 chars'] })}
+ let(:errors) { double(messages: { hosts: ['hosts array is over 255 chars'] }, '[]': [] )}
before do
allow(service).to receive(:alert).and_call_original
@@ -35,9 +35,10 @@ RSpec.shared_examples 'creates an alert management alert or errors' do
it 'writes a warning to the log' do
expect(Gitlab::AppLogger).to receive(:warn).with(
- message: "Unable to create AlertManagement::Alert from #{source}",
+ message: "Unable to create AlertManagement::Alert",
project_id: project.id,
- alert_errors: { hosts: ['hosts array is over 255 chars'] }
+ alert_errors: { hosts: ['hosts array is over 255 chars'] },
+ alert_source: source
)
subject
@@ -45,6 +46,46 @@ RSpec.shared_examples 'creates an alert management alert or errors' do
end
end
+RSpec.shared_examples 'handles race condition in alert creation' do
+ let(:other_alert) { create(:alert_management_alert, project: project) }
+
+ context 'when another alert is saved at the same time' do
+ before do
+ allow_next_instance_of(::AlertManagement::Alert) do |alert|
+ allow(alert).to receive(:save) do
+ other_alert.update!(fingerprint: alert.fingerprint)
+
+ raise ActiveRecord::RecordNotUnique
+ end
+ end
+ end
+
+ it 'finds the other alert and increments the counter' do
+ subject
+
+ expect(other_alert.reload.events).to eq(2)
+ end
+ end
+
+ context 'when another alert is saved before the validation runes' do
+ before do
+ allow_next_instance_of(::AlertManagement::Alert) do |alert|
+ allow(alert).to receive(:save).and_wrap_original do |method, *args|
+ other_alert.update!(fingerprint: alert.fingerprint)
+
+ method.call(*args)
+ end
+ end
+ end
+
+ it 'finds the other alert and increments the counter' do
+ subject
+
+ expect(other_alert.reload.events).to eq(2)
+ end
+ end
+end
+
# This shared_example requires the following variables:
# - last_alert_attributes, last created alert
# - project, project that alert created
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
index f8e096297d3..eb9f76d8626 100644
--- 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
@@ -4,8 +4,6 @@
# - `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)
@@ -22,36 +20,6 @@ RSpec.shared_examples 'does not change the alert end time' do
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 }
@@ -69,7 +37,6 @@ RSpec.shared_examples 'processes recovery alert' do
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'
@@ -83,7 +50,6 @@ RSpec.shared_examples 'processes recovery alert' do
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'
@@ -97,7 +63,6 @@ RSpec.shared_examples 'processes recovery alert' do
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'
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
index 98834f01ce2..6becc3dc071 100644
--- 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
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# Expects usage of 'incident settings enabled' context.
+# Expects usage of 'incident management settings enabled' context.
#
# This shared_example includes the following option:
# - with_issue: includes a test for when the defined `alert` has an associated issue
@@ -8,7 +8,7 @@
# 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
+ include_examples 'processes incident issues', with_issue: with_issue
context 'with incident setting disabled' do
let(:create_issue) { false }
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
index 3add5485fca..1973577d742 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# Expects usage of 'incident settings enabled' context.
+# Expects usage of 'incident management settings enabled' context.
#
# This shared_example requires the following variables:
# - `alert`, alert for which related incidents should be closed
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
index 5f30b58176b..92e7dee7533 100644
--- 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
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-# Expects usage of 'incident settings enabled' context.
+# Expects usage of 'incident management 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
+ include_examples 'sends alert notification emails', count: count
context 'with email setting disabled' do
let(:send_email) { false }
diff --git a/spec/support/shared_examples/services/boards/lists_move_service_shared_examples.rb b/spec/support/shared_examples/services/boards/lists_move_service_shared_examples.rb
index bf84b912610..97d0bae3552 100644
--- a/spec/support/shared_examples/services/boards/lists_move_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/boards/lists_move_service_shared_examples.rb
@@ -1,95 +1,103 @@
# frozen_string_literal: true
RSpec.shared_examples 'lists move service' do
- let!(:planning) { create(:list, board: board, position: 0) }
- let!(:development) { create(:list, board: board, position: 1) }
- let!(:review) { create(:list, board: board, position: 2) }
- let!(:staging) { create(:list, board: board, position: 3) }
- let!(:closed) { create(:closed_list, board: board) }
+ shared_examples 'correct movement behavior' do
+ context 'when list type is set to label' do
+ it 'does not reorder lists when new position is nil' do
+ service = described_class.new(parent, user, position: nil)
- context 'when list type is set to label' do
- it 'keeps position of lists when new position is nil' do
- service = described_class.new(parent, user, position: nil)
+ service.execute(planning)
- service.execute(planning)
+ expect(ordered_lists).to eq([planning, development, review, staging])
+ end
- expect(current_list_positions).to eq [0, 1, 2, 3]
- end
-
- it 'keeps position of lists when new position is equal to old position' do
- service = described_class.new(parent, user, position: planning.position)
+ it 'does not reorder lists when new position is equal to old position' do
+ service = described_class.new(parent, user, position: planning.position)
- service.execute(planning)
+ service.execute(planning)
- expect(current_list_positions).to eq [0, 1, 2, 3]
- end
+ expect(ordered_lists).to eq([planning, development, review, staging])
+ end
- it 'keeps position of lists when new position is negative' do
- service = described_class.new(parent, user, position: -1)
+ it 'does not reorder lists when new position is negative' do
+ service = described_class.new(parent, user, position: -1)
- service.execute(planning)
+ service.execute(planning)
- expect(current_list_positions).to eq [0, 1, 2, 3]
- end
+ expect(ordered_lists).to eq([planning, development, review, staging])
+ end
- it 'keeps position of lists when new position is equal to number of labels lists' do
- service = described_class.new(parent, user, position: board.lists.label.size)
+ it 'does not reorder lists when new position is bigger then last position' do
+ service = described_class.new(parent, user, position: ordered_lists.last.position + 1)
- service.execute(planning)
+ service.execute(planning)
- expect(current_list_positions).to eq [0, 1, 2, 3]
- end
+ expect(ordered_lists).to eq([planning, development, review, staging])
+ end
- it 'keeps position of lists when new position is greater than number of labels lists' do
- service = described_class.new(parent, user, position: board.lists.label.size + 1)
+ it 'moves the list to the first position when new position is equal to first position' do
+ service = described_class.new(parent, user, position: 0)
- service.execute(planning)
+ service.execute(staging)
- expect(current_list_positions).to eq [0, 1, 2, 3]
- end
+ expect(ordered_lists).to eq([staging, planning, development, review])
+ end
- it 'increments position of intermediate lists when new position is equal to first position' do
- service = described_class.new(parent, user, position: 0)
+ it 'moves the list to the last position when new position is equal to last position' do
+ service = described_class.new(parent, user, position: board.lists.label.last.position)
- service.execute(staging)
+ service.execute(planning)
- expect(current_list_positions).to eq [1, 2, 3, 0]
- end
+ expect(ordered_lists).to eq([development, review, staging, planning])
+ end
- it 'decrements position of intermediate lists when new position is equal to last position' do
- service = described_class.new(parent, user, position: board.lists.label.last.position)
+ it 'moves the list to the correct position when new position is greater than old position (third list)' do
+ service = described_class.new(parent, user, position: review.position)
- service.execute(planning)
+ service.execute(planning)
- expect(current_list_positions).to eq [3, 0, 1, 2]
- end
+ expect(ordered_lists).to eq([development, review, planning, staging])
+ end
- it 'decrements position of intermediate lists when new position is greater than old position' do
- service = described_class.new(parent, user, position: 2)
+ it 'moves the list to the correct position when new position is lower than old position (second list)' do
+ service = described_class.new(parent, user, position: development.position)
- service.execute(planning)
+ service.execute(staging)
- expect(current_list_positions).to eq [2, 0, 1, 3]
+ expect(ordered_lists).to eq([planning, staging, development, review])
+ end
end
- it 'increments position of intermediate lists when new position is lower than old position' do
- service = described_class.new(parent, user, position: 1)
+ it 'keeps position of lists when list type is closed' do
+ service = described_class.new(parent, user, position: 2)
- service.execute(staging)
+ service.execute(closed)
- expect(current_list_positions).to eq [0, 2, 3, 1]
+ expect(ordered_lists).to eq([planning, development, review, staging])
end
end
- it 'keeps position of lists when list type is closed' do
- service = described_class.new(parent, user, position: 2)
+ context 'with complete position sequence' do
+ let!(:planning) { create(:list, board: board, position: 0) }
+ let!(:development) { create(:list, board: board, position: 1) }
+ let!(:review) { create(:list, board: board, position: 2) }
+ let!(:staging) { create(:list, board: board, position: 3) }
+ let!(:closed) { create(:closed_list, board: board) }
+
+ it_behaves_like 'correct movement behavior'
+ end
- service.execute(closed)
+ context 'with corrupted position sequence' do
+ let!(:planning) { create(:list, board: board, position: 0) }
+ let!(:staging) { create(:list, board: board, position: 6) }
+ let!(:development) { create(:list, board: board, position: 1) }
+ let!(:review) { create(:list, board: board, position: 4) }
+ let!(:closed) { create(:closed_list, board: board) }
- expect(current_list_positions).to eq [0, 1, 2, 3]
+ it_behaves_like 'correct movement behavior'
end
- def current_list_positions
- [planning, development, review, staging].map { |list| list.reload.position }
+ def ordered_lists
+ board.lists.where.not(position: nil)
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 a50a386afe1..142d4ae8531 100644
--- a/spec/support/shared_examples/services/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable_shared_examples.rb
@@ -45,7 +45,7 @@ RSpec.shared_examples 'updating a single task' do
end
it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 1** as completed')
+ note1 = find_note('marked the checklist item **Task 1** as completed')
expect(note1).not_to be_nil
@@ -61,7 +61,7 @@ RSpec.shared_examples 'updating a single task' do
end
it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 2** as incomplete')
+ note1 = find_note('marked the checklist item **Task 2** as incomplete')
expect(note1).not_to be_nil
@@ -92,7 +92,7 @@ RSpec.shared_examples 'updating a single task' do
end
it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 2** as incomplete')
+ note1 = find_note('marked the checklist item **Task 2** as incomplete')
expect(note1).not_to be_nil
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
index 6bc4f171d9c..704a4bbe0b8 100644
--- a/spec/support/shared_examples/services/packages_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -81,6 +81,26 @@ RSpec.shared_examples 'returns packages' do |container_type, user_type|
end
end
+RSpec.shared_examples 'returns package' do |container_type, user_type|
+ context "for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it 'returns success response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+
+ it 'returns a valid response schema' do
+ subject
+
+ expect(response).to match_response_schema(single_package_schema)
+ end
+ end
+end
+
RSpec.shared_examples 'returns packages with subgroups' do |container_type, user_type|
context "with subgroups for #{user_type}" do
before do
diff --git a/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb b/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb
new file mode 100644
index 00000000000..0687be6f429
--- /dev/null
+++ b/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+shared_examples 'issue_edit snowplow tracking' do
+ let(:category) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_CATEGORY }
+ let(:action) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_ACTION }
+ let(:label) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_LABEL }
+ let(:namespace) { project.namespace }
+ let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
+
+ it_behaves_like 'Snowplow event tracking'
+end
diff --git a/spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb b/spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb
new file mode 100644
index 00000000000..53c42ec0e00
--- /dev/null
+++ b/spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'issuable supports timelog creation service' do
+ shared_examples 'success_response' do
+ it 'sucessfully saves the timelog' do
+ is_expected.to be_success
+
+ timelog = subject.payload[:timelog]
+
+ expect(timelog).to be_persisted
+ expect(timelog.time_spent).to eq(time_spent)
+ expect(timelog.spent_at).to eq('Fri, 08 Jul 2022 00:00:00.000000000 UTC +00:00')
+ expect(timelog.summary).to eq(summary)
+ expect(timelog.issuable).to eq(issuable)
+ end
+ end
+
+ context 'when the user does not have permission' do
+ let(:user) { create(:user) }
+
+ it 'returns an error' do
+ is_expected.to be_error
+
+ expect(subject.message).to eq(
+ "#{issuable.base_class_name} doesn't exist or you don't have permission to add timelog to it.")
+ expect(subject.http_status).to eq(404)
+ end
+ end
+
+ context 'when the user has permissions' do
+ let(:user) { author }
+
+ before do
+ users_container.add_reporter(user)
+ end
+
+ context 'when the timelog save fails' do
+ before do
+ allow_next_instance_of(Timelog) do |timelog|
+ allow(timelog).to receive(:save).and_return(false)
+ end
+ end
+
+ it 'returns an error' do
+ is_expected.to be_error
+ expect(subject.message).to eq('Failed to save timelog')
+ end
+ end
+
+ context 'when the creation completes sucessfully' do
+ it_behaves_like 'success_response'
+ end
+ end
+end
+
+RSpec.shared_examples 'issuable does not support timelog creation service' do
+ shared_examples 'error_response' do
+ it 'returns an error' do
+ is_expected.to be_error
+
+ issuable_type = if issuable.nil?
+ 'Issuable'
+ else
+ issuable.base_class_name
+ end
+
+ expect(subject.message).to eq(
+ "#{issuable_type} doesn't exist or you don't have permission to add timelog to it."
+ )
+ expect(subject.http_status).to eq(404)
+ end
+ end
+
+ context 'when the user does not have permission' do
+ let(:user) { create(:user) }
+
+ it_behaves_like 'error_response'
+ end
+
+ context 'when the user has permissions' do
+ let(:user) { author }
+
+ before do
+ users_container.add_reporter(user)
+ end
+
+ it_behaves_like 'error_response'
+ end
+end
diff --git a/spec/support/shared_examples/services/work_items/create_task_shared_examples.rb b/spec/support/shared_examples/services/work_items/create_task_shared_examples.rb
new file mode 100644
index 00000000000..7771e7f0e21
--- /dev/null
+++ b/spec/support/shared_examples/services/work_items/create_task_shared_examples.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'title with extra spaces' do
+ context 'when title has extra spaces' do
+ before do
+ params[:title] = " Awesome work item "
+ end
+
+ it 'removes extra leading and trailing whitespaces from title' do
+ subject
+
+ created_work_item = WorkItem.last
+ expect(created_work_item.title).to eq('Awesome work item')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
index 1da21633504..3ba5f080a01 100644
--- a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
+++ b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_database|
+RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_database, table_name|
include ExclusiveLeaseHelpers
describe 'defining the job attributes' do
@@ -136,8 +136,10 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
let(:job_interval) { 5.minutes }
let(:lease_timeout) { 15.minutes }
let(:lease_key) { described_class.name.demodulize.underscore }
- let(:migration) { build(:batched_background_migration, :active, interval: job_interval) }
let(:interval_variance) { described_class::INTERVAL_VARIANCE }
+ let(:migration) do
+ build(:batched_background_migration, :active, interval: job_interval, table_name: table_name)
+ end
before do
allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration)
@@ -233,7 +235,9 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
let(:migration_class) do
Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
- def perform(matching_status)
+ job_arguments :matching_status
+
+ def perform
each_sub_batch(
operation_name: :update_all,
batching_scope: -> (relation) { relation.where(status: matching_status) }
@@ -249,7 +253,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
create(
:batched_background_migration,
:active,
- table_name: table_name,
+ table_name: new_table_name,
column_name: :id,
max_value: migration_records,
batch_size: batch_size,
@@ -261,14 +265,14 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
end
let(:base_model) { Gitlab::Database.database_base_models[tracking_database] }
- let(:table_name) { 'example_data' }
+ let(:new_table_name) { '_test_example_data' }
let(:batch_size) { 5 }
let(:sub_batch_size) { 2 }
let(:number_of_batches) { 10 }
let(:migration_records) { batch_size * number_of_batches }
let(:connection) { Gitlab::Database.database_base_models[tracking_database].connection }
- let(:example_data) { define_batchable_model(table_name, connection: connection) }
+ let(:example_data) { define_batchable_model(new_table_name, connection: connection) }
around do |example|
Gitlab::Database::SharedModel.using_connection(connection) do
@@ -283,16 +287,16 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
# - one record beyond the migration's range
# - one record that doesn't match the migration job's batch condition
connection.execute(<<~SQL)
- CREATE TABLE #{table_name} (
+ CREATE TABLE #{new_table_name} (
id integer primary key,
some_column integer,
status smallint);
- INSERT INTO #{table_name} (id, some_column, status)
+ INSERT INTO #{new_table_name} (id, some_column, status)
SELECT generate_series, generate_series, 1
FROM generate_series(1, #{migration_records + 1});
- UPDATE #{table_name}
+ UPDATE #{new_table_name}
SET status = 0
WHERE some_column = #{migration_records - 5};
SQL
@@ -362,6 +366,15 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
expect { migration_run }.to change { migration.reload.on_hold? }.from(false).to(true)
end
+
+ it 'puts migration on hold when the pending WAL count is above the limit' do
+ sql = Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::WriteAheadLog::PENDING_WAL_COUNT_SQL
+ limit = Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::WriteAheadLog::LIMIT
+
+ expect(connection).to receive(:execute).with(sql).and_return([{ 'pending_wal_count' => limit + 1 }])
+
+ expect { migration_run }.to change { migration.reload.on_hold? }.from(false).to(true)
+ end
end
end
end