summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/database_cleaner.rb3
-rw-r--r--spec/support/fips.rb27
-rw-r--r--spec/support/gitlab_stubs/gitlab_ci.yml2
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb19
-rw-r--r--spec/support/helpers/features/invite_members_modal_helper.rb40
-rw-r--r--spec/support/helpers/features/runner_helpers.rb68
-rw-r--r--spec/support/helpers/gitaly_setup.rb2
-rw-r--r--spec/support/helpers/login_helpers.rb2
-rw-r--r--spec/support/helpers/navbar_structure_helper.rb12
-rw-r--r--spec/support/helpers/search_helpers.rb7
-rw-r--r--spec/support/helpers/test_env.rb5
-rw-r--r--spec/support/helpers/usage_data_helpers.rb1
-rw-r--r--spec/support/matchers/graphql_matchers.rb10
-rw-r--r--spec/support/matchers/markdown_matchers.rb2
-rw-r--r--spec/support/matchers/project_namespace_matcher.rb2
-rw-r--r--spec/support/services/deploy_token_shared_examples.rb4
-rw-r--r--spec/support/services/issuable_update_service_shared_examples.rb44
-rw-r--r--spec/support/shared_contexts/container_repositories_shared_context.rb14
-rw-r--r--spec/support/shared_contexts/finders/users_finder_shared_contexts.rb4
-rw-r--r--spec/support/shared_contexts/lib/container_registry/client_stubs_shared_context.rb4
-rw-r--r--spec/support/shared_contexts/markdown_golden_master_shared_examples.rb3
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb10
-rw-r--r--spec/support/shared_contexts/serializers/group_group_link_shared_context.rb6
-rw-r--r--spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb9
-rw-r--r--spec/support/shared_contexts/url_shared_context.rb4
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/features/access_tokens_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb46
-rw-r--r--spec/support/shared_examples/features/inviting_members_shared_examples.rb175
-rw-r--r--spec/support/shared_examples/features/project_upload_files_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/runners_shared_examples.rb141
-rw-r--r--spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/graphql/notes_creation_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/incident_management/issuable_escalation_statuses/build_examples.rb20
-rw-r--r--spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/lib/wikis_api_examples.rb6
-rw-r--r--spec/support/shared_examples/models/application_setting_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/concerns/bulk_users_by_email_load_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/models/concerns/from_set_operator_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/group_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb61
-rw-r--r--spec/support/shared_examples/models/issuable_link_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb60
-rw-r--r--spec/support/shared_examples/models/project_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb140
-rw-r--r--spec/support/shared_examples/policies/wiki_policies_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb90
-rw-r--r--spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/issuable_participants_examples.rb30
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb79
-rw-r--r--spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb94
-rw-r--r--spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/views/milestone_shared_examples.rb78
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb70
68 files changed, 1360 insertions, 379 deletions
diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb
index 8f706fdebc9..f8ddf3e66a5 100644
--- a/spec/support/database_cleaner.rb
+++ b/spec/support/database_cleaner.rb
@@ -20,6 +20,9 @@ RSpec.configure do |config|
# We drop and recreate the database if any table has more than 1200 columns, just to be safe.
if any_connection_class_with_more_than_allowed_columns?
recreate_all_databases!
+
+ # Seed required data as recreating DBs will delete it
+ TestEnv.seed_db
end
end
diff --git a/spec/support/fips.rb b/spec/support/fips.rb
new file mode 100644
index 00000000000..1d278dcdf60
--- /dev/null
+++ b/spec/support/fips.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+# rubocop: disable RSpec/EnvAssignment
+
+RSpec.configure do |config|
+ config.around(:each, :fips_mode) do |example|
+ set_fips_mode(true) do
+ example.run
+ end
+ end
+
+ config.around(:each, fips_mode: false) do |example|
+ set_fips_mode(false) do
+ example.run
+ end
+ end
+
+ def set_fips_mode(value)
+ prior_value = ENV["FIPS_MODE"]
+ ENV["FIPS_MODE"] = value.to_s
+
+ yield
+
+ ENV["FIPS_MODE"] = prior_value
+ end
+end
+
+# rubocop: enable RSpec/EnvAssignment
diff --git a/spec/support/gitlab_stubs/gitlab_ci.yml b/spec/support/gitlab_stubs/gitlab_ci.yml
index 52ae36229a6..b1533879e32 100644
--- a/spec/support/gitlab_stubs/gitlab_ci.yml
+++ b/spec/support/gitlab_stubs/gitlab_ci.yml
@@ -1,4 +1,4 @@
-image: ruby:2.6
+image: image:1.0
services:
- postgres
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index 70b794f7d82..044ec56b1cc 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -86,6 +86,25 @@ module CycleAnalyticsHelpers
wait_for_stages_to_load(ready_selector)
end
+ def select_value_stream(value_stream_name)
+ toggle_value_stream_dropdown
+
+ page.find('[data-testid="dropdown-value-streams"]').all('li button').find { |item| item.text == value_stream_name.to_s }.click
+ wait_for_requests
+ end
+
+ def create_value_stream_group_aggregation(group)
+ aggregation = Analytics::CycleAnalytics::Aggregation.safe_create_for_group(group)
+ Analytics::CycleAnalytics::AggregatorService.new(aggregation: aggregation).execute
+ end
+
+ def select_group_and_custom_value_stream(group, custom_value_stream_name)
+ create_value_stream_group_aggregation(group)
+
+ select_group(group)
+ select_value_stream(custom_value_stream_name)
+ end
+
def toggle_dropdown(field)
page.within("[data-testid*='#{field}']") do
find('.dropdown-toggle').click
diff --git a/spec/support/helpers/features/invite_members_modal_helper.rb b/spec/support/helpers/features/invite_members_modal_helper.rb
index 2a4f78ca57f..7ed64615020 100644
--- a/spec/support/helpers/features/invite_members_modal_helper.rb
+++ b/spec/support/helpers/features/invite_members_modal_helper.rb
@@ -5,19 +5,22 @@ module Spec
module Helpers
module Features
module InviteMembersModalHelper
- def invite_member(name, role: 'Guest', expires_at: nil)
+ def invite_member(names, role: 'Guest', expires_at: nil, refresh: true)
click_on 'Invite members'
- page.within '[data-testid="invite-modal"]' do
- find('[data-testid="members-token-select-input"]').set(name)
+ page.within invite_modal_selector do
+ Array.wrap(names).each do |name|
+ find(member_dropdown_selector).set(name)
+
+ wait_for_requests
+ click_button name
+ end
- wait_for_requests
- click_button name
choose_options(role, expires_at)
click_button 'Invite'
- page.refresh
+ page.refresh if refresh
end
end
@@ -43,6 +46,31 @@ module Spec
fill_in 'YYYY-MM-DD', with: expires_at.strftime('%Y-%m-%d') if expires_at
end
+
+ def click_groups_tab
+ expect(page).to have_link 'Groups'
+ click_link "Groups"
+ end
+
+ def group_dropdown_selector
+ '[data-testid="group-select-dropdown"]'
+ end
+
+ def member_dropdown_selector
+ '[data-testid="members-token-select-input"]'
+ end
+
+ def invite_modal_selector
+ '[data-testid="invite-modal"]'
+ end
+
+ def expect_to_have_group(group)
+ expect(page).to have_selector("[entity-id='#{group.id}']")
+ end
+
+ def expect_not_to_have_group(group)
+ expect(page).not_to have_selector("[entity-id='#{group.id}']")
+ end
end
end
end
diff --git a/spec/support/helpers/features/runner_helpers.rb b/spec/support/helpers/features/runner_helpers.rb
new file mode 100644
index 00000000000..63fc628358c
--- /dev/null
+++ b/spec/support/helpers/features/runner_helpers.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Spec
+ module Support
+ module Helpers
+ module Features
+ module RunnersHelpers
+ def within_runner_row(runner_id)
+ within "[data-testid='runner-row-#{runner_id}']" do
+ yield
+ end
+ end
+
+ def search_bar_selector
+ '[data-testid="runners-filtered-search"]'
+ end
+
+ # The filters must be clicked first to be able to receive events
+ # See: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1493
+ def focus_filtered_search
+ page.within(search_bar_selector) do
+ page.find('.gl-filtered-search-term-token').click
+ end
+ end
+
+ def input_filtered_search_keys(search_term)
+ focus_filtered_search
+
+ page.within(search_bar_selector) do
+ page.find('input').send_keys(search_term)
+ click_on 'Search'
+ end
+
+ wait_for_requests
+ end
+
+ def open_filtered_search_suggestions(filter)
+ focus_filtered_search
+
+ page.within(search_bar_selector) do
+ click_on filter
+ end
+
+ wait_for_requests
+ end
+
+ def input_filtered_search_filter_is_only(filter, value)
+ focus_filtered_search
+
+ page.within(search_bar_selector) do
+ click_on filter
+
+ # For OPERATOR_IS_ONLY, clicking the filter
+ # immediately preselects "=" operator
+
+ page.find('input').send_keys(value)
+ page.find('input').send_keys(:enter)
+
+ click_on 'Search'
+ end
+
+ wait_for_requests
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb
index a4ee618457d..0ad83bdeeb2 100644
--- a/spec/support/helpers/gitaly_setup.rb
+++ b/spec/support/helpers/gitaly_setup.rb
@@ -267,7 +267,7 @@ module GitalySetup
{ 'default' => repos_path },
force: true,
options: {
- internal_socket_dir: File.join(gitaly_dir, "internal_gitaly2"),
+ runtime_dir: File.join(gitaly_dir, "run2"),
gitaly_socket: "gitaly2.socket",
config_filename: "gitaly2.config.toml"
}
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 7666d71f13c..29b1bb260f2 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -99,7 +99,7 @@ module LoginHelpers
fill_in "user_password", with: (password || "12345678")
check 'user_remember_me' if remember
- click_button "Sign in"
+ find('[data-testid="sign-in-button"]:enabled').click
if two_factor_auth
fill_in "user_otp_attempt", with: user.reload.current_otp
diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb
index fb06ebfdae2..315303401cc 100644
--- a/spec/support/helpers/navbar_structure_helper.rb
+++ b/spec/support/helpers/navbar_structure_helper.rb
@@ -92,4 +92,16 @@ module NavbarStructureHelper
new_sub_nav_item_name: _('Google Cloud')
)
end
+
+ def analytics_sub_nav_item
+ [
+ _('Value stream'),
+ _('CI/CD'),
+ (_('Code review') if Gitlab.ee?),
+ (_('Merge request') if Gitlab.ee?),
+ _('Repository')
+ ]
+ end
end
+
+NavbarStructureHelper.prepend_mod
diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb
index f5a1a97a1d0..581ef07752e 100644
--- a/spec/support/helpers/search_helpers.rb
+++ b/spec/support/helpers/search_helpers.rb
@@ -2,9 +2,12 @@
module SearchHelpers
def fill_in_search(text)
- page.within('.search-input-wrap') do
+ # Once the `new_header_search` feature flag has been removed
+ # We can remove the `.search-input-wrap` selector
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/339348
+ page.within('.header-search-new') do
find('#search').click
- fill_in('search', with: text)
+ fill_in 'search', with: text
end
wait_for_all_requests
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 587d4e22828..d81d0d436a1 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -41,7 +41,6 @@ module TestEnv
'pages-deploy-target' => '7975be0',
'audio' => 'c3c21fd',
'video' => '8879059',
- 'add-balsamiq-file' => 'b89b56d',
'crlf-diff' => '5938907',
'conflict-start' => '824be60',
'conflict-resolvable' => '1450cd6',
@@ -81,7 +80,9 @@ module TestEnv
'compare-with-merge-head-source' => 'f20a03d',
'compare-with-merge-head-target' => '2f1e176',
'trailers' => 'f0a5ed6',
- 'add_commit_with_5mb_subject' => '8cf8e80'
+ 'add_commit_with_5mb_subject' => '8cf8e80',
+ 'blame-on-renamed' => '32c33da',
+ 'with-executables' => '6b8dc4a'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index b9f90b11a69..50d1b14cf56 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -26,7 +26,6 @@ module UsageDataHelpers
COUNTS_KEYS = %i(
assignee_lists
- boards
ci_builds
ci_internal_pipelines
ci_external_pipelines
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index dcaec176687..3ba88c3ae71 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -7,14 +7,14 @@ RSpec::Matchers.define :require_graphql_authorizations do |*expected|
if klass.respond_to?(:required_permissions)
klass.required_permissions
else
- [klass.to_graphql.metadata[:authorize]]
+ Array.wrap(klass.authorize)
end
end
match do |klass|
actual = permissions_for(klass)
- expect(actual).to match_array(expected)
+ expect(actual).to match_array(expected.compact)
end
failure_message do |klass|
@@ -213,16 +213,16 @@ RSpec::Matchers.define :have_graphql_resolver do |expected|
match do |field|
case expected
when Method
- expect(field.to_graphql.metadata[:type_class].resolve_proc).to eq(expected)
+ expect(field.type_class.resolve_proc).to eq(expected)
else
- expect(field.to_graphql.metadata[:type_class].resolver).to eq(expected)
+ expect(field.type_class.resolver).to eq(expected)
end
end
end
RSpec::Matchers.define :have_graphql_extension do |expected|
match do |field|
- expect(field.to_graphql.metadata[:type_class].extensions).to include(expected)
+ expect(field.type_class.extensions).to include(expected)
end
end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index f01c4075eeb..1932f78506f 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -270,7 +270,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_link(href: 'http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==')
+ expect(actual).to have_link(href: 'http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjliuUCAE_tHdw=')
end
end
end
diff --git a/spec/support/matchers/project_namespace_matcher.rb b/spec/support/matchers/project_namespace_matcher.rb
index 95aa5429679..8666a605276 100644
--- a/spec/support/matchers/project_namespace_matcher.rb
+++ b/spec/support/matchers/project_namespace_matcher.rb
@@ -10,7 +10,7 @@ RSpec::Matchers.define :be_in_sync_with_project do |project|
project_namespace.present? &&
project.name == project_namespace.name &&
project.path == project_namespace.path &&
- project.namespace == project_namespace.parent &&
+ project.namespace_id == project_namespace.parent_id &&
project.visibility_level == project_namespace.visibility_level &&
project.shared_runners_enabled == project_namespace.shared_runners_enabled
end
diff --git a/spec/support/services/deploy_token_shared_examples.rb b/spec/support/services/deploy_token_shared_examples.rb
index adc5ea0fcdc..d322b3fc81d 100644
--- a/spec/support/services/deploy_token_shared_examples.rb
+++ b/spec/support/services/deploy_token_shared_examples.rb
@@ -19,6 +19,10 @@ RSpec.shared_examples 'a deploy token creation service' do
it 'returns a DeployToken' do
expect(subject[:deploy_token]).to be_an_instance_of DeployToken
end
+
+ it 'sets the creator_id as the id of the current_user' do
+ expect(subject[:deploy_token].read_attribute(:creator_id)).to eq(user.id)
+ end
end
context 'when expires at date is not passed' do
diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb
index 4d2843af1c4..c168df7a7d2 100644
--- a/spec/support/services/issuable_update_service_shared_examples.rb
+++ b/spec/support/services/issuable_update_service_shared_examples.rb
@@ -23,3 +23,47 @@ RSpec.shared_examples 'issuable update service' do
end
end
end
+
+RSpec.shared_examples 'keeps issuable labels sorted after update' do
+ before do
+ update_issuable(label_ids: [label_b.id])
+ end
+
+ context 'when label is changed' do
+ it 'keeps the labels sorted by title ASC' do
+ update_issuable({ add_label_ids: [label_a.id] })
+
+ expect(issuable.labels).to eq([label_a, label_b])
+ end
+ end
+end
+
+RSpec.shared_examples 'broadcasting issuable labels updates' do
+ before do
+ update_issuable(label_ids: [label_a.id])
+ end
+
+ context 'when label is added' do
+ it 'triggers the GraphQL subscription' do
+ expect(GraphqlTriggers).to receive(:issuable_labels_updated).with(issuable)
+
+ update_issuable({ add_label_ids: [label_b.id] })
+ end
+ end
+
+ context 'when label is removed' do
+ it 'triggers the GraphQL subscription' do
+ expect(GraphqlTriggers).to receive(:issuable_labels_updated).with(issuable)
+
+ update_issuable({ remove_label_ids: [label_a.id] })
+ end
+ end
+
+ context 'when label is unchanged' do
+ it 'does not trigger the GraphQL subscription' do
+ expect(GraphqlTriggers).not_to receive(:issuable_labels_updated).with(issuable)
+
+ update_issuable({ label_ids: [label_a.id] })
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/container_repositories_shared_context.rb b/spec/support/shared_contexts/container_repositories_shared_context.rb
index 9a9f80a3cbd..a74b09d38bd 100644
--- a/spec/support/shared_contexts/container_repositories_shared_context.rb
+++ b/spec/support/shared_contexts/container_repositories_shared_context.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
RSpec.shared_context 'importable repositories' do
- let_it_be(:root_group) { create(:group) }
- let_it_be(:group) { create(:group, parent_id: root_group.id) }
- let_it_be(:project) { create(:project, namespace: group) }
- let_it_be(:valid_container_repository) { create(:container_repository, project: project, created_at: 2.days.ago) }
- let_it_be(:valid_container_repository2) { create(:container_repository, project: project, created_at: 1.year.ago) }
- let_it_be(:importing_container_repository) { create(:container_repository, :importing, project: project, created_at: 2.days.ago) }
- let_it_be(:new_container_repository) { create(:container_repository, project: project) }
+ let_it_be(:valid_container_repository) { create(:container_repository, created_at: 2.days.ago, migration_plan: 'free') }
+ let_it_be(:valid_container_repository2) { create(:container_repository, created_at: 1.year.ago, migration_plan: 'free') }
+ let_it_be(:importing_container_repository) { create(:container_repository, :importing, created_at: 2.days.ago, migration_plan: 'free') }
+ let_it_be(:new_container_repository) { create(:container_repository, migration_plan: 'free') }
let_it_be(:denied_root_group) { create(:group) }
let_it_be(:denied_group) { create(:group, parent_id: denied_root_group.id) }
@@ -18,7 +15,8 @@ RSpec.shared_context 'importable repositories' do
stub_application_setting(container_registry_import_created_before: 1.day.ago)
stub_feature_flags(
container_registry_phase_2_deny_list: false,
- container_registry_migration_limit_gitlab_org: false
+ container_registry_migration_limit_gitlab_org: false,
+ container_registry_migration_phase2_all_plans: false
)
Feature::FlipperGate.create!(
diff --git a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
index 6a09497a497..ef1c01f72f9 100644
--- a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
@@ -3,8 +3,10 @@
RSpec.shared_context 'UsersFinder#execute filter by project context' do
let_it_be(:normal_user) { create(:user, username: 'johndoe') }
let_it_be(:admin_user) { create(:user, :admin, username: 'iamadmin') }
+ let_it_be(:banned_user) { create(:user, :banned, username: 'iambanned') }
let_it_be(:blocked_user) { create(:user, :blocked, username: 'notsorandom') }
let_it_be(:external_user) { create(:user, :external) }
+ let_it_be(:unconfirmed_user) { create(:user, confirmed_at: nil) }
let_it_be(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
- let_it_be(:internal_user) { User.alert_bot }
+ let_it_be(:internal_user) { User.alert_bot.tap { |u| u.confirm } }
end
diff --git a/spec/support/shared_contexts/lib/container_registry/client_stubs_shared_context.rb b/spec/support/shared_contexts/lib/container_registry/client_stubs_shared_context.rb
index d857e683aa2..196173d4a63 100644
--- a/spec/support/shared_contexts/lib/container_registry/client_stubs_shared_context.rb
+++ b/spec/support/shared_contexts/lib/container_registry/client_stubs_shared_context.rb
@@ -8,8 +8,8 @@ RSpec.shared_context 'container registry client stubs' do
end
end
- def stub_container_registry_gitlab_api_repository_details(client, path:, size_bytes:)
- allow(client).to receive(:repository_details).with(path, with_size: true).and_return('size_bytes' => size_bytes)
+ def stub_container_registry_gitlab_api_repository_details(client, path:, size_bytes:, sizing: :self)
+ allow(client).to receive(:repository_details).with(path, sizing: sizing).and_return('size_bytes' => size_bytes)
end
def stub_container_registry_gitlab_api_network_error(client_method: :supports_gitlab_api?)
diff --git a/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb b/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb
index d0915bbf158..dea03af2248 100644
--- a/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb
+++ b/spec/support/shared_contexts/markdown_golden_master_shared_examples.rb
@@ -64,6 +64,9 @@ RSpec.shared_context 'API::Markdown Golden Master shared context' do |markdown_y
let(:substitutions) { markdown_example.fetch(:substitutions, {}) }
it "verifies conversion of GFM to HTML", :unlimited_max_formatted_output_length do
+ stub_application_setting(plantuml_enabled: true, plantuml_url: 'http://localhost:8080')
+ stub_application_setting(kroki_enabled: true, kroki_url: 'http://localhost:8000')
+
pending pending_reason if pending_reason
normalized_example_html = normalize_html(example_html, substitutions)
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index b4a71f52092..65c7f63cf6e 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.shared_context 'project navbar structure' do
+ include NavbarStructureHelper
+
let(:security_and_compliance_nav_item) do
{
nav_item: _('Security & Compliance'),
@@ -93,13 +95,7 @@ RSpec.shared_context 'project navbar structure' do
},
{
nav_item: _('Analytics'),
- nav_sub_items: [
- _('Value stream'),
- _('CI/CD'),
- (_('Code review') if Gitlab.ee?),
- (_('Merge request') if Gitlab.ee?),
- _('Repository')
- ]
+ nav_sub_items: analytics_sub_nav_item
},
{
nav_item: _('Wiki'),
diff --git a/spec/support/shared_contexts/serializers/group_group_link_shared_context.rb b/spec/support/shared_contexts/serializers/group_group_link_shared_context.rb
index fce78957eba..efd5d344a28 100644
--- a/spec/support/shared_contexts/serializers/group_group_link_shared_context.rb
+++ b/spec/support/shared_contexts/serializers/group_group_link_shared_context.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
RSpec.shared_context 'group_group_link' do
- let(:shared_with_group) { create(:group) }
- let(:shared_group) { create(:group) }
+ let_it_be(:shared_with_group) { create(:group) }
+ let_it_be(:shared_group) { create(:group) }
- let!(:group_group_link) do
+ let_it_be(:group_group_link) do
create(
:group_group_link,
{
diff --git a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb
index e1d864213b5..37d410a35bf 100644
--- a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb
+++ b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb
@@ -21,7 +21,7 @@ RSpec.shared_context 'stubbed service ping metrics definitions' do
let(:optional_metrics) do
[
- metric_attributes('counts.boards', 'optional', 'number'),
+ metric_attributes('counts.boards', 'optional', 'number', 'CountBoardsMetric'),
metric_attributes('gitaly.filesystems', '').except('data_category'),
metric_attributes('usage_activity_by_stage.monitor.projects_with_enabled_alert_integrations_histogram', 'optional', 'object'),
metric_attributes('topology', 'optional', 'object')
@@ -43,11 +43,14 @@ RSpec.shared_context 'stubbed service ping metrics definitions' do
Gitlab::Usage::MetricDefinition.instance_variable_set(:@all, nil)
end
- def metric_attributes(key_path, category, value_type = 'string')
+ def metric_attributes(key_path, category, value_type = 'string', instrumentation_class = '')
{
'key_path' => key_path,
'data_category' => category,
- 'value_type' => value_type
+ 'value_type' => value_type,
+ 'status' => 'active',
+ 'instrumentation_class' => instrumentation_class,
+ 'time_frame' => 'all'
}
end
end
diff --git a/spec/support/shared_contexts/url_shared_context.rb b/spec/support/shared_contexts/url_shared_context.rb
index da1d6e0049c..0e1534bf6c7 100644
--- a/spec/support/shared_contexts/url_shared_context.rb
+++ b/spec/support/shared_contexts/url_shared_context.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.shared_context 'valid urls with CRLF' do
- let(:valid_urls_with_CRLF) do
+ let(:valid_urls_with_crlf) do
[
"http://example.com/pa%0dth",
"http://example.com/pa%0ath",
@@ -16,7 +16,7 @@ RSpec.shared_context 'valid urls with CRLF' do
end
RSpec.shared_context 'invalid urls' do
- let(:urls_with_CRLF) do
+ let(:urls_with_crlf) do
[
"git://example.com/pa%0dth",
"git://example.com/pa%0ath",
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
index 1e303197990..15590fd10dc 100644
--- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -138,7 +138,7 @@ RSpec.shared_examples 'multiple issue boards' do
wait_for_requests
- dropdown_selector = '.js-boards-selector .dropdown-menu'
+ dropdown_selector = '[data-testid="boards-selector"] .dropdown-menu'
page.within(dropdown_selector) do
yield
end
diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
index 46fc2cbdc9b..2ea98002de1 100644
--- a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb
@@ -184,6 +184,41 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do
expect(json_response.dig("provider_repos").count).to eq(1)
end
end
+
+ context 'when namespace_id query param is provided' do
+ let_it_be(:current_user) { create(:user) }
+
+ let(:namespace) { create(:namespace) }
+
+ before do
+ allow(controller).to receive(:current_user).and_return(current_user)
+ end
+
+ context 'when user is allowed to create projects in this namespace' do
+ before do
+ allow(current_user).to receive(:can?).and_return(true)
+ end
+
+ it 'provides namespace to the template' do
+ get :status, params: { namespace_id: namespace.id }, format: :html
+
+ expect(response).to have_gitlab_http_status :ok
+ expect(assigns(:namespace)).to eq(namespace)
+ end
+ end
+
+ context 'when user is not allowed to create projects in this namespace' do
+ before do
+ allow(current_user).to receive(:can?).and_return(false)
+ end
+
+ it 'renders 404' do
+ get :status, params: { namespace_id: namespace.id }, format: :html
+
+ expect(response).to have_gitlab_http_status :not_found
+ end
+ end
+ end
end
end
@@ -515,7 +550,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET realtime_changes' do
get :realtime_changes
- expect(json_response).to eq([{ "id" => project.id, "import_status" => project.import_status }])
+ expect(json_response).to match([a_hash_including({ "id" => project.id, "import_status" => project.import_status })])
expect(Integer(response.headers['Poll-Interval'])).to be > -1
end
end
diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
index bf26922d9c5..885c0229038 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -150,6 +150,7 @@ RSpec.shared_examples 'wiki controller actions' do
expect(response).to render_template('shared/wikis/diff')
expect(assigns(:diffs)).to be_a(Gitlab::Diff::FileCollection::Base)
expect(assigns(:diff_notes_disabled)).to be(true)
+ expect(assigns(:page).content).to be_empty
end
end
@@ -475,9 +476,13 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when page exists' do
shared_examples 'deletes the page' do
specify do
- expect do
- request
- end.to change { wiki.list_pages.size }.by(-1)
+ aggregate_failures do
+ expect do
+ request
+ end.to change { wiki.list_pages.size }.by(-1)
+
+ expect(assigns(:page).content).to be_empty
+ 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 ae246a87bb6..215d9d3e5a8 100644
--- a/spec/support/shared_examples/features/access_tokens_shared_examples.rb
+++ b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
@@ -29,15 +29,15 @@ RSpec.shared_examples 'resource access tokens creation' do |resource_type|
click_on '1'
# Scopes
- check 'api'
check 'read_api'
+ check 'read_repository'
click_on "Create #{resource_type} access token"
expect(active_resource_access_tokens).to have_text(name)
expect(active_resource_access_tokens).to have_text('in')
- expect(active_resource_access_tokens).to have_text('api')
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(created_resource_access_token).not_to be_empty
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 2332285540a..5c44cb7f04b 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -1,14 +1,48 @@
# frozen_string_literal: true
RSpec.shared_examples 'edits content using the content editor' do
- it 'formats text as bold using bubble menu' do
- content_editor_testid = '[data-testid="content-editor"] [contenteditable]'
+ content_editor_testid = '[data-testid="content-editor"] [contenteditable].ProseMirror'
- expect(page).to have_css(content_editor_testid)
+ describe 'formatting bubble menu' do
+ it 'shows a formatting bubble menu for a regular paragraph' do
+ expect(page).to have_css(content_editor_testid)
- find(content_editor_testid).send_keys 'Typing text in the content editor'
- find(content_editor_testid).send_keys [:shift, :left]
+ find(content_editor_testid).send_keys 'Typing text in the content editor'
+ find(content_editor_testid).send_keys [:shift, :left]
- expect(page).to have_css('[data-testid="formatting-bubble-menu"]')
+ 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]
+
+ expect(page).not_to have_css('[data-testid="formatting-bubble-menu"]')
+ end
+ end
+
+ describe 'code block bubble menu' do
+ it 'shows a code block bubble menu for a code block' do
+ find(content_editor_testid).send_keys '```js ' # trigger input rule
+ find(content_editor_testid).send_keys 'var a = 0'
+ find(content_editor_testid).send_keys [:shift, :left]
+
+ expect(page).not_to have_css('[data-testid="formatting-bubble-menu"]')
+ expect(page).to have_css('[data-testid="code-block-bubble-menu"]')
+ end
+
+ it 'sets code block type to "javascript" for `js`' do
+ find(content_editor_testid).send_keys '```js '
+ find(content_editor_testid).send_keys 'var a = 0'
+
+ expect(find('[data-testid="code-block-bubble-menu"]')).to have_text('Javascript')
+ end
+
+ it 'sets code block type to "Custom (nomnoml)" for `nomnoml`' do
+ find(content_editor_testid).send_keys '```nomnoml '
+ find(content_editor_testid).send_keys 'test'
+
+ expect(find('[data-testid="code-block-bubble-menu"]')).to have_text('Custom (nomnoml)')
+ end
end
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
new file mode 100644
index 00000000000..58357b262f5
--- /dev/null
+++ b/spec/support/shared_examples/features/inviting_members_shared_examples.rb
@@ -0,0 +1,175 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
+ before_all do
+ group.add_owner(user1)
+ end
+
+ it 'adds user as member', :js, :snowplow, :aggregate_failures do
+ visit members_page_path
+
+ invite_member(user2.name, role: 'Reporter')
+
+ page.within find_member_row(user2) do
+ expect(page).to have_button('Reporter')
+ end
+
+ expect_snowplow_event(
+ category: 'Members::InviteService',
+ action: 'create_member',
+ label: snowplow_invite_label,
+ property: 'existing_user',
+ user: user1
+ )
+ end
+
+ it 'invites user by email', :js, :snowplow, :aggregate_failures do
+ visit members_page_path
+
+ invite_member('test@example.com', role: 'Reporter')
+
+ click_link 'Invited'
+
+ page.within find_invited_member_row('test@example.com') do
+ expect(page).to have_button('Reporter')
+ end
+
+ expect_snowplow_event(
+ category: 'Members::InviteService',
+ action: 'create_member',
+ label: snowplow_invite_label,
+ property: 'net_new_user',
+ user: user1
+ )
+ end
+
+ it 'invites user by username and invites user by email', :js, :aggregate_failures do
+ visit members_page_path
+
+ invite_member([user2.name, 'test@example.com'], role: 'Reporter')
+
+ page.within find_member_row(user2) do
+ expect(page).to have_button('Reporter')
+ end
+
+ click_link 'Invited'
+
+ page.within find_invited_member_row('test@example.com') do
+ expect(page).to have_button('Reporter')
+ end
+ end
+
+ context 'when member is already a member by username' do
+ it 'updates the member for that user', :js do
+ visit members_page_path
+
+ invite_member(user2.name, role: 'Developer')
+
+ invite_member(user2.name, role: 'Reporter', refresh: false)
+
+ expect(page).not_to have_selector(invite_modal_selector)
+
+ page.refresh
+
+ page.within find_invited_member_row(user2.name) do
+ expect(page).to have_button('Reporter')
+ end
+ end
+ end
+
+ context 'when member is already a member by email' do
+ it 'fails with an error', :js do
+ visit members_page_path
+
+ invite_member('test@example.com', role: 'Developer')
+
+ invite_member('test@example.com', role: 'Reporter', refresh: false)
+
+ expect(page).to have_selector(invite_modal_selector)
+ expect(page).to have_content("The member's email address has already been taken")
+
+ page.refresh
+
+ click_link 'Invited'
+
+ page.within find_invited_member_row('test@example.com') do
+ expect(page).to have_button('Developer')
+ end
+ end
+ end
+
+ context 'when inviting a parent group member to the sub-entity' do
+ before_all do
+ group.add_owner(user1)
+ group.add_developer(user2)
+ end
+
+ context 'when role is higher than parent group membership' do
+ let(:role) { 'Maintainer' }
+
+ it 'adds the user as a member on sub-entity with higher access level', :js do
+ visit subentity_members_page_path
+
+ invite_member(user2.name, role: role, refresh: false)
+
+ expect(page).not_to have_selector(invite_modal_selector)
+
+ page.refresh
+
+ page.within find_invited_member_row(user2.name) do
+ expect(page).to have_button(role)
+ end
+ end
+ end
+
+ context 'when role is lower than parent group membership' do
+ let(:role) { 'Reporter' }
+
+ it 'fails with an error', :js do
+ visit subentity_members_page_path
+
+ invite_member(user2.name, role: role, refresh: false)
+
+ expect(page).to have_selector(invite_modal_selector)
+ expect(page).to have_content "Access level should be greater than or equal to Developer inherited membership " \
+ "from group #{group.name}"
+
+ page.refresh
+
+ page.within find_invited_member_row(user2.name) do
+ expect(page).to have_content('Developer')
+ expect(page).not_to have_button('Developer')
+ end
+ end
+
+ context 'when there are multiple users invited with errors' do
+ let_it_be(:user3) { create(:user) }
+
+ before do
+ group.add_maintainer(user3)
+ end
+
+ it 'only shows the first user error', :js do
+ visit subentity_members_page_path
+
+ invite_member([user2.name, user3.name], role: role, refresh: false)
+
+ expect(page).to have_selector(invite_modal_selector)
+ expect(page).to have_text("Access level should be greater than or equal to", count: 1)
+
+ page.refresh
+
+ page.within find_invited_member_row(user2.name) do
+ expect(page).to have_content('Developer')
+ expect(page).not_to have_button('Developer')
+ end
+
+ page.within find_invited_member_row(user3.name) do
+ expect(page).to have_content('Maintainer')
+ expect(page).not_to have_button('Maintainer')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
index 066c3e17a09..0a5ad5a59c0 100644
--- a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
@@ -62,7 +62,7 @@ RSpec.shared_examples 'it uploads and commits a new image file' do |drop: false|
visit(project_blob_path(project, 'upload_image/logo_sample.svg'))
- expect(page).to have_css('.file-content img')
+ expect(page).to have_css('.file-holder img')
end
end
diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb
new file mode 100644
index 00000000000..d9460c7b8f1
--- /dev/null
+++ b/spec/support/shared_examples/features/runners_shared_examples.rb
@@ -0,0 +1,141 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'shows and resets runner registration token' do
+ include Spec::Support::Helpers::ModalHelpers
+ include Spec::Support::Helpers::Features::RunnersHelpers
+
+ before do
+ click_on dropdown_text
+ end
+
+ describe 'shows registration instructions' do
+ before do
+ click_on 'Show runner installation and registration instructions'
+
+ wait_for_requests
+ end
+
+ it 'opens runner installation modal', :aggregate_failures do
+ within_modal do
+ expect(page).to have_text "Install a runner"
+ expect(page).to have_text "Environment"
+ expect(page).to have_text "Architecture"
+ expect(page).to have_text "Download and install binary"
+ end
+ end
+
+ it 'dismisses runner installation modal' do
+ within_modal do
+ click_button('Close', match: :first)
+ end
+
+ expect(page).not_to have_text "Install a runner"
+ end
+ end
+
+ it 'has a registration token' do
+ click_on 'Click to reveal'
+ expect(page.find('[data-testid="token-value"] input').value).to have_content(registration_token)
+ end
+
+ describe 'reset registration token' do
+ let!(:old_registration_token) { find('[data-testid="token-value"] input').value }
+
+ before do
+ click_on 'Reset registration token'
+
+ within_modal do
+ click_button('Reset token', match: :first)
+ end
+
+ wait_for_requests
+ end
+
+ it 'changes registration token' do
+ expect(find('.gl-toast')).to have_content('New registration token generated!')
+
+ click_on dropdown_text
+ click_on 'Click to reveal'
+
+ expect(old_registration_token).not_to eq registration_token
+ end
+ end
+end
+
+RSpec.shared_examples 'shows no runners' do
+ it 'shows counts with 0' do
+ expect(page).to have_text "Online runners 0"
+ expect(page).to have_text "Offline runners 0"
+ expect(page).to have_text "Stale runners 0"
+ end
+
+ it 'shows "no runners" message' do
+ expect(page).to have_text 'No runners found'
+ end
+end
+
+RSpec.shared_examples 'shows runner in list' do
+ it 'does not show empty state' do
+ expect(page).not_to have_content 'No runners found'
+ end
+
+ it 'shows runner row' do
+ within_runner_row(runner.id) do
+ expect(page).to have_text "##{runner.id}"
+ expect(page).to have_text runner.short_sha
+ expect(page).to have_text runner.description
+ end
+ end
+end
+
+RSpec.shared_examples 'pauses, resumes and deletes a runner' do
+ include Spec::Support::Helpers::ModalHelpers
+
+ it 'pauses and resumes runner' do
+ within_runner_row(runner.id) do
+ click_button "Pause"
+
+ expect(page).to have_text 'paused'
+ expect(page).to have_button 'Resume'
+ expect(page).not_to have_button 'Pause'
+
+ click_button "Resume"
+
+ expect(page).not_to have_text 'paused'
+ expect(page).not_to have_button 'Resume'
+ expect(page).to have_button 'Pause'
+ end
+ end
+
+ describe 'deletes runner' do
+ before do
+ within_runner_row(runner.id) do
+ click_on 'Delete runner'
+ end
+ end
+
+ it 'shows a confirmation modal' do
+ expect(page).to have_text "Delete runner ##{runner.id} (#{runner.short_sha})?"
+ expect(page).to have_text "Are you sure you want to continue?"
+ end
+
+ it 'deletes a runner' do
+ within_modal do
+ click_on 'Delete runner'
+ end
+
+ expect(page.find('.gl-toast')).to have_text(/Runner .+ deleted/)
+ expect(page).not_to have_content runner.description
+ end
+
+ it 'cancels runner deletion' do
+ within_modal do
+ click_on 'Cancel'
+ end
+
+ wait_for_requests
+
+ expect(page).to have_content runner.description
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
index bb5460e2a6f..095c48cade8 100644
--- a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
+++ b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
@@ -11,6 +11,7 @@ RSpec.shared_examples 'search timeouts' do |scope|
end
it 'renders timeout information' do
+ # expect(page).to have_content('This endpoint has been requested too many times.')
expect(page).to have_content('Your search timed out')
end
diff --git a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
index f676b6aa60d..41b1964cff0 100644
--- a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb
@@ -20,6 +20,12 @@ RSpec.shared_examples 'User creates wiki page' do
click_link "Create your first page"
end
+ it 'shows all available formats in the dropdown' do
+ Wiki::VALID_USER_MARKUPS.each do |key, markup|
+ expect(page).to have_css("#wiki_format option[value=#{key}]", text: markup[:name])
+ end
+ end
+
it "disables the submit button", :js do
page.within(".wiki-form") do
fill_in(:wiki_content, with: "")
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 85490bffc0e..12a4c6d7583 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
@@ -145,19 +145,6 @@ RSpec.shared_examples 'User updates wiki page' do
it_behaves_like 'edits content using the content editor'
end
-
- context 'with feature flag off' do
- before do
- stub_feature_flags(wiki_switch_between_content_editor_raw_markdown: false)
- visit(wiki_path(wiki))
-
- click_link('Edit')
-
- click_button 'Use the new editor'
- end
-
- it_behaves_like 'edits content using the content editor'
- end
end
end
diff --git a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
index 2e3a3ce6b41..04bb2fb69bb 100644
--- a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
@@ -3,16 +3,6 @@
RSpec.shared_examples 'boards create mutation' do
include GraphqlHelpers
- let_it_be(:current_user, reload: true) { create(:user) }
- let(:name) { 'board name' }
- let(:mutation) { graphql_mutation(:create_board, params) }
-
- subject { post_graphql_mutation(mutation, current_user: current_user) }
-
- def mutation_response
- graphql_mutation_response(:create_board)
- end
-
context 'when the user does not have permission' do
it_behaves_like 'a mutation that returns a top-level access error'
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 56b6dc682eb..2c6118779e6 100644
--- a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
@@ -85,3 +85,14 @@ RSpec.shared_examples 'a Note mutation when there are rate limit validation erro
end
end
end
+
+RSpec.shared_examples 'a Note mutation with confidential notes' do
+ it_behaves_like 'a Note mutation that creates a Note'
+
+ it 'returns a Note with confidentiality enabled' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('note')
+ expect(mutation_response['note']['confidential']).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 efb2c466f70..3caf153c2fa 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
@@ -62,9 +62,9 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
expect(deprecable.deprecation_reason).to include 'This was renamed.'
end
- it 'supports named reasons: discouraged' do
- deprecable = subject(deprecated: { milestone: '1.10', reason: :discouraged })
+ it 'supports named reasons: alpha' do
+ deprecable = subject(deprecated: { milestone: '1.10', reason: :alpha })
- expect(deprecable.deprecation_reason).to include 'Use of this is not recommended.'
+ expect(deprecable.deprecation_reason).to include 'This feature is in Alpha'
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
new file mode 100644
index 00000000000..c2c27fb65ca
--- /dev/null
+++ b/spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+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}"}
+
+ it 'returns the full endpoint url' do
+ expect(helper.wiki_page_render_api_endpoint(page)).to end_with(url)
+ end
+
+ context 'when relative url is set' do
+ let(:relative_url) { "/gitlab#{url}" }
+
+ it 'returns the full endpoint url with the relative path' do
+ stub_config_setting(relative_url_root: '/gitlab')
+
+ expect(helper.wiki_page_render_api_endpoint(page)).to end_with(relative_url)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/incident_management/issuable_escalation_statuses/build_examples.rb b/spec/support/shared_examples/incident_management/issuable_escalation_statuses/build_examples.rb
new file mode 100644
index 00000000000..050fdc3fff7
--- /dev/null
+++ b/spec/support/shared_examples/incident_management/issuable_escalation_statuses/build_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'initializes new escalation status with expected attributes' do |attributes = {}|
+ let(:expected_attributes) { attributes }
+
+ specify do
+ expect { execute }.to change { incident.escalation_status }
+ .from(nil)
+ .to(instance_of(::IncidentManagement::IssuableEscalationStatus))
+
+ expect(incident.escalation_status).to have_attributes(
+ id: nil,
+ issue_id: incident.id,
+ policy_id: nil,
+ escalations_started_at: nil,
+ status_event: nil,
+ **expected_attributes
+ )
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb
new file mode 100644
index 00000000000..4fc15cacab4
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'subscribes to event' do
+ include AfterNextHelpers
+
+ it 'consumes the published event', :sidekiq_inline do
+ expect_next(described_class)
+ .to receive(:handle_event)
+ .with(instance_of(event.class))
+ .and_call_original
+
+ ::Gitlab::EventStore.publish(event)
+ end
+end
+
+def consume_event(subscriber:, event:)
+ subscriber.new.perform(event.class.name, event.data)
+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 4b956c2b566..b5d93aec1bf 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
@@ -5,7 +5,7 @@ RSpec.shared_examples 'a daily tracked issuable event' do
stub_application_setting(usage_ping_enabled: true)
end
- def count_unique(date_from:, date_to:)
+ def count_unique(date_from: 1.minute.ago, date_to: 1.minute.from_now)
Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to)
end
@@ -14,6 +14,7 @@ RSpec.shared_examples 'a daily tracked issuable event' do
expect(track_action(author: user1)).to be_truthy
expect(track_action(author: user1)).to be_truthy
expect(track_action(author: user2)).to be_truthy
+ expect(count_unique).to eq(2)
end
end
diff --git a/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb
index b4c438771ce..d816754f328 100644
--- a/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb
+++ b/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb
@@ -34,8 +34,16 @@ RSpec.shared_examples 'ZenTao menu with CE version' do
expect(subject.link).to eq zentao_integration.url
end
- it 'contains only open ZenTao item' do
- expect(subject.renderable_items.map(&:item_id)).to match_array [:open_zentao]
+ it 'renders external-link icon' do
+ expect(subject.sprite_icon).to eq 'external-link'
+ end
+
+ it 'renders ZenTao menu' do
+ expect(subject.title).to eq s_('ZentaoIntegration|ZenTao')
+ end
+
+ it 'does not contain items' do
+ expect(subject.renderable_items.count).to eq 0
end
end
end
diff --git a/spec/support/shared_examples/lib/wikis_api_examples.rb b/spec/support/shared_examples/lib/wikis_api_examples.rb
index f068a7676ad..c57ac328a60 100644
--- a/spec/support/shared_examples/lib/wikis_api_examples.rb
+++ b/spec/support/shared_examples/lib/wikis_api_examples.rb
@@ -80,6 +80,8 @@ RSpec.shared_examples_for 'wikis API returns wiki page' do
context 'when wiki page has versions' do
let(:new_content) { 'New content' }
+ let(:old_content) { page.content }
+ let(:old_version_id) { page.version.id }
before do
wiki.update_page(page.page, content: new_content, message: 'updated page')
@@ -96,10 +98,10 @@ RSpec.shared_examples_for 'wikis API returns wiki page' do
end
context 'when version param is set' do
- let(:params) { { version: page.version.id } }
+ let(:params) { { version: old_version_id } }
it 'retrieves the specific page version' do
- expect(json_response['content']).to eq(page.content)
+ expect(json_response['content']).to eq(old_content)
end
context 'when version param is not valid or inexistent' do
diff --git a/spec/support/shared_examples/models/application_setting_shared_examples.rb b/spec/support/shared_examples/models/application_setting_shared_examples.rb
index 38f5c7be393..74ec6474e80 100644
--- a/spec/support/shared_examples/models/application_setting_shared_examples.rb
+++ b/spec/support/shared_examples/models/application_setting_shared_examples.rb
@@ -239,7 +239,7 @@ RSpec.shared_examples 'application settings examples' do
describe '#allowed_key_types' do
it 'includes all key types by default' do
- expect(setting.allowed_key_types).to contain_exactly(*described_class::SUPPORTED_KEY_TYPES)
+ expect(setting.allowed_key_types).to contain_exactly(*Gitlab::SSHPublicKey.supported_types)
end
it 'excludes disabled key types' do
diff --git a/spec/support/shared_examples/models/concerns/bulk_users_by_email_load_shared_examples.rb b/spec/support/shared_examples/models/concerns/bulk_users_by_email_load_shared_examples.rb
new file mode 100644
index 00000000000..c3e9ff5c91a
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/bulk_users_by_email_load_shared_examples.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a BulkUsersByEmailLoad model' do
+ describe '#users_by_emails' do
+ let_it_be(:user1) { create(:user, emails: [create(:email, email: 'user1@example.com')]) }
+ let_it_be(:user2) { create(:user, emails: [create(:email, email: 'user2@example.com')]) }
+
+ subject(:model) { described_class.new(id: non_existing_record_id) }
+
+ context 'when nothing is loaded' do
+ let(:passed_emails) { [user1.emails.first.email, user2.email] }
+
+ it 'preforms the yielded query and supplies the data with only emails desired' do
+ expect(model.users_by_emails(passed_emails).keys).to contain_exactly(*passed_emails)
+ end
+ end
+
+ context 'when store is preloaded', :request_store do
+ let(:passed_emails) { [user1.emails.first.email, user2.email, user1.email] }
+ let(:resource_data) do
+ {
+ user1.emails.first.email => instance_double('User'),
+ user2.email => instance_double('User')
+ }
+ end
+
+ before do
+ Gitlab::SafeRequestStore["user_by_email_for_users:#{model.class.name}:#{model.id}"] = resource_data
+ end
+
+ it 'passes back loaded data and does not update the items that already exist' do
+ users_by_emails = model.users_by_emails(passed_emails)
+
+ expect(users_by_emails.keys).to contain_exactly(*passed_emails)
+ expect(users_by_emails).to include(resource_data.merge(user1.email => user1))
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/from_set_operator_shared_examples.rb b/spec/support/shared_examples/models/concerns/from_set_operator_shared_examples.rb
index 6b208c0024d..e625ba785d2 100644
--- a/spec/support/shared_examples/models/concerns/from_set_operator_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/from_set_operator_shared_examples.rb
@@ -20,6 +20,12 @@ RSpec.shared_examples 'from set operator' do |sql_klass|
expect(query.to_sql).to match(/FROM \(\(SELECT.+\)\n#{operator_keyword}\n\(SELECT.+\)\) users/m)
end
+ it "returns empty set when passing empty array" do
+ query = model.public_send(operator_method, [])
+
+ expect(query.to_sql).to match(/WHERE \(1=0\)/m)
+ end
+
it 'supports the use of a custom alias for the sub query' do
query = model.public_send(operator_method,
[model.where(id: 1), model.where(id: 2)],
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 d6415e98289..da5c35c970a 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
@@ -227,9 +227,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'for confidential notes' do
- before_all do
- issue_note.update!(confidential: true)
- end
+ let_it_be(:issue_note) { create(:note_on_issue, project: project, note: "issue note", confidential: true) }
it 'falls back to note channel' do
expect(::Slack::Messenger).to execute_with_options(channel: ['random'])
diff --git a/spec/support/shared_examples/models/group_shared_examples.rb b/spec/support/shared_examples/models/group_shared_examples.rb
new file mode 100644
index 00000000000..9f3359ba4ab
--- /dev/null
+++ b/spec/support/shared_examples/models/group_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'checks self and root ancestor feature flag' do
+ let_it_be(:root_group) { create(:group) }
+ let_it_be(:group) { create(:group, parent: root_group) }
+ let_it_be(:project) { create(:project, group: group) }
+
+ subject { group.public_send(feature_flag_method) }
+
+ context 'when FF 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 FF is enabled for the group' do
+ before do
+ stub_feature_flags(feature_flag => group)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when root_group is the actor' do
+ it 'is not enabled if the FF is enabled for a child' do
+ expect(root_group.public_send(feature_flag_method)).to be_falsey
+ end
+ end
+ end
+
+ context 'when FF is disabled globally' do
+ before do
+ stub_feature_flags(feature_flag => false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when FF is enabled globally' do
+ it { is_expected.to be_truthy }
+ end
+end
diff --git a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
deleted file mode 100644
index 13ffc1b7f87..00000000000
--- a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-# This shared example requires a `builder` and `user` variable
-RSpec.shared_examples 'issuable hook data' do |kind|
- let(:data) { builder.build(user: user) }
-
- include_examples 'project hook data' do
- let(:project) { builder.issuable.project }
- end
-
- include_examples 'deprecated repository hook data'
-
- context "with a #{kind}" do
- it 'contains issuable data' do
- expect(data[:object_kind]).to eq(kind)
- expect(data[:user]).to eq(user.hook_attrs)
- expect(data[:project]).to eq(builder.issuable.project.hook_attrs)
- expect(data[:object_attributes]).to eq(builder.issuable.hook_attrs)
- expect(data[:changes]).to eq({})
- expect(data[:repository]).to eq(builder.issuable.project.hook_attrs.slice(:name, :url, :description, :homepage))
- end
-
- it 'does not contain certain keys' do
- expect(data).not_to have_key(:assignees)
- expect(data).not_to have_key(:assignee)
- end
-
- describe 'changes are given' do
- let(:changes) do
- {
- cached_markdown_version: %w[foo bar],
- description: ['A description', 'A cool description'],
- description_html: %w[foo bar],
- in_progress_merge_commit_sha: %w[foo bar],
- lock_version: %w[foo bar],
- merge_jid: %w[foo bar],
- title: ['A title', 'Hello World'],
- title_html: %w[foo bar]
- }
- end
-
- let(:data) { builder.build(user: user, changes: changes) }
-
- it 'populates the :changes hash' do
- expect(data[:changes]).to match(hash_including({
- title: { previous: 'A title', current: 'Hello World' },
- description: { previous: 'A description', current: 'A cool description' }
- }))
- end
-
- it 'does not contain certain keys' do
- expect(data[:changes]).not_to have_key('cached_markdown_version')
- expect(data[:changes]).not_to have_key('description_html')
- expect(data[:changes]).not_to have_key('lock_version')
- expect(data[:changes]).not_to have_key('title_html')
- expect(data[:changes]).not_to have_key('in_progress_merge_commit_sha')
- expect(data[:changes]).not_to have_key('merge_jid')
- end
- end
- end
-end
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 ca98c2597a2..9892e66b582 100644
--- a/spec/support/shared_examples/models/issuable_link_shared_examples.rb
+++ b/spec/support/shared_examples/models/issuable_link_shared_examples.rb
@@ -55,6 +55,19 @@ RSpec.shared_examples 'issuable link' do
end
end
+ describe 'scopes' do
+ describe '.for_source_or_target' do
+ it 'returns only links where id is either source or target id' do
+ link1 = create(issuable_link_factory, source: issuable_link.source)
+ link2 = create(issuable_link_factory, target: issuable_link.source)
+ # unrelated link, should not be included in result list
+ create(issuable_link_factory) # rubocop: disable Rails/SaveBang
+
+ expect(described_class.for_source_or_target(issuable_link.source_id)).to match_array([issuable_link, link1, link2])
+ end
+ end
+ end
+
describe '.link_type' do
it { is_expected.to define_enum_for(:link_type).with_values(relates_to: 0, blocks: 1) }
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index 17026f085bb..a329a6dca91 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -88,19 +88,55 @@ RSpec.shared_examples_for "member creation" do
expect(member).to be_persisted
end
- context 'when admin mode is enabled', :enable_admin_mode do
+ context 'when adding a project_bot' do
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+
+ before_all do
+ source.add_owner(user)
+ end
+
+ context 'when project_bot is already a member' do
+ before do
+ source.add_developer(project_bot)
+ end
+
+ it 'does not update the member' do
+ member = described_class.new(source, project_bot, :maintainer, current_user: user).execute
+
+ expect(source.users.reload).to include(project_bot)
+ expect(member).to be_persisted
+ expect(member.access_level).to eq(Gitlab::Access::DEVELOPER)
+ expect(member.errors.full_messages).to include(/not authorized to update member/)
+ end
+ end
+
+ context 'when project_bot is not already a member' do
+ it 'adds the member' do
+ member = described_class.new(source, project_bot, :maintainer, current_user: user).execute
+
+ expect(source.users.reload).to include(project_bot)
+ expect(member).to be_persisted
+ end
+ end
+ end
+
+ context 'when admin mode is enabled', :enable_admin_mode, :aggregate_failures do
it 'sets members.created_by to the given admin current_user' do
member = described_class.new(source, user, :maintainer, current_user: admin).execute
+ expect(member).to be_persisted
+ expect(source.users.reload).to include(user)
expect(member.created_by).to eq(admin)
end
end
context 'when admin mode is disabled' do
- it 'rejects setting members.created_by to the given admin current_user' do
+ it 'rejects setting members.created_by to the given admin current_user', :aggregate_failures do
member = described_class.new(source, user, :maintainer, current_user: admin).execute
- expect(member.created_by).to be_nil
+ expect(member).not_to be_persisted
+ expect(source.users.reload).not_to include(user)
+ expect(member.errors.full_messages).to include(/not authorized to create member/)
end
end
@@ -142,7 +178,7 @@ RSpec.shared_examples_for "member creation" do
end
context 'when called with an unknown user id' do
- it 'adds the user as a member' do
+ it 'does not add the user as a member' do
expect(source.users).not_to include(user)
described_class.new(source, non_existing_record_id, :maintainer).execute
@@ -410,6 +446,22 @@ RSpec.shared_examples_for "bulk member creation" do
end
end
+ it 'with the same user sent more than once by user and by email' do
+ members = described_class.add_users(source, [user1, user1.email], :maintainer)
+
+ expect(members.map(&:user)).to contain_exactly(user1)
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
+ end
+
+ it 'with the same user sent more than once by user id and by email' do
+ members = described_class.add_users(source, [user1.id, user1.email], :maintainer)
+
+ expect(members.map(&:user)).to contain_exactly(user1)
+ expect(members).to all(be_a(member_type))
+ expect(members).to all(be_persisted)
+ end
+
context 'when a member already exists' do
before do
source.add_user(user1, :developer)
diff --git a/spec/support/shared_examples/models/project_shared_examples.rb b/spec/support/shared_examples/models/project_shared_examples.rb
new file mode 100644
index 00000000000..475ac1da04b
--- /dev/null
+++ b/spec/support/shared_examples/models/project_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'returns true if project is inactive' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:storage_size, :last_activity_at, :expected_result) do
+ 1.megabyte | 1.month.ago | false
+ 1.megabyte | 3.years.ago | false
+ 8.megabytes | 1.month.ago | false
+ 8.megabytes | 3.years.ago | true
+ end
+
+ with_them do
+ before do
+ stub_application_setting(inactive_projects_min_size_mb: 5)
+ stub_application_setting(inactive_projects_send_warning_email_after_months: 24)
+
+ project.statistics.storage_size = storage_size
+ project.last_activity_at = last_activity_at
+ project.save!
+ end
+
+ it 'returns expected result' do
+ expect(project.inactive?).to eq(expected_result)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index b3f79d9fe6e..03e9dd65e33 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -11,6 +11,10 @@ RSpec.shared_examples 'wiki model' do
subject { wiki }
+ it 'VALID_USER_MARKUPS contains all valid markups' do
+ expect(described_class::VALID_USER_MARKUPS.keys).to match_array(%i(markdown rdoc asciidoc org))
+ end
+
it 'container class includes HasWiki' do
# NOTE: This is not enforced at runtime, since we also need to support Geo::DeletedProject
expect(wiki_container).to be_kind_of(HasWiki)
@@ -427,45 +431,131 @@ RSpec.shared_examples 'wiki model' do
end
describe '#update_page' do
- let(:page) { create(:wiki_page, wiki: subject, title: 'update-page') }
+ shared_examples 'update_page tests' do
+ with_them do
+ let!(:page) { create(:wiki_page, wiki: subject, title: original_title, format: original_format, content: 'original content') }
+
+ let(:message) { 'updated page' }
+ let(:updated_content) { 'updated content' }
+
+ def update_page
+ subject.update_page(
+ page.page,
+ content: updated_content,
+ title: updated_title,
+ format: updated_format,
+ message: message
+ )
+ end
+
+ specify :aggregate_failures do
+ expect(subject).to receive(:after_wiki_activity)
+ expect(update_page).to eq true
+
+ page = subject.find_page(updated_title.presence || original_title)
- def update_page
- subject.update_page(
- page.page,
- content: 'some other content',
- format: :markdown,
- message: 'updated page'
- )
+ expect(page.raw_content).to eq(updated_content)
+ expect(page.path).to eq(expected_path)
+ expect(page.version.message).to eq(message)
+ expect(user.commit_email).not_to eq(user.email)
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ end
+ end
end
- it 'updates the content of the page' do
- update_page
- page = subject.find_page('update-page')
+ shared_context 'common examples' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:original_title, :original_format, :updated_title, :updated_format, :expected_path) do
+ 'test page' | :markdown | 'new test page' | :markdown | 'new-test-page.md'
+ 'test page' | :markdown | 'test page' | :markdown | 'test-page.md'
+ 'test page' | :markdown | 'test page' | :asciidoc | 'test-page.asciidoc'
+
+ 'test page' | :markdown | 'new dir/new test page' | :markdown | 'new-dir/new-test-page.md'
+ 'test page' | :markdown | 'new dir/test page' | :markdown | 'new-dir/test-page.md'
- expect(page.raw_content).to eq('some other content')
+ 'test dir/test page' | :markdown | 'new dir/new test page' | :markdown | 'new-dir/new-test-page.md'
+ 'test dir/test page' | :markdown | 'test dir/test page' | :markdown | 'test-dir/test-page.md'
+ 'test dir/test page' | :markdown | 'test dir/test page' | :asciidoc | 'test-dir/test-page.asciidoc'
+
+ 'test dir/test page' | :markdown | 'new test page' | :markdown | 'new-test-page.md'
+ 'test dir/test page' | :markdown | 'test page' | :markdown | 'test-page.md'
+
+ 'test page' | :markdown | nil | :markdown | 'test-page.md'
+ 'test.page' | :markdown | nil | :markdown | 'test.page.md'
+ end
end
- it 'sets the correct commit message' do
- update_page
- page = subject.find_page('update-page')
+ # There are two bugs in Gollum. THe first one is when the title and the format are updated
+ # at the same time https://gitlab.com/gitlab-org/gitlab/-/issues/243519.
+ # The second one is when the wiki page is within a dir and the `title` argument
+ # we pass to the update method is `nil`. Gollum will remove the dir and move the page.
+ #
+ # We can include this context into the former once it is fixed
+ # or when Gollum is removed since the Gitaly approach already fixes it.
+ shared_context 'extended examples' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:original_title, :original_format, :updated_title, :updated_format, :expected_path) do
+ 'test page' | :markdown | 'new test page' | :asciidoc | 'new-test-page.asciidoc'
+ 'test page' | :markdown | 'new dir/new test page' | :asciidoc | 'new-dir/new-test-page.asciidoc'
+ 'test dir/test page' | :markdown | 'new dir/new test page' | :asciidoc | 'new-dir/new-test-page.asciidoc'
+ 'test dir/test page' | :markdown | 'new test page' | :asciidoc | 'new-test-page.asciidoc'
+ 'test page' | :markdown | nil | :asciidoc | 'test-page.asciidoc'
+ 'test dir/test page' | :markdown | nil | :asciidoc | 'test-dir/test-page.asciidoc'
+ 'test dir/test page' | :markdown | nil | :markdown | 'test-dir/test-page.md'
+ 'test page' | :markdown | '' | :markdown | 'test-page.md'
+ 'test.page' | :markdown | '' | :markdown | 'test.page.md'
+ end
+ end
- expect(page.version.message).to eq('updated page')
+ it_behaves_like 'update_page tests' do
+ include_context 'common examples'
+ include_context 'extended examples'
end
- it 'sets the correct commit email' do
- update_page
+ context 'when format is invalid' do
+ let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') }
- expect(user.commit_email).not_to eq(user.email)
- expect(commit.author_email).to eq(user.commit_email)
- expect(commit.committer_email).to eq(user.commit_email)
+ it 'returns false and sets error message' do
+ expect(subject.update_page(page.page, content: 'new content', format: :foobar)).to eq false
+ expect(subject.error_message).to match(/Invalid format selected/)
+ end
end
- it 'runs after_wiki_activity callbacks' do
- page
+ context 'when format is not allowed' do
+ let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') }
- expect(subject).to receive(:after_wiki_activity)
+ it 'returns false and sets error message' do
+ expect(subject.update_page(page.page, content: 'new content', format: :creole)).to eq false
+ expect(subject.error_message).to match(/Invalid format selected/)
+ end
+ end
+
+ context 'when page path does not have a default extension' do
+ let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') }
+
+ context 'when format is not different' do
+ it 'does not change the default extension' do
+ path = 'test-page.markdown'
+ page.page.instance_variable_set(:@path, path)
- update_page
+ expect(subject.repository).to receive(:update_file).with(user, path, anything, anything)
+
+ subject.update_page(page.page, content: 'new content', format: :markdown)
+ end
+ end
+ end
+
+ context 'when feature flag :gitaly_replace_wiki_update_page is disabled' do
+ before do
+ stub_feature_flags(gitaly_replace_wiki_update_page: false)
+ end
+
+ it_behaves_like 'update_page tests' do
+ include_context 'common examples'
+ end
end
end
diff --git a/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb b/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb
index 58822f4309b..991d6289373 100644
--- a/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb
+++ b/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb
@@ -107,10 +107,4 @@ RSpec.shared_examples 'model with wiki policies' do
expect_disallowed(*disallowed_permissions)
end
end
-
- # TODO: Remove this helper once we implement group features
- # https://gitlab.com/gitlab-org/gitlab/-/issues/208412
- def set_access_level(access_level)
- raise NotImplementedError
- end
end
diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
index 9f4fdcf7ba1..dc2c4f890b1 100644
--- a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
@@ -163,11 +163,11 @@ RSpec.shared_examples 'rejects Composer access with unknown project id' do
let(:project) { double(id: non_existing_record_id) }
context 'as anonymous' do
- it_behaves_like 'process Composer api request', :anonymous, :not_found
+ it_behaves_like 'process Composer api request', :anonymous, :unauthorized
end
context 'as authenticated user' do
- subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
+ subject { get api(url), params: params, headers: basic_auth_header(user.username, personal_access_token.token) }
it_behaves_like 'process Composer api request', :anonymous, :not_found
end
diff --git a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb
index e1e75be2494..c1eccafa987 100644
--- a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb
@@ -116,3 +116,93 @@ RSpec.shared_examples 'not hitting graphql network errors with the container reg
expect_graphql_errors_to_be_empty
end
end
+
+RSpec.shared_examples 'reconciling migration_state' do
+ shared_examples 'enforcing states coherence to' do |expected_migration_state|
+ it 'leaves the repository in the expected migration_state' do
+ expect(repository.gitlab_api_client).not_to receive(:pre_import_repository)
+ expect(repository.gitlab_api_client).not_to receive(:import_repository)
+
+ subject
+
+ expect(repository.reload.migration_state).to eq(expected_migration_state)
+ end
+ end
+
+ shared_examples 'retrying the pre_import' do
+ it 'retries the pre_import' do
+ expect(repository).to receive(:migration_pre_import).and_return(:ok)
+
+ expect { subject }.to change { repository.reload.migration_state }.to('pre_importing')
+ end
+ end
+
+ shared_examples 'retrying the import' do
+ it 'retries the import' do
+ expect(repository).to receive(:migration_import).and_return(:ok)
+
+ expect { subject }.to change { repository.reload.migration_state }.to('importing')
+ end
+ end
+
+ context 'native response' do
+ let(:status) { 'native' }
+
+ it 'finishes the import' do
+ expect { subject }
+ .to change { repository.reload.migration_state }.to('import_done')
+ .and change { repository.reload.migration_skipped_reason }.to('native_import')
+ end
+ end
+
+ context 'import_in_progress response' do
+ let(:status) { 'import_in_progress' }
+
+ it_behaves_like 'enforcing states coherence to', 'importing'
+ end
+
+ context 'import_complete response' do
+ let(:status) { 'import_complete' }
+
+ it 'finishes the import' do
+ expect { subject }.to change { repository.reload.migration_state }.to('import_done')
+ end
+ end
+
+ context 'import_failed response' do
+ let(:status) { 'import_failed' }
+
+ it_behaves_like 'retrying the import'
+ end
+
+ context 'pre_import_in_progress response' do
+ let(:status) { 'pre_import_in_progress' }
+
+ it_behaves_like 'enforcing states coherence to', 'pre_importing'
+ end
+
+ context 'pre_import_complete response' do
+ let(:status) { 'pre_import_complete' }
+
+ it 'finishes the pre_import and starts the import' do
+ expect(repository).to receive(:finish_pre_import).and_call_original
+ expect(repository).to receive(:migration_import).and_return(:ok)
+
+ expect { subject }.to change { repository.reload.migration_state }.to('importing')
+ end
+ end
+
+ context 'pre_import_failed response' do
+ let(:status) { 'pre_import_failed' }
+
+ it_behaves_like 'retrying the pre_import'
+ end
+
+ %w[pre_import_canceled import_canceled].each do |canceled_status|
+ context "#{canceled_status} response" do
+ let(:status) { canceled_status }
+
+ it_behaves_like 'enforcing states coherence to', 'import_skipped'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
index 01ed6c26576..da9d254039b 100644
--- a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
@@ -54,11 +54,13 @@ RSpec.shared_examples 'group and project boards query' do
end
context 'when using default sorting' do
+ # rubocop:disable RSpec/VariableName
let!(:board_B) { create(:board, resource_parent: board_parent, name: 'B') }
let!(:board_C) { create(:board, resource_parent: board_parent, name: 'C') }
let!(:board_a) { create(:board, resource_parent: board_parent, name: 'a') }
let!(:board_A) { create(:board, resource_parent: board_parent, name: 'A') }
let(:boards) { [board_a, board_A, board_B, board_C] }
+ # rubocop:enable RSpec/VariableName
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
diff --git a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
index c5e5803c0a7..673d7741017 100644
--- a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
+++ b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
@@ -28,34 +28,4 @@ RSpec.shared_examples 'issuable participants endpoint' do
expect(response).to have_gitlab_http_status(:not_found)
end
-
- context 'with a confidential note' do
- let!(:note) do
- create(
- :note,
- :confidential,
- project: project,
- noteable: entity,
- author: create(:user)
- )
- end
-
- it 'returns a full list of participants' do
- get api("/projects/#{project.id}/#{area}/#{entity.iid}/participants", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- participant_ids = json_response.map { |el| el['id'] }
- expect(participant_ids).to match_array([entity.author_id, note.author_id])
- end
-
- context 'when user cannot see a confidential note' do
- it 'returns a limited list of participants' do
- get api("/projects/#{project.id}/#{area}/#{entity.iid}/participants", create(:user))
-
- expect(response).to have_gitlab_http_status(:ok)
- participant_ids = json_response.map { |el| el['id'] }
- expect(participant_ids).to match_array([entity.author_id])
- end
- end
- end
end
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index 2a157f6e855..e7e30665b08 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -142,15 +142,6 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
expect(json_response['author']['username']).to eq(user.username)
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: { body: 'hi!', 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['author']['username']).to eq(user.username)
- end
-
it "returns a 400 bad request error if body not given" do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user)
@@ -306,52 +297,31 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end
describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do
- let(:params) { { body: 'Hello!', confidential: false } }
+ let(:params) { { body: 'Hello!' } }
subject do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user), params: params
end
- context 'when eveything is ok' do
- before do
- note.update!(confidential: true)
- end
+ context 'when only body param is present' do
+ let(:params) { { body: 'Hello!' } }
- context 'with multiple params present' do
- before do
- subject
- end
-
- it 'returns modified note' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['body']).to eq('Hello!')
- expect(json_response['confidential']).to be_falsey
- end
-
- it 'updates the note' do
- expect(note.reload.note).to eq('Hello!')
- expect(note.confidential).to be_falsey
- end
- end
-
- context 'when only body param is present' do
- let(:params) { { body: 'Hello!' } }
-
- it 'updates only the note text' do
- expect { subject }.not_to change { note.reload.confidential }
+ it 'updates the note text' do
+ subject
- expect(note.note).to eq('Hello!')
- end
+ expect(note.reload.note).to eq('Hello!')
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['body']).to eq('Hello!')
end
+ end
- context 'when only confidential param is present' do
- let(:params) { { confidential: false } }
+ context 'when confidential param is present' do
+ let(:params) { { confidential: true } }
- it 'updates only the note text' do
- expect { subject }.not_to change { note.reload.note }
+ it 'does not allow to change confidentiality' do
+ expect { subject }.not_to change { note.reload.note }
- expect(note.confidential).to be_falsey
- end
+ expect(response).to have_gitlab_http_status(:bad_request)
end
end
@@ -393,3 +363,24 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end
end
end
+
+RSpec.shared_examples 'noteable API with confidential notes' do |parent_type, noteable_type, id_name|
+ it_behaves_like 'noteable API', parent_type, noteable_type, id_name
+
+ describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do
+ let(:params) { { body: 'hi!' } }
+
+ subject do
+ 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)
+
+ 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)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
index 87a33060435..fcd52cdf7fa 100644
--- a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
+++ b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb
@@ -1,8 +1,5 @@
# frozen_string_literal: true
RSpec.shared_examples 'avoid N+1 on environments serialization' do |ee: false|
- # Investigating in https://gitlab.com/gitlab-org/gitlab/-/issues/353209
- let(:query_threshold) { 1 + (ee ? 4 : 0) }
-
it 'avoids N+1 database queries with grouping', :request_store do
create_environment_with_associations(project)
@@ -11,9 +8,11 @@ RSpec.shared_examples 'avoid N+1 on environments serialization' do |ee: false|
create_environment_with_associations(project)
create_environment_with_associations(project)
- expect { serialize(grouping: true) }
- .not_to exceed_query_limit(control.count)
- .with_threshold(query_threshold)
+ # Fix N+1 queries introduced by multi stop_actions for environment.
+ # Tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/358780
+ relax_count = 14
+
+ expect { serialize(grouping: true) }.not_to exceed_query_limit(control.count + relax_count)
end
it 'avoids N+1 database queries without grouping', :request_store do
@@ -24,9 +23,11 @@ RSpec.shared_examples 'avoid N+1 on environments serialization' do |ee: false|
create_environment_with_associations(project)
create_environment_with_associations(project)
- expect { serialize(grouping: false) }
- .not_to exceed_query_limit(control.count)
- .with_threshold(query_threshold)
+ # Fix N+1 queries introduced by multi stop_actions for environment.
+ # Tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/358780
+ relax_count = 14
+
+ expect { serialize(grouping: false) }.not_to exceed_query_limit(control.count + relax_count)
end
it 'does not preload for environments that does not exist in the page', :request_store do
diff --git a/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb b/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb
index 0e2bddc19ab..fd832d4484d 100644
--- a/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb
@@ -13,10 +13,12 @@ RSpec.shared_examples 'boards list service' do
end
RSpec.shared_examples 'multiple boards list service' do
+ # rubocop:disable RSpec/VariableName
let(:service) { described_class.new(parent, double) }
let!(:board_B) { create(:board, resource_parent: parent, name: 'B-board') }
let!(:board_c) { create(:board, resource_parent: parent, name: 'c-board') }
let!(:board_a) { create(:board, resource_parent: parent, name: 'a-board') }
+ # rubocop:enable RSpec/VariableName
describe '#execute' do
it 'returns all issue boards' do
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
index a780952d51b..7677e5d8cb2 100644
--- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -154,6 +154,30 @@ RSpec.shared_examples 'logs an auth warning' do |requested_actions|
end
end
+RSpec.shared_examples 'allowed to delete container repository images' do
+ let(:authentication_abilities) do
+ [:admin_container_image]
+ end
+
+ it_behaves_like 'a valid token'
+
+ context 'allow to delete images' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:*"] }
+ end
+
+ it_behaves_like 'a deletable'
+ end
+
+ context 'allow to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'a deletable since registry 2.7'
+ end
+end
+
RSpec.shared_examples 'a container registry auth service' do
include_context 'container registry auth service context'
@@ -204,6 +228,46 @@ RSpec.shared_examples 'a container registry auth service' do
it_behaves_like 'not a container repository factory'
end
+ describe '.pull_nested_repositories_access_token' do
+ let_it_be(:project) { create(:project) }
+
+ let(:token) { described_class.pull_nested_repositories_access_token(project.full_path) }
+ let(:access) do
+ [
+ {
+ 'type' => 'repository',
+ 'name' => project.full_path,
+ 'actions' => ['pull']
+ },
+ {
+ 'type' => 'repository',
+ 'name' => "#{project.full_path}/*",
+ 'actions' => ['pull']
+ }
+ ]
+ end
+
+ subject { { token: token } }
+
+ it 'has the correct scope' do
+ expect(payload).to include('access' => access)
+ end
+
+ it_behaves_like 'a valid token'
+ it_behaves_like 'not a container repository factory'
+
+ context 'with path ending with a slash' do
+ let(:token) { described_class.pull_nested_repositories_access_token("#{project.full_path}/") }
+
+ it 'has the correct scope' do
+ expect(payload).to include('access' => access)
+ end
+
+ it_behaves_like 'a valid token'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
+
context 'user authorization' do
let_it_be(:current_user) { create(:user) }
@@ -504,38 +568,14 @@ RSpec.shared_examples 'a container registry auth service' do
end
context 'delete authorized as maintainer' do
- let_it_be(:current_project) { create(:project) }
+ let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) }
- let(:authentication_abilities) do
- [:admin_container_image]
- end
-
before_all do
- current_project.add_maintainer(current_user)
- end
-
- it_behaves_like 'a valid token'
-
- context 'allow to delete images' do
- let(:current_params) do
- { scopes: ["repository:#{current_project.full_path}:*"] }
- end
-
- it_behaves_like 'a deletable' do
- let(:project) { current_project }
- end
+ project.add_maintainer(current_user)
end
- context 'allow to delete images since registry 2.7' do
- let(:current_params) do
- { scopes: ["repository:#{current_project.full_path}:delete"] }
- end
-
- it_behaves_like 'a deletable since registry 2.7' do
- let(:project) { current_project }
- end
- end
+ it_behaves_like 'allowed to delete container repository images'
end
context 'build authorized as user' do
diff --git a/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb b/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb
index 6146aae6b9b..9610cdd18a3 100644
--- a/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb
@@ -70,8 +70,10 @@ shared_examples 'issuable link creation' do
expect(issuable_link_class.find_by!(target: issuable3)).to have_attributes(source: issuable, link_type: 'relates_to')
end
- it 'returns success status' do
- is_expected.to eq(status: :success)
+ it 'returns success status and created links', :aggregate_failures do
+ expect(subject.keys).to match_array([:status, :created_references])
+ expect(subject[:status]).to eq(:success)
+ expect(subject[:created_references].map(&:target_id)).to match_array([issuable2.id, issuable3.id])
end
it 'creates notes' do
diff --git a/spec/support/shared_examples/views/milestone_shared_examples.rb b/spec/support/shared_examples/views/milestone_shared_examples.rb
new file mode 100644
index 00000000000..b6f4d0db0e9
--- /dev/null
+++ b/spec/support/shared_examples/views/milestone_shared_examples.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'milestone empty states' do
+ include Devise::Test::ControllerHelpers
+
+ let_it_be(:user) { build(:user) }
+ let(:empty_state) { 'Use milestones to track issues and merge requests over a fixed period of time' }
+
+ before do
+ assign(:projects, [])
+ allow(view).to receive(:current_user).and_return(user)
+ end
+
+ context 'with no milestones' do
+ before do
+ assign(:milestones, [])
+ assign(:milestone_states, { opened: 0, closed: 0, all: 0 })
+ render
+ end
+
+ it 'shows empty state' do
+ expect(rendered).to have_content(empty_state)
+ end
+
+ it 'does not show tabs or searchbar' do
+ expect(rendered).not_to have_link('Open')
+ expect(rendered).not_to have_link('Closed')
+ expect(rendered).not_to have_link('All')
+ end
+ end
+
+ context 'with no open milestones' do
+ before do
+ allow(view).to receive(:milestone_path).and_return("/milestones/1")
+ assign(:milestones, [])
+ assign(:milestone_states, { opened: 0, closed: 1, all: 1 })
+ end
+
+ it 'shows tabs and searchbar', :aggregate_failures do
+ render
+
+ expect(rendered).not_to have_content(empty_state)
+ expect(rendered).to have_link('Open')
+ expect(rendered).to have_link('Closed')
+ expect(rendered).to have_link('All')
+ end
+
+ it 'shows empty state' do
+ render
+
+ expect(rendered).to have_content('There are no open milestones')
+ end
+ end
+
+ context 'with no closed milestones' do
+ before do
+ allow(view).to receive(:milestone_path).and_return("/milestones/1")
+ allow(view).to receive(:params).and_return(state: 'closed')
+ assign(:milestones, [])
+ assign(:milestone_states, { opened: 1, closed: 0, all: 1 })
+ end
+
+ it 'shows tabs and searchbar', :aggregate_failures do
+ render
+
+ expect(rendered).not_to have_content(empty_state)
+ expect(rendered).to have_link('Open')
+ expect(rendered).to have_link('Closed')
+ expect(rendered).to have_link('All')
+ end
+
+ it 'shows empty state on closed milestones' do
+ render
+
+ expect(rendered).to have_content('There are no closed milestones')
+ 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 d202c4e00f0..26731f34ed6 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, feature_flag:|
include ExclusiveLeaseHelpers
describe 'defining the job attributes' do
@@ -39,6 +39,16 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
end
end
+ describe '.enabled?' do
+ it 'does not raise an error' do
+ expect { described_class.enabled? }.not_to raise_error
+ end
+
+ it 'returns true' do
+ expect(described_class.enabled?).to be_truthy
+ end
+ end
+
describe '#perform' do
subject(:worker) { described_class.new }
@@ -76,7 +86,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
context 'when the feature flag is disabled' do
before do
- stub_feature_flags(execute_batched_migrations_on_schedule: false)
+ stub_feature_flags(feature_flag => false)
end
it 'does nothing' do
@@ -89,7 +99,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
context 'when the feature flag is enabled' do
before do
- stub_feature_flags(execute_batched_migrations_on_schedule: true)
+ stub_feature_flags(feature_flag => true)
allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration).and_return(nil)
end
diff --git a/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb b/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb
index 202606c6aa6..4751d91efde 100644
--- a/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb
+++ b/spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb
@@ -230,76 +230,6 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
stub_feature_flags(optimized_housekeeping: false)
end
- it 'incremental repack adds a new packfile' do
- create_objects(resource)
- before_packs = packs(resource)
-
- expect(before_packs.count).to be >= 1
-
- subject.perform(resource.id, 'incremental_repack', lease_key, lease_uuid)
- after_packs = packs(resource)
-
- # Exactly one new pack should have been created
- expect(after_packs.count).to eq(before_packs.count + 1)
-
- # Previously existing packs are still around
- expect(before_packs & after_packs).to eq(before_packs)
- end
-
- it 'full repack consolidates into 1 packfile' do
- create_objects(resource)
- subject.perform(resource.id, 'incremental_repack', lease_key, lease_uuid)
- before_packs = packs(resource)
-
- expect(before_packs.count).to be >= 2
-
- subject.perform(resource.id, 'full_repack', lease_key, lease_uuid)
- after_packs = packs(resource)
-
- expect(after_packs.count).to eq(1)
-
- # Previously existing packs should be gone now
- expect(after_packs - before_packs).to eq(after_packs)
-
- expect(File.exist?(bitmap_path(after_packs.first))).to eq(bitmaps_enabled)
- end
-
- it 'gc consolidates into 1 packfile and updates packed-refs' do
- create_objects(resource)
- before_packs = packs(resource)
- before_packed_refs = packed_refs(resource)
-
- expect(before_packs.count).to be >= 1
-
- # It's quite difficult to use `expect_next_instance_of` in this place
- # because the RepositoryService is instantiated several times to do
- # some repository calls like `exists?`, `create_repository`, ... .
- # Therefore, since we're instantiating the object several times,
- # RSpec has troubles figuring out which instance is the next and which
- # one we want to mock.
- # Besides, at this point, we actually want to perform the call to Gitaly,
- # otherwise we would just use `instance_double` like in other parts of the
- # spec file.
- expect_any_instance_of(Gitlab::GitalyClient::RepositoryService) # rubocop:disable RSpec/AnyInstanceOf
- .to receive(:garbage_collect)
- .with(bitmaps_enabled, prune: false)
- .and_call_original
-
- subject.perform(resource.id, 'gc', lease_key, lease_uuid)
- after_packed_refs = packed_refs(resource)
- after_packs = packs(resource)
-
- expect(after_packs.count).to eq(1)
-
- # Previously existing packs should be gone now
- expect(after_packs - before_packs).to eq(after_packs)
-
- # The packed-refs file should have been updated during 'git gc'
- expect(before_packed_refs).not_to eq(after_packed_refs)
-
- expect(File.exist?(bitmap_path(after_packs.first))).to eq(bitmaps_enabled)
- end
-
it 'cleans up repository after finishing' do
expect(resource).to receive(:cleanup).and_call_original