summaryrefslogtreecommitdiff
path: root/spec/support/shared_examples
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 15:40:28 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 15:40:28 +0000
commitb595cb0c1dec83de5bdee18284abe86614bed33b (patch)
tree8c3d4540f193c5ff98019352f554e921b3a41a72 /spec/support/shared_examples
parent2f9104a328fc8a4bddeaa4627b595166d24671d0 (diff)
downloadgitlab-ce-b595cb0c1dec83de5bdee18284abe86614bed33b.tar.gz
Add latest changes from gitlab-org/gitlab@15-2-stable-eev15.2.0-rc42
Diffstat (limited to 'spec/support/shared_examples')
-rw-r--r--spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb37
-rw-r--r--spec/support/shared_examples/csp.rb76
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb2
-rw-r--r--spec/support/shared_examples/features/inviting_members_shared_examples.rb70
-rw-r--r--spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/finders/issues_finder_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb162
-rw-r--r--spec/support/shared_examples/harbor/container_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb172
-rw-r--r--spec/support/shared_examples/harbor/tags_controller_shared_examples.rb155
-rw-r--r--spec/support/shared_examples/integrations/integration_settings_form.rb5
-rw-r--r--spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb54
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/merge_request_author_auto_assign_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb82
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/concerns/timebox_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/issuable_participants_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb124
-rw-r--r--spec/support/shared_examples/models/mentionable_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/requests/api/debian_common_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/hooks_shared_examples.rb415
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/services/alert_management_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/services/feature_flags/client_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/usage_data_counters/work_item_activity_unique_counter_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/views/themed_layout_examples.rb35
-rw-r--r--spec/support/shared_examples/work_items/widgetable_service_shared_examples.rb13
-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.rb37
51 files changed, 1719 insertions, 288 deletions
diff --git a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
new file mode 100644
index 00000000000..98fc52add51
--- /dev/null
+++ b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+#
+# Requires a context containing:
+# - subject
+# - project
+# - feature_flag_name
+# - category
+# - action
+# - namespace
+# - user
+
+shared_examples 'Snowplow event tracking' do
+ let(:label) { nil }
+
+ it 'is not emitted if FF is disabled' do
+ stub_feature_flags(feature_flag_name => false)
+
+ subject
+
+ expect_no_snowplow_event
+ end
+
+ it 'is emitted' do
+ params = {
+ category: category,
+ action: action,
+ namespace: namespace,
+ user: user,
+ project: project,
+ label: label
+ }.compact
+
+ subject
+
+ expect_snowplow_event(**params)
+ end
+end
diff --git a/spec/support/shared_examples/csp.rb b/spec/support/shared_examples/csp.rb
index 9143d0f4720..91242ae9f37 100644
--- a/spec/support/shared_examples/csp.rb
+++ b/spec/support/shared_examples/csp.rb
@@ -15,64 +15,66 @@ RSpec.shared_examples 'setting CSP' do |rule_name|
end
end
- context 'when no CSP config' do
- include_context 'csp config', nil
+ context 'csp config and feature toggle', :do_not_stub_snowplow_by_default do
+ context 'when no CSP config' do
+ include_context 'csp config', nil
- it 'does not add CSP directives' do
- is_expected.to be_blank
+ it 'does not add CSP directives' do
+ is_expected.to be_blank
+ end
end
- end
- describe "when a CSP config exists for #{rule_name}" do
- include_context 'csp config', rule_name.parameterize.underscore.to_sym
+ describe "when a CSP config exists for #{rule_name}" do
+ include_context 'csp config', rule_name.parameterize.underscore.to_sym
- context 'when feature is enabled' do
- it "appends to #{rule_name}" do
- is_expected.to eql("#{rule_name} #{default_csp_values} #{allowlisted_url}")
+ context 'when feature is enabled' do
+ it "appends to #{rule_name}" do
+ is_expected.to eql("#{rule_name} #{default_csp_values} #{allowlisted_url}")
+ end
end
- end
- context 'when feature is disabled' do
- include_context 'disable feature'
+ context 'when feature is disabled' do
+ include_context 'disable feature'
- it "keeps original #{rule_name}" do
- is_expected.to eql("#{rule_name} #{default_csp_values}")
+ it "keeps original #{rule_name}" do
+ is_expected.to eql("#{rule_name} #{default_csp_values}")
+ end
end
end
- end
- describe "when a CSP config exists for default-src but not #{rule_name}" do
- include_context 'csp config', :default_src
+ describe "when a CSP config exists for default-src but not #{rule_name}" do
+ include_context 'csp config', :default_src
- context 'when feature is enabled' do
- it "uses default-src values in #{rule_name}" do
- is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{allowlisted_url}")
+ context 'when feature is enabled' do
+ it "uses default-src values in #{rule_name}" do
+ is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{allowlisted_url}")
+ end
end
- end
- context 'when feature is disabled' do
- include_context 'disable feature'
+ context 'when feature is disabled' do
+ include_context 'disable feature'
- it "does not add #{rule_name}" do
- is_expected.to eql("default-src #{default_csp_values}")
+ it "does not add #{rule_name}" do
+ is_expected.to eql("default-src #{default_csp_values}")
+ end
end
end
- end
- describe "when a CSP config exists for font-src but not #{rule_name}" do
- include_context 'csp config', :font_src
+ describe "when a CSP config exists for font-src but not #{rule_name}" do
+ include_context 'csp config', :font_src
- context 'when feature is enabled' do
- it "uses default-src values in #{rule_name}" do
- is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{allowlisted_url}")
+ context 'when feature is enabled' do
+ it "uses default-src values in #{rule_name}" do
+ is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{allowlisted_url}")
+ end
end
- end
- context 'when feature is disabled' do
- include_context 'disable feature'
+ context 'when feature is disabled' do
+ include_context 'disable feature'
- it "does not add #{rule_name}" do
- is_expected.to eql("font-src #{default_csp_values}")
+ it "does not add #{rule_name}" do
+ is_expected.to eql("font-src #{default_csp_values}")
+ end
end
end
end
diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb
index 591f7973454..0ea82f37db0 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -31,8 +31,6 @@ RSpec.shared_examples 'edits content using the content editor' do
page.go_back
refresh
-
- click_button 'Edit rich text'
end
it 'applies theme classes to code blocks' do
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index 6c06cbf9082..24dc4bcfc59 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -293,7 +293,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
it 'can be collapsed' do
submit_reply('another text')
- find('.js-collapse-replies').click
+ click_button s_('Notes|Collapse replies'), match: :first
expect(page).to have_css('.discussion-notes .note', count: 1)
expect(page).to have_content '1 reply'
end
diff --git a/spec/support/shared_examples/features/inviting_members_shared_examples.rb b/spec/support/shared_examples/features/inviting_members_shared_examples.rb
index 58357b262f5..bca0e02fcdd 100644
--- a/spec/support/shared_examples/features/inviting_members_shared_examples.rb
+++ b/spec/support/shared_examples/features/inviting_members_shared_examples.rb
@@ -23,6 +23,22 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
)
end
+ it 'displays the user\'s avatar in the member input token', :js do
+ visit members_page_path
+
+ input_invites(user2.name)
+
+ expect(page).to have_selector(member_token_avatar_selector)
+ end
+
+ it 'does not display an avatar in the member input token for an email address', :js do
+ visit members_page_path
+
+ input_invites('test@example.com')
+
+ expect(page).not_to have_selector(member_token_avatar_selector)
+ end
+
it 'invites user by email', :js, :snowplow, :aggregate_failures do
visit members_page_path
@@ -78,22 +94,23 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
end
context 'when member is already a member by email' do
- it 'fails with an error', :js do
+ it 'updates the member for that email', :js do
+ email = 'test@example.com'
+
visit members_page_path
- invite_member('test@example.com', role: 'Developer')
+ invite_member(email, role: 'Developer')
- invite_member('test@example.com', role: 'Reporter', refresh: false)
+ invite_member(email, 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")
+ expect(page).not_to have_selector(invite_modal_selector)
page.refresh
click_link 'Invited'
- page.within find_invited_member_row('test@example.com') do
- expect(page).to have_button('Developer')
+ page.within find_invited_member_row(email) do
+ expect(page).to have_button('Reporter')
end
end
end
@@ -131,8 +148,8 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
invite_member(user2.name, role: role, refresh: false)
expect(page).to have_selector(invite_modal_selector)
- expect(page).to have_content "Access level should be greater than or equal to Developer inherited membership " \
- "from group #{group.name}"
+ expect(page).to have_content "#{user2.name}: Access level should be greater than or equal to Developer " \
+ "inherited membership from group #{group.name}"
page.refresh
@@ -149,13 +166,31 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
group.add_maintainer(user3)
end
- it 'only shows the first user error', :js do
+ it 'shows the user errors and then removes them from the form', :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)
+ expect(page).to have_selector(member_token_error_selector(user2.id))
+ expect(page).to have_selector(member_token_error_selector(user3.id))
+ expect(page).to have_text("The following 2 members couldn't be invited")
+ expect(page).to have_text("#{user2.name}: Access level should be greater than or equal to")
+ expect(page).to have_text("#{user3.name}: Access level should be greater than or equal to")
+
+ remove_token(user2.id)
+
+ expect(page).not_to have_selector(member_token_error_selector(user2.id))
+ expect(page).to have_selector(member_token_error_selector(user3.id))
+ expect(page).to have_text("The following member couldn't be invited")
+ expect(page).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
+
+ remove_token(user3.id)
+
+ expect(page).not_to have_selector(member_token_error_selector(user3.id))
+ expect(page).not_to have_text("The following member couldn't be invited")
+ expect(page).not_to have_text("Review the invite errors and try again")
+ expect(page).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
page.refresh
@@ -169,6 +204,19 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
expect(page).not_to have_button('Maintainer')
end
end
+
+ it 'only shows the error for an invalid formatted email and does not display other member errors', :js do
+ visit subentity_members_page_path
+
+ invite_member([user2.name, user3.name, 'bad@email'], role: role, refresh: false)
+
+ expect(page).to have_selector(invite_modal_selector)
+ expect(page).to have_text('email contains an invalid email address')
+ expect(page).not_to have_text("The following 2 members couldn't be invited")
+ expect(page).not_to have_text("Review the invite errors and try again")
+ expect(page).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
+ expect(page).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
+ end
end
end
end
diff --git a/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb b/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb
index 4565108b5e4..9d023d9514a 100644
--- a/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb
+++ b/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb
@@ -4,7 +4,7 @@ RSpec.shared_examples 'multiple assignees merge request' do |action, save_button
it "#{action} a MR with multiple assignees", :js do
find('.js-assignee-search').click
page.within '.dropdown-menu-user' do
- click_link user.name unless action == 'creates'
+ click_link user.name
click_link user2.name
end
diff --git a/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb b/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
index a44a699c878..bbde448a1a1 100644
--- a/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
+++ b/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
@@ -4,7 +4,7 @@ RSpec.shared_examples 'multiple assignees widget merge request' do |action, save
it "#{action} a MR with multiple assignees", :js do
find('.js-assignee-search').click
page.within '.dropdown-menu-user' do
- click_link user.name unless action == 'creates'
+ click_link user.name
click_link user2.name
end
diff --git a/spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb b/spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb
new file mode 100644
index 00000000000..79de2aedf3b
--- /dev/null
+++ b/spec/support/shared_examples/features/wiki/autocomplete_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'autocompletes items' do
+ before do
+ if defined?(project)
+ create(:issue, project: project, title: 'My Cool Linked Issue')
+ create(:merge_request, source_project: project, title: 'My Cool Merge Request')
+ create(:label, project: project, title: 'My Cool Label')
+ create(:milestone, project: project, title: 'My Cool Milestone')
+
+ project.add_maintainer(create(:user, name: 'JohnDoe123'))
+ else # group wikis
+ project = create(:project, group: group)
+
+ create(:issue, project: project, title: 'My Cool Linked Issue')
+ create(:merge_request, source_project: project, title: 'My Cool Merge Request')
+ create(:group_label, group: group, title: 'My Cool Label')
+ create(:milestone, group: group, title: 'My Cool Milestone')
+
+ project.add_maintainer(create(:user, name: 'JohnDoe123'))
+ end
+ end
+
+ it 'works well for issues, labels, MRs, members, etc' do
+ fill_in :wiki_content, with: "#"
+ expect(page).to have_text 'My Cool Linked Issue'
+
+ fill_in :wiki_content, with: "~"
+ expect(page).to have_text 'My Cool Label'
+
+ fill_in :wiki_content, with: "!"
+ expect(page).to have_text 'My Cool Merge Request'
+
+ fill_in :wiki_content, with: "%"
+ expect(page).to have_text 'My Cool Milestone'
+
+ fill_in :wiki_content, with: "@"
+ expect(page).to have_text 'JohnDoe123'
+
+ fill_in :wiki_content, with: ':smil'
+ expect(page).to have_text 'smile_cat'
+ end
+end
diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
index 12a4c6d7583..79c7c1891ac 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
@@ -146,6 +146,8 @@ RSpec.shared_examples 'User updates wiki page' do
it_behaves_like 'edits content using the content editor'
end
end
+
+ it_behaves_like 'autocompletes items'
end
context 'when the page is in a subdir', :js do
diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
index 622a88e8323..9d8f37a3e64 100644
--- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
+++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
@@ -269,6 +269,17 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns items not assigned to that milestone' do
expect(items).to contain_exactly(item2, item3, item4, item5)
end
+
+ context 'with multiple milestones' do
+ let(:milestone2) { create(:milestone, project: project2) }
+ let(:params) { { not: { milestone_title: [milestone.title, milestone2.title] } } }
+
+ it 'returns items not assigned to both milestones' do
+ item2.update!(milestone: milestone2)
+
+ expect(items).to contain_exactly(item3, item4, item5)
+ end
+ end
end
context 'filtering by group milestone' do
@@ -962,7 +973,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
group = create(:group)
project = create(:project, group: group)
item = create(factory, project: project)
- group.add_user(user, :owner)
+ group.add_member(user, :owner)
expect(items).to include(item)
end
diff --git a/spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb
new file mode 100644
index 00000000000..56c2ca22e15
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/work_items/update_description_widget_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'update work item description widget' do
+ it 'updates the description widget' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to change(work_item, :description).from(nil).to(new_description)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['workItem']['widgets']).to include(
+ {
+ 'description' => new_description,
+ 'type' => 'DESCRIPTION'
+ }
+ )
+ end
+
+ context 'when the updated work item is not valid' do
+ it 'returns validation errors without the work item' do
+ errors = ActiveModel::Errors.new(work_item).tap { |e| e.add(:description, 'error message') }
+
+ allow_next_found_instance_of(::WorkItem) do |instance|
+ allow(instance).to receive(:valid?).and_return(false)
+ allow(instance).to receive(:errors).and_return(errors)
+ end
+
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['workItem']).to be_nil
+ expect(mutation_response['errors']).to match_array(['Description error message'])
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb
new file mode 100644
index 00000000000..3c32b7e0310
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'update work item weight widget' do
+ it 'updates the weight widget' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to change(work_item, :weight).from(nil).to(new_weight)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['workItem']['widgets']).to include(
+ {
+ 'weight' => new_weight,
+ 'type' => 'WEIGHT'
+ }
+ )
+ end
+
+ context 'when the updated work item is not valid' do
+ it 'returns validation errors without the work item' do
+ errors = ActiveModel::Errors.new(work_item).tap { |e| e.add(:weight, 'error message') }
+
+ allow_next_found_instance_of(::WorkItem) do |instance|
+ allow(instance).to receive(:valid?).and_return(false)
+ allow(instance).to receive(:errors).and_return(errors)
+ end
+
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['workItem']).to be_nil
+ expect(mutation_response['errors']).to match_array(['Weight error message'])
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
index 6d6e7b761f6..59927fa1cc9 100644
--- a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
@@ -44,19 +44,25 @@
# end
# end
#
+
+# Include this context if your field does not accept a sort argument
+RSpec.shared_context 'no sort argument' do
+ let(:sort_argument) { graphql_args }
+end
+
RSpec.shared_examples 'sorted paginated query' do |conditions = {}|
# Provided as a convenience when constructing queries using string concatenation
let(:page_info) { 'pageInfo { startCursor endCursor }' }
# Convenience for using default implementation of pagination_results_data
let(:node_path) { ['id'] }
+ let(:sort_argument) { graphql_args(sort: sort_param) }
it_behaves_like 'requires variables' do
- let(:required_variables) { [:sort_param, :first_param, :all_records, :data_path, :current_user] }
+ let(:required_variables) { [:first_param, :all_records, :data_path, :current_user] }
end
describe do
- let(:sort_argument) { graphql_args(sort: sort_param) }
- let(:params) { sort_argument }
+ let(:params) { sort_argument }
# Convenience helper for the large number of queries defined as a projection
# from some root value indexed by full_path to a collection of objects with IID
diff --git a/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb b/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb
new file mode 100644
index 00000000000..85fcd426e3d
--- /dev/null
+++ b/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb
@@ -0,0 +1,162 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a harbor artifacts controller' do |args|
+ include HarborHelper
+ let_it_be(:user) { create(:user) }
+ let_it_be(:unauthorized_user) { create(:user) }
+ let_it_be(:json_header) { { accept: 'application/json' } }
+
+ let(:mock_artifacts) do
+ [
+ {
+ "digest": "sha256:661e8e44e5d7290fbd42d0495ab4ff6fdf1ad251a9f358969b3264a22107c14d",
+ "icon": "sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06",
+ "id": 1,
+ "project_id": 1,
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-04-23T08:04:08.901Z",
+ "repository_id": 1,
+ "size": 126745886,
+ "tags": [
+ {
+ "artifact_id": 1,
+ "id": 1,
+ "immutable": false,
+ "name": "2",
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-04-23T08:04:08.920Z",
+ "repository_id": 1,
+ "signed": false
+ }
+ ],
+ "type": "IMAGE"
+ }
+ ]
+ end
+
+ let(:repository_id) { 'test' }
+
+ shared_examples 'responds with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'responds with 200 status with json' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).not_to render_template(:index)
+ end
+ end
+
+ shared_examples 'responds with 302 status' do
+ it 'returns 302' do
+ subject
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ shared_examples 'responds with 422 status with json' do
+ it 'returns 422' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ before do
+ stub_request(:get,
+ "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts"\
+ "?page=1&page_size=10&with_tag=true")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ }).to_return(status: 200, body: mock_artifacts.to_json, headers: { "x-total-count": 2 })
+ container.add_reporter(user)
+ sign_in(user)
+ end
+
+ describe 'GET #index.json' do
+ subject do
+ get harbor_artifact_url(container, repository_id), headers: json_header
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it_behaves_like "responds with #{args[:anonymous_status_code]} status"
+ end
+
+ context 'with unauthorized user' do
+ before do
+ sign_in(unauthorized_user)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with valid params' do
+ context 'with valid repository' do
+ subject do
+ get harbor_artifact_url(container, repository_id), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with valid page' do
+ subject do
+ get harbor_artifact_url(container, repository_id, page: '1'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with valid limit' do
+ subject do
+ get harbor_artifact_url(container, repository_id, limit: '10'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+ end
+
+ context 'with invalid params' do
+ context 'with invalid page' do
+ subject do
+ get harbor_artifact_url(container, repository_id, page: 'aaa'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+
+ context 'with invalid limit' do
+ subject do
+ get harbor_artifact_url(container, repository_id, limit: 'aaa'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/harbor/container_shared_examples.rb b/spec/support/shared_examples/harbor/container_shared_examples.rb
new file mode 100644
index 00000000000..57274e0b457
--- /dev/null
+++ b/spec/support/shared_examples/harbor/container_shared_examples.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'raises NotImplementedError when calling #container' do
+ describe '#container' do
+ it 'raises NotImplementedError' do
+ expect { controller.send(:container) }.to raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb b/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb
new file mode 100644
index 00000000000..b35595a10b2
--- /dev/null
+++ b/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb
@@ -0,0 +1,172 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a harbor repositories controller' do |args|
+ include HarborHelper
+ let_it_be(:user) { create(:user) }
+ let_it_be(:unauthorized_user) { create(:user) }
+ let_it_be(:json_header) { { accept: 'application/json' } }
+
+ let(:mock_repositories) do
+ [
+ {
+ "artifact_count": 6,
+ "creation_time": "2022-04-24T10:59:02.719Z",
+ "id": 33,
+ "name": "test/photon",
+ "project_id": 3,
+ "pull_count": 12,
+ "update_time": "2022-04-24T11:06:27.678Z"
+ },
+ {
+ "artifact_count": 1,
+ "creation_time": "2022-04-23T08:04:08.880Z",
+ "id": 1,
+ "name": "test/gemnasium",
+ "project_id": 3,
+ "pull_count": 0,
+ "update_time": "2022-04-23T08:04:08.880Z"
+ }
+ ]
+ end
+
+ shared_examples 'responds with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'responds with 200 status with html' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ end
+ end
+
+ shared_examples 'responds with 302 status' do
+ it 'returns 302' do
+ subject
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ shared_examples 'responds with 200 status with json' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).not_to render_template(:index)
+ end
+ end
+
+ shared_examples 'responds with 422 status with json' do
+ it 'returns 422' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories?page=1&page_size=10")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ }).to_return(status: 200, body: mock_repositories.to_json, headers: { "x-total-count": 2 })
+ container.add_reporter(user)
+ sign_in(user)
+ end
+
+ describe 'GET #index.html' do
+ subject do
+ get harbor_repository_url(container)
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status with html'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it_behaves_like "responds with #{args[:anonymous_status_code]} status"
+ end
+
+ context 'with unauthorized user' do
+ before do
+ sign_in(unauthorized_user)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+ end
+
+ describe 'GET #index.json' do
+ subject do
+ get harbor_repository_url(container), headers: json_header
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with valid params' do
+ context 'with valid page params' do
+ subject do
+ get harbor_repository_url(container, page: '1'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with valid limit params' do
+ subject do
+ get harbor_repository_url(container, limit: '10'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+ end
+
+ context 'with invalid params' do
+ context 'with invalid page params' do
+ subject do
+ get harbor_repository_url(container, page: 'aaa'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+
+ context 'with invalid limit params' do
+ subject do
+ get harbor_repository_url(container, limit: 'aaa'), headers: json_header
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb b/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb
new file mode 100644
index 00000000000..46fea7fdff6
--- /dev/null
+++ b/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a harbor tags controller' do |args|
+ include HarborHelper
+ let_it_be(:user) { create(:user) }
+ let_it_be(:unauthorized_user) { create(:user) }
+ let_it_be(:json_header) { { accept: 'application/json' } }
+
+ let(:mock_artifacts) do
+ [
+ {
+ "artifact_id": 1,
+ "id": 1,
+ "immutable": false,
+ "name": "2",
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-04-23T08:04:08.920Z",
+ "repository_id": 1,
+ "signed": false
+ }
+ ]
+ end
+
+ let(:repository_id) { 'test' }
+ let(:artifact_id) { '1' }
+
+ shared_examples 'responds with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'responds with 200 status with json' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).not_to render_template(:index)
+ end
+ end
+
+ shared_examples 'responds with 302 status' do
+ it 'returns 302' do
+ subject
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ shared_examples 'responds with 422 status with json' do
+ it 'returns 422' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ before do
+ stub_request(:get,
+ "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts/1/tags"\
+ "?page=1&page_size=10")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ }).to_return(status: 200, body: mock_artifacts.to_json, headers: { "x-total-count": 2 })
+ container.add_reporter(user)
+ sign_in(user)
+ end
+
+ describe 'GET #index.json' do
+ subject do
+ get(harbor_tag_url(container, repository_id, artifact_id),
+ headers: json_header)
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it_behaves_like "responds with #{args[:anonymous_status_code]} status"
+ end
+
+ context 'with unauthorized user' do
+ before do
+ sign_in(unauthorized_user)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+
+ context 'with valid params' do
+ context 'with valid repository' do
+ subject do
+ get harbor_tag_url(container, repository_id, artifact_id), headers: json_header
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with valid page' do
+ subject do
+ get(harbor_tag_url(container, repository_id, artifact_id, page: '1'),
+ headers: json_header)
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+
+ context 'with valid limit' do
+ subject do
+ get(harbor_tag_url(container, repository_id, artifact_id, limit: '10'),
+ headers: json_header)
+ end
+
+ it_behaves_like 'responds with 200 status with json'
+ end
+ end
+
+ context 'with invalid params' do
+ context 'with invalid page' do
+ subject do
+ get(harbor_tag_url(container, repository_id, artifact_id, page: 'aaa'),
+ headers: json_header)
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+
+ context 'with invalid limit' do
+ subject do
+ get(harbor_tag_url(container, repository_id, artifact_id, limit: 'aaa'),
+ headers: json_header)
+ end
+
+ it_behaves_like 'responds with 422 status with json'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/integrations/integration_settings_form.rb b/spec/support/shared_examples/integrations/integration_settings_form.rb
index dfe5a071f91..5041ac4a660 100644
--- a/spec/support/shared_examples/integrations/integration_settings_form.rb
+++ b/spec/support/shared_examples/integrations/integration_settings_form.rb
@@ -20,6 +20,11 @@ RSpec.shared_examples 'integration settings form' do
"#{integration.title} field #{field_name} not present"
end
+ api_only_fields = integration.fields.select { _1[:api_only] }
+ api_only_fields.each do |field|
+ expect(page).not_to have_field("service[#{field.name}]", wait: 0)
+ end
+
sections = integration.sections
events = parse_json(trigger_events_for_integration(integration))
diff --git a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
index 284c129221b..b786d7e5527 100644
--- a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
@@ -265,10 +265,9 @@ RSpec.shared_examples 'common trace features' do
end
context 'build token' do
- let(:token) { 'my_secret_token' }
+ let(:token) { build.token }
before do
- build.update!(token: token)
trace.append(token, 0)
end
diff --git a/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb
index 326800e6dc2..c9300aff3e6 100644
--- a/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb
@@ -32,21 +32,7 @@ RSpec.shared_examples "position formatter" do
subject { formatter.to_h }
- context 'when file_identifier_hash is disabled' do
- before do
- stub_feature_flags(file_identifier_hash: false)
- end
-
- it { is_expected.to eq(formatter_hash.except(:file_identifier_hash)) }
- end
-
- context 'when file_identifier_hash is enabled' do
- before do
- stub_feature_flags(file_identifier_hash: true)
- end
-
- it { is_expected.to eq(formatter_hash) }
- end
+ it { is_expected.to eq(formatter_hash) }
end
describe '#==' do
diff --git a/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb
new file mode 100644
index 00000000000..a3e4379f4d3
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'search results filtered by language' do
+ let(:scope) { 'blobs' }
+ let(:filters) { { language: %w[Ruby Markdown] } }
+ let(:query) { 'def | popen | test' }
+
+ before do
+ project.repository.index_commits_and_blobs
+
+ ensure_elasticsearch_index!
+ end
+
+ subject(:blob_results) { results.objects('blobs') }
+
+ it 'filters by language', :sidekiq_inline, :aggregate_failures do
+ expected_paths = %w[
+ files/ruby/popen.rb
+ files/markdown/ruby-style-guide.md
+ files/ruby/regex.rb
+ files/ruby/version_info.rb
+ CONTRIBUTING.md
+ ]
+
+ paths = blob_results.map { |blob| blob.binary_path }
+ expect(blob_results.size).to eq(5)
+ expect(paths).to match_array(expected_paths)
+ end
+
+ context 'when the search_blobs_language_aggregation feature flag is disabled' do
+ before do
+ stub_feature_flags(search_blobs_language_aggregation: false)
+ end
+
+ it 'does not filter by language', :sidekiq_inline, :aggregate_failures do
+ expected_paths = %w[
+ CHANGELOG
+ CONTRIBUTING.md
+ bar/branch-test.txt
+ custom-highlighting/test.gitlab-custom
+ files/ruby/popen.rb
+ files/ruby/regex.rb
+ files/ruby/version_info.rb
+ files/whitespace
+ encoding/test.txt
+ files/markdown/ruby-style-guide.md
+ ]
+
+ paths = blob_results.map { |blob| blob.binary_path }
+ expect(blob_results.size).to eq(10)
+ expect(paths).to match_array(expected_paths)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
index b5d93aec1bf..9d280d9404a 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
@@ -32,3 +32,45 @@ RSpec.shared_examples 'does not track when feature flag is disabled' do |feature
end
end
end
+
+RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events' do
+ before do
+ stub_application_setting(usage_ping_enabled: true)
+ end
+
+ def count_unique(date_from: 1.minute.ago, date_to: 1.minute.from_now)
+ Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to)
+ end
+
+ specify do
+ aggregate_failures do
+ expect(track_action(author: user1, project: project)).to be_truthy
+ expect(track_action(author: user1, project: project)).to be_truthy
+ expect(track_action(author: user2, project: project)).to be_truthy
+ expect(count_unique).to eq(2)
+ end
+ end
+
+ it 'does not track edit actions if author is not present' do
+ expect(track_action(author: nil, project: project)).to be_nil
+ end
+
+ it 'emits snowplow event' do
+ track_action(author: user1, project: project)
+
+ expect_snowplow_event(category: 'issues_edit', action: action, user: user1,
+ namespace: project.namespace, project: project)
+ end
+
+ context 'with route_hll_to_snowplow_phase2 disabled' do
+ before do
+ stub_feature_flags(route_hll_to_snowplow_phase2: false)
+ end
+
+ it 'does not emit snowplow event' do
+ track_action(author: user1, project: project)
+
+ expect_no_snowplow_event
+ end
+ end
+end
diff --git a/spec/support/shared_examples/merge_request_author_auto_assign_shared_examples.rb b/spec/support/shared_examples/merge_request_author_auto_assign_shared_examples.rb
deleted file mode 100644
index d4986975f03..00000000000
--- a/spec/support/shared_examples/merge_request_author_auto_assign_shared_examples.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'merge request author auto assign' do
- it 'populates merge request author as assignee' do
- expect(find('.js-assignee-search')).to have_content(user.name)
- expect(page).not_to have_content 'Assign yourself'
- end
-end
diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
index fa10b03fa90..d189e91effd 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -357,7 +357,8 @@ RSpec.shared_examples "chat integration" do |integration_name|
end
context 'deployment events' do
- let(:sample_data) { Gitlab::DataBuilder::Deployment.build(create(:deployment), Time.now) }
+ let(:deployment) { create(:deployment) }
+ let(:sample_data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.now) }
it_behaves_like "untriggered #{integration_name} integration"
end
diff --git a/spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb b/spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb
deleted file mode 100644
index 744262d79ea..00000000000
--- a/spec/support/shared_examples/models/clusters/elastic_stack_client_shared.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-# frozen_string_literal: true
-
-# Input
-# - factory: [:clusters_applications_elastic_stack, :clusters_integrations_elastic_stack]
-RSpec.shared_examples 'cluster-based #elasticsearch_client' do |factory|
- describe '#elasticsearch_client' do
- context 'cluster is nil' do
- subject { build(factory, cluster: nil) }
-
- it 'returns nil' do
- expect(subject.cluster).to be_nil
- expect(subject.elasticsearch_client).to be_nil
- end
- end
-
- context "cluster doesn't have kubeclient" do
- let(:cluster) { create(:cluster) }
-
- subject { create(factory, cluster: cluster) }
-
- it 'returns nil' do
- expect(subject.elasticsearch_client).to be_nil
- end
- end
-
- context 'cluster has kubeclient' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url }
- let(:kube_client) { subject.cluster.kubeclient.core_client }
-
- subject { create(factory, cluster: cluster) }
-
- before do
- subject.cluster.platform_kubernetes.namespace = 'a-namespace'
- stub_kubeclient_discover(cluster.platform_kubernetes.api_url)
-
- create(:cluster_kubernetes_namespace,
- cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
- end
-
- it 'creates proxy elasticsearch_client' do
- expect(subject.elasticsearch_client).to be_instance_of(Elasticsearch::Transport::Client)
- end
-
- it 'copies proxy_url, options and headers from kube client to elasticsearch_client' do
- expect(Elasticsearch::Client)
- .to(receive(:new))
- .with(url: a_valid_url, adapter: :net_http)
- .and_call_original
-
- client = subject.elasticsearch_client
- faraday_connection = client.transport.connections.first.connection
-
- expect(faraday_connection.headers["Authorization"]).to eq(kube_client.headers[:Authorization])
- expect(faraday_connection.ssl.cert_store).to be_instance_of(OpenSSL::X509::Store)
- expect(faraday_connection.ssl.verify).to eq(1)
- expect(faraday_connection.options.timeout).to be_nil
- end
-
- context 'when cluster is not reachable' do
- before do
- allow(kube_client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
- end
-
- it 'returns nil' do
- expect(subject.elasticsearch_client).to be_nil
- end
- end
-
- context 'when timeout is provided' do
- it 'sets timeout in elasticsearch_client' do
- client = subject.elasticsearch_client(timeout: 123)
- faraday_connection = client.transport.connections.first.connection
-
- expect(faraday_connection.options.timeout).to eq(123)
- end
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
index 2e062cda4e9..d80be5be3b3 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
@@ -230,7 +230,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
context 'deployment events' do
let_it_be(:deployment) { create(:deployment) }
- let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.current) }
+ let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, 'created', Time.current) }
it_behaves_like 'calls the integration API with the event message', /Deploy to (.*?) created/
end
@@ -677,7 +677,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
create(:deployment, :success, project: project, sha: project.commit.sha, ref: project.default_branch)
end
- let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.now) }
+ let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.now) }
before do
allow(chat_integration).to receive_messages(
diff --git a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
index a2b4cdc33d0..d06e8391a9a 100644
--- a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
@@ -82,7 +82,7 @@ RSpec.shared_examples 'a timebox' do |timebox_type|
it { is_expected.to belong_to(:group) }
it { is_expected.to have_many(:issues) }
it { is_expected.to have_many(:merge_requests) }
- it { is_expected.to have_many(:labels) }
+ it { is_expected.to have_many(:labels).through(:issues) }
end
describe '#timebox_name' do
diff --git a/spec/support/shared_examples/models/issuable_participants_shared_examples.rb b/spec/support/shared_examples/models/issuable_participants_shared_examples.rb
new file mode 100644
index 00000000000..c3eaae0ace2
--- /dev/null
+++ b/spec/support/shared_examples/models/issuable_participants_shared_examples.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'issuable participants' do
+ context 'when resource parent is public' do
+ context 'and users are referenced on notes' do
+ let_it_be(:notes_author) { create(:user) }
+
+ let(:note_params) { params.merge(author: notes_author) }
+
+ before do
+ create(:note, note_params)
+ end
+
+ it 'includes the issue author' do
+ expect(issuable.participants).to include(issuable.author)
+ end
+
+ it 'includes the authors of the notes' do
+ expect(issuable.participants).to include(notes_author)
+ end
+
+ context 'and note is confidential' do
+ context 'and mentions users' do
+ let_it_be(:guest_1) { create(:user) }
+ let_it_be(:guest_2) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+
+ before do
+ issuable_parent.add_guest(guest_1)
+ issuable_parent.add_guest(guest_2)
+ issuable_parent.add_reporter(reporter)
+
+ confidential_note_params =
+ note_params.merge(
+ confidential: true,
+ note: "mentions #{guest_1.to_reference} and #{guest_2.to_reference} and #{reporter.to_reference}"
+ )
+
+ regular_note_params =
+ note_params.merge(note: "Mentions #{guest_2.to_reference}")
+
+ create(:note, confidential_note_params)
+ create(:note, regular_note_params)
+ end
+
+ it 'only includes users that can read the note as participants' do
+ expect(issuable.participants).to contain_exactly(issuable.author, notes_author, reporter, guest_2)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index 75fff11cecd..aa40a2c7135 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -80,7 +80,7 @@ RSpec.shared_examples_for "member creation" do
let_it_be(:admin) { create(:admin) }
it 'returns a Member object', :aggregate_failures do
- member = described_class.add_user(source, user, :maintainer)
+ member = described_class.add_member(source, user, :maintainer)
expect(member).to be_a member_type
expect(member).to be_persisted
@@ -99,7 +99,7 @@ RSpec.shared_examples_for "member creation" do
end
it 'does not update the member' do
- member = described_class.add_user(source, project_bot, :maintainer, current_user: user)
+ member = described_class.add_member(source, project_bot, :maintainer, current_user: user)
expect(source.users.reload).to include(project_bot)
expect(member).to be_persisted
@@ -110,7 +110,7 @@ RSpec.shared_examples_for "member creation" do
context 'when project_bot is not already a member' do
it 'adds the member' do
- member = described_class.add_user(source, project_bot, :maintainer, current_user: user)
+ member = described_class.add_member(source, project_bot, :maintainer, current_user: user)
expect(source.users.reload).to include(project_bot)
expect(member).to be_persisted
@@ -120,7 +120,7 @@ RSpec.shared_examples_for "member creation" do
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.add_user(source, user, :maintainer, current_user: admin)
+ member = described_class.add_member(source, user, :maintainer, current_user: admin)
expect(member).to be_persisted
expect(source.users.reload).to include(user)
@@ -130,7 +130,7 @@ RSpec.shared_examples_for "member creation" do
context 'when admin mode is disabled' do
it 'rejects setting members.created_by to the given admin current_user', :aggregate_failures do
- member = described_class.add_user(source, user, :maintainer, current_user: admin)
+ member = described_class.add_member(source, user, :maintainer, current_user: admin)
expect(member).not_to be_persisted
expect(source.users.reload).not_to include(user)
@@ -139,7 +139,7 @@ RSpec.shared_examples_for "member creation" do
end
it 'sets members.expires_at to the given expires_at' do
- member = described_class.add_user(source, user, :maintainer, expires_at: Date.new(2016, 9, 22))
+ member = described_class.add_member(source, user, :maintainer, expires_at: Date.new(2016, 9, 22))
expect(member.expires_at).to eq(Date.new(2016, 9, 22))
end
@@ -148,7 +148,7 @@ RSpec.shared_examples_for "member creation" do
it "accepts the :#{sym_key} symbol as access level", :aggregate_failures do
expect(source.users).not_to include(user)
- member = described_class.add_user(source, user.id, sym_key)
+ member = described_class.add_member(source, user.id, sym_key)
expect(member.access_level).to eq(int_access_level)
expect(source.users.reload).to include(user)
@@ -157,7 +157,7 @@ RSpec.shared_examples_for "member creation" do
it "accepts the #{int_access_level} integer as access level", :aggregate_failures do
expect(source.users).not_to include(user)
- member = described_class.add_user(source, user.id, int_access_level)
+ member = described_class.add_member(source, user.id, int_access_level)
expect(member.access_level).to eq(int_access_level)
expect(source.users.reload).to include(user)
@@ -169,7 +169,7 @@ RSpec.shared_examples_for "member creation" do
it 'adds the user as a member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, user.id, :maintainer)
+ described_class.add_member(source, user.id, :maintainer)
expect(source.users.reload).to include(user)
end
@@ -179,7 +179,7 @@ RSpec.shared_examples_for "member creation" do
it 'does not add the user as a member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, non_existing_record_id, :maintainer)
+ described_class.add_member(source, non_existing_record_id, :maintainer)
expect(source.users.reload).not_to include(user)
end
@@ -189,7 +189,7 @@ RSpec.shared_examples_for "member creation" do
it 'adds the user as a member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, user, :maintainer)
+ described_class.add_member(source, user, :maintainer)
expect(source.users.reload).to include(user)
end
@@ -205,7 +205,7 @@ RSpec.shared_examples_for "member creation" do
expect(source.requesters.exists?(user_id: user)).to be_truthy
expect do
- described_class.add_user(source, user, :maintainer)
+ described_class.add_member(source, user, :maintainer)
end.to raise_error(Gitlab::Access::AccessDeniedError)
expect(source.users.reload).not_to include(user)
@@ -217,7 +217,7 @@ RSpec.shared_examples_for "member creation" do
it 'adds the user as a member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, user.email, :maintainer)
+ described_class.add_member(source, user.email, :maintainer)
expect(source.users.reload).to include(user)
end
@@ -227,7 +227,7 @@ RSpec.shared_examples_for "member creation" do
it 'creates an invited member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, 'user@example.com', :maintainer)
+ described_class.add_member(source, 'user@example.com', :maintainer)
expect(source.members.invite.pluck(:invite_email)).to include('user@example.com')
end
@@ -237,7 +237,7 @@ RSpec.shared_examples_for "member creation" do
it 'creates an invited member', :aggregate_failures do
email_starting_with_number = "#{user.id}_email@example.com"
- described_class.add_user(source, email_starting_with_number, :maintainer)
+ described_class.add_member(source, email_starting_with_number, :maintainer)
expect(source.members.invite.pluck(:invite_email)).to include(email_starting_with_number)
expect(source.users.reload).not_to include(user)
@@ -249,7 +249,7 @@ RSpec.shared_examples_for "member creation" do
it 'creates the member' do
expect(source.users).not_to include(user)
- described_class.add_user(source, user, :maintainer, current_user: admin)
+ described_class.add_member(source, user, :maintainer, current_user: admin)
expect(source.users.reload).to include(user)
end
@@ -263,7 +263,7 @@ RSpec.shared_examples_for "member creation" do
expect(source.users).not_to include(user)
expect(source.requesters.exists?(user_id: user)).to be_truthy
- described_class.add_user(source, user, :maintainer, current_user: admin)
+ described_class.add_member(source, user, :maintainer, current_user: admin)
expect(source.users.reload).to include(user)
expect(source.requesters.reload.exists?(user_id: user)).to be_falsy
@@ -275,7 +275,7 @@ RSpec.shared_examples_for "member creation" do
it 'does not create the member', :aggregate_failures do
expect(source.users).not_to include(user)
- member = described_class.add_user(source, user, :maintainer, current_user: user)
+ member = described_class.add_member(source, user, :maintainer, current_user: user)
expect(source.users.reload).not_to include(user)
expect(member).not_to be_persisted
@@ -290,7 +290,7 @@ RSpec.shared_examples_for "member creation" do
expect(source.users).not_to include(user)
expect(source.requesters.exists?(user_id: user)).to be_truthy
- described_class.add_user(source, user, :maintainer, current_user: user)
+ described_class.add_member(source, user, :maintainer, current_user: user)
expect(source.users.reload).not_to include(user)
expect(source.requesters.exists?(user_id: user)).to be_truthy
@@ -299,37 +299,51 @@ RSpec.shared_examples_for "member creation" do
end
context 'when member already exists' do
- before do
- source.add_user(user, :developer)
- end
+ context 'when member is a user' do
+ before do
+ source.add_member(user, :developer)
+ end
- context 'with no current_user' do
- it 'updates the member' do
- expect(source.users).to include(user)
+ context 'with no current_user' do
+ it 'updates the member' do
+ expect(source.users).to include(user)
- described_class.add_user(source, user, :maintainer)
+ described_class.add_member(source, user, :maintainer)
- expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
end
- end
- context 'when current_user can update member', :enable_admin_mode do
- it 'updates the member' do
- expect(source.users).to include(user)
+ context 'when current_user can update member', :enable_admin_mode do
+ it 'updates the member' do
+ expect(source.users).to include(user)
- described_class.add_user(source, user, :maintainer, current_user: admin)
+ described_class.add_member(source, user, :maintainer, current_user: admin)
- expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
end
- end
- context 'when current_user cannot update member' do
- it 'does not update the member' do
- expect(source.users).to include(user)
+ context 'when current_user cannot update member' do
+ it 'does not update the member' do
+ expect(source.users).to include(user)
+
+ described_class.add_member(source, user, :maintainer, current_user: user)
+
+ expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+ end
- described_class.add_user(source, user, :maintainer, current_user: user)
+ context 'when member is an invite by email' do
+ let_it_be(:email) { 'user@email.com' }
+ let_it_be(:existing_member) { source.add_developer(email) }
- expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
+ it 'updates the member for that email' do
+ expect do
+ described_class.add_member(source, email, :maintainer)
+ end.to change { existing_member.reset.access_level }.from(Member::DEVELOPER).to(Member::MAINTAINER)
+ .and not_change { source.members.invite.count }
end
end
end
@@ -345,12 +359,12 @@ RSpec.shared_examples_for "bulk member creation" do
# maintainers cannot add owners
source.add_maintainer(user)
- expect(described_class.add_users(source, [user1, user2], :owner, current_user: user)).to be_empty
+ expect(described_class.add_members(source, [user1, user2], :owner, current_user: user)).to be_empty
end
end
it 'returns Member objects' do
- members = described_class.add_users(source, [user1, user2], :maintainer)
+ members = described_class.add_members(source, [user1, user2], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1, user2)
expect(members).to all(be_a(member_type))
@@ -358,7 +372,7 @@ RSpec.shared_examples_for "bulk member creation" do
end
it 'returns an empty array' do
- members = described_class.add_users(source, [], :maintainer)
+ members = described_class.add_members(source, [], :maintainer)
expect(members).to be_a Array
expect(members).to be_empty
@@ -367,7 +381,7 @@ RSpec.shared_examples_for "bulk member creation" do
it 'supports different formats' do
list = ['joe@local.test', admin, user1.id, user2.id.to_s]
- members = described_class.add_users(source, list, :maintainer)
+ members = described_class.add_members(source, list, :maintainer)
expect(members.size).to eq(4)
expect(members.first).to be_invite
@@ -375,7 +389,7 @@ RSpec.shared_examples_for "bulk member creation" do
context 'with de-duplication' do
it 'has the same user by id and user' do
- members = described_class.add_users(source, [user1.id, user1, user1.id, user2, user2.id, user2], :maintainer)
+ members = described_class.add_members(source, [user1.id, user1, user1.id, user2, user2.id, user2], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1, user2)
expect(members).to all(be_a(member_type))
@@ -383,7 +397,7 @@ RSpec.shared_examples_for "bulk member creation" do
end
it 'has the same user sent more than once' do
- members = described_class.add_users(source, [user1, user1], :maintainer)
+ members = described_class.add_members(source, [user1, user1], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1)
expect(members).to all(be_a(member_type))
@@ -392,7 +406,7 @@ RSpec.shared_examples_for "bulk member creation" do
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)
+ members = described_class.add_members(source, [user1, user1.email], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1)
expect(members).to all(be_a(member_type))
@@ -400,7 +414,7 @@ RSpec.shared_examples_for "bulk member creation" do
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)
+ members = described_class.add_members(source, [user1.id, user1.email], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1)
expect(members).to all(be_a(member_type))
@@ -409,12 +423,12 @@ RSpec.shared_examples_for "bulk member creation" do
context 'when a member already exists' do
before do
- source.add_user(user1, :developer)
+ source.add_member(user1, :developer)
end
it 'has the same user sent more than once with the member already existing' do
expect do
- members = described_class.add_users(source, [user1, user1, user2], :maintainer)
+ members = described_class.add_members(source, [user1, user1, user2], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1, user2)
expect(members).to all(be_a(member_type))
expect(members).to all(be_persisted)
@@ -425,7 +439,7 @@ RSpec.shared_examples_for "bulk member creation" do
user3 = create(:user)
expect do
- members = described_class.add_users(source, [user1.id, user2, user3.id], :maintainer)
+ members = described_class.add_members(source, [user1.id, user2, user3.id], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1, user2, user3)
expect(members).to all(be_a(member_type))
expect(members).to all(be_persisted)
@@ -436,7 +450,7 @@ RSpec.shared_examples_for "bulk member creation" do
user3 = create(:user)
expect do
- members = described_class.add_users(source, [user1, user2, user3], :maintainer)
+ members = described_class.add_members(source, [user1, user2, user3], :maintainer)
expect(members.map(&:user)).to contain_exactly(user1, user2, user3)
expect(members).to all(be_a(member_type))
expect(members).to all(be_persisted)
@@ -448,7 +462,7 @@ RSpec.shared_examples_for "bulk member creation" do
let(:task_project) { source.is_a?(Group) ? create(:project, group: source) : source }
it 'creates a member_task with the correct attributes', :aggregate_failures do
- members = described_class.add_users(source, [user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id)
+ members = described_class.add_members(source, [user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id)
member = members.last
expect(member.tasks_to_be_done).to match_array([:ci, :code])
@@ -457,7 +471,7 @@ RSpec.shared_examples_for "bulk member creation" do
context 'with an already existing member' do
before do
- source.add_user(user1, :developer)
+ source.add_member(user1, :developer)
end
it 'does not update tasks to be done if tasks already exist', :aggregate_failures do
@@ -465,7 +479,7 @@ RSpec.shared_examples_for "bulk member creation" do
create(:member_task, member: member, project: task_project, tasks_to_be_done: %w(code ci))
expect do
- described_class.add_users(source,
+ described_class.add_members(source,
[user1.id],
:developer,
tasks_to_be_done: %w(issues),
@@ -479,7 +493,7 @@ RSpec.shared_examples_for "bulk member creation" do
it 'adds tasks to be done if they do not exist', :aggregate_failures do
expect do
- described_class.add_users(source,
+ described_class.add_members(source,
[user1.id],
:developer,
tasks_to_be_done: %w(issues),
diff --git a/spec/support/shared_examples/models/mentionable_shared_examples.rb b/spec/support/shared_examples/models/mentionable_shared_examples.rb
index e23658d1774..f9612dd61be 100644
--- a/spec/support/shared_examples/models/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/models/mentionable_shared_examples.rb
@@ -260,6 +260,25 @@ RSpec.shared_examples 'mentions in notes' do |mentionable_type|
expect(mentionable.referenced_projects(user)).to eq [mentionable.project].compact # epic.project is nil, and we want empty []
expect(mentionable.referenced_groups(user)).to eq [group]
end
+
+ if [:epic, :issue].include?(mentionable_type)
+ context 'and note is confidential' do
+ let_it_be(:guest) { create(:user) }
+
+ let(:note_desc) { "#{guest.to_reference} and #{user2.to_reference} and #{user.to_reference}" }
+
+ before do
+ note.resource_parent.add_reporter(user2)
+ note.resource_parent.add_guest(guest)
+ # Bypass :confidential update model validation for testing purposes
+ note.update_attribute(:confidential, true)
+ end
+
+ it 'returns only mentioned users that has permissions' do
+ expect(note.mentioned_users).to contain_exactly(user, user2)
+ end
+ end
+ end
end
end
@@ -294,6 +313,26 @@ RSpec.shared_examples 'load mentions from DB' do |mentionable_type|
end
end
+ if [:epic, :issue].include?(mentionable_type)
+ context 'and note is confidential' do
+ let_it_be(:guest) { create(:user) }
+
+ let(:note_desc) { "#{guest.to_reference} and #{mentioned_user.to_reference}" }
+
+ before do
+ note.resource_parent.add_reporter(mentioned_user)
+ note.resource_parent.add_guest(guest)
+ # Bypass :confidential update model validation for testing purposes
+ note.update_attribute(:confidential, true)
+ note.store_mentions!
+ end
+
+ it 'stores only mentioned users that has permissions' do
+ expect(mentionable.referenced_users).to contain_exactly(mentioned_user)
+ end
+ end
+ end
+
context 'when private projects and groups are mentioned' do
let(:mega_user) { create(:user) }
let(:private_project) { create(:project, :private) }
diff --git a/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb
index ab04692616a..d42e925ed22 100644
--- a/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/clone_quick_action_shared_examples.rb
@@ -89,10 +89,13 @@ RSpec.shared_examples 'clone quick action' do
let(:bug) { create(:label, project: project, title: 'bug') }
let(:wontfix) { create(:label, project: project, title: 'wontfix') }
- let!(:target_milestone) { create(:milestone, title: '1.0', project: target_project) }
-
before do
target_project.add_maintainer(user)
+
+ # create equivalent labels and milestones in the target project
+ create(:label, project: target_project, title: 'bug')
+ create(:label, project: target_project, title: 'wontfix')
+ create(:milestone, title: '1.0', project: target_project)
end
shared_examples 'applies the commands to issues in both projects, target and source' do
diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
index e6b0772aec1..bb2f8965294 100644
--- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
RSpec.shared_examples 'conan ping endpoint' do
+ it_behaves_like 'conan FIPS mode' do
+ subject { get api(url) }
+ end
+
it 'responds with 200 OK when no token provided' do
get api(url)
@@ -68,7 +72,7 @@ RSpec.shared_examples 'conan search endpoint' do
project.update!(visibility: 'private')
project.team.truncate
user.project_authorizations.delete_all
- project.add_user(user, role) unless role == :anonymous
+ project.add_member(user, role) unless role == :anonymous
get api(url), params: params, headers: headers
end
@@ -85,6 +89,8 @@ end
RSpec.shared_examples 'conan authenticate endpoint' do
subject { get api(url), headers: headers }
+ it_behaves_like 'conan FIPS mode'
+
context 'when using invalid token' do
let(:auth_token) { 'invalid_token' }
@@ -159,6 +165,10 @@ RSpec.shared_examples 'conan authenticate endpoint' do
end
RSpec.shared_examples 'conan check_credentials endpoint' do
+ it_behaves_like 'conan FIPS mode' do
+ subject { get api(url), headers: headers }
+ end
+
it 'responds with a 200 OK with PAT' do
get api(url), headers: headers
@@ -390,6 +400,7 @@ end
RSpec.shared_examples 'recipe snapshot endpoint' do
subject { get api(url), headers: headers }
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'empty recipe for not found package'
@@ -415,6 +426,7 @@ end
RSpec.shared_examples 'package snapshot endpoint' do
subject { get api(url), headers: headers }
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'empty recipe for not found package'
@@ -436,6 +448,10 @@ RSpec.shared_examples 'package snapshot endpoint' do
end
RSpec.shared_examples 'recipe download_urls endpoint' do
+ it_behaves_like 'conan FIPS mode' do
+ let(:recipe_path) { package.conan_recipe_path }
+ end
+
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'recipe download_urls'
@@ -443,6 +459,10 @@ RSpec.shared_examples 'recipe download_urls endpoint' do
end
RSpec.shared_examples 'package download_urls endpoint' do
+ it_behaves_like 'conan FIPS mode' do
+ let(:recipe_path) { package.conan_recipe_path }
+ end
+
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects recipe for invalid project'
it_behaves_like 'package download_urls'
@@ -457,6 +477,7 @@ RSpec.shared_examples 'recipe upload_urls endpoint' do
'conanmanifest.txt': 123 }
end
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid upload_url params'
it_behaves_like 'handling empty values for username and channel'
@@ -519,6 +540,7 @@ RSpec.shared_examples 'package upload_urls endpoint' do
'conan_package.tgz': 523 }
end
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid upload_url params'
it_behaves_like 'handling empty values for username and channel'
@@ -556,6 +578,7 @@ end
RSpec.shared_examples 'delete package endpoint' do
let(:recipe_path) { package.conan_recipe_path }
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'handling empty values for username and channel'
@@ -665,6 +688,7 @@ RSpec.shared_examples 'not found request' do
end
RSpec.shared_examples 'recipe file download endpoint' do
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'a public project with packages'
it_behaves_like 'an internal project with packages'
it_behaves_like 'a private project with packages'
@@ -672,6 +696,7 @@ RSpec.shared_examples 'recipe file download endpoint' do
end
RSpec.shared_examples 'package file download endpoint' do
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'a public project with packages'
it_behaves_like 'an internal project with packages'
it_behaves_like 'a private project with packages'
@@ -697,6 +722,7 @@ RSpec.shared_examples 'project not found by project id' do
end
RSpec.shared_examples 'workhorse authorize endpoint' do
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'workhorse authorization'
@@ -718,6 +744,7 @@ RSpec.shared_examples 'workhorse recipe file upload endpoint' do
)
end
+ it_behaves_like 'conan FIPS mode'
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'uploads a package file'
@@ -979,3 +1006,9 @@ RSpec.shared_examples 'workhorse authorization' do
end
end
end
+
+RSpec.shared_examples 'conan FIPS mode' do
+ context 'when FIPS mode is enabled', :fips_mode do
+ it_behaves_like 'returning response status', :not_found
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb
index e0225070986..2ba42b8e8fa 100644
--- a/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb
@@ -15,3 +15,9 @@ RSpec.shared_examples 'rejects Debian access with unknown container id' do |anon
end
end
end
+
+RSpec.shared_examples 'Debian API FIPS mode' do
+ context 'when FIPS mode is enabled', :fips_mode do
+ it_behaves_like 'returning response status', :not_found
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb
index 5cd63c33936..f13ac05591c 100644
--- a/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb
@@ -3,6 +3,8 @@
RSpec.shared_examples 'Debian distributions GET request' do |status, body = nil|
and_body = body.nil? ? '' : ' and expected body'
+ it_behaves_like 'Debian API FIPS mode'
+
it "returns #{status}#{and_body}" do
subject
@@ -17,6 +19,8 @@ end
RSpec.shared_examples 'Debian distributions PUT request' do |status, body|
and_body = body.nil? ? '' : ' and expected body'
+ it_behaves_like 'Debian API FIPS mode'
+
if status == :success
it 'updates distribution', :aggregate_failures do
expect(::Packages::Debian::UpdateDistributionService).to receive(:new).with(distribution, api_params.except(:codename)).and_call_original
@@ -49,6 +53,8 @@ end
RSpec.shared_examples 'Debian distributions DELETE request' do |status, body|
and_body = body.nil? ? '' : ' and expected body'
+ it_behaves_like 'Debian API FIPS mode'
+
if status == :success
it 'updates distribution', :aggregate_failures do
expect { subject }
diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
index 9f96cb2a164..de7032450a5 100644
--- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
@@ -3,6 +3,8 @@
RSpec.shared_examples 'Debian packages GET request' do |status, body = nil|
and_body = body.nil? ? '' : ' and expected body'
+ it_behaves_like 'Debian API FIPS mode'
+
it "returns #{status}#{and_body}" do
subject
@@ -17,6 +19,8 @@ end
RSpec.shared_examples 'Debian packages upload request' do |status, body = nil|
and_body = body.nil? ? '' : ' and expected body'
+ it_behaves_like 'Debian API FIPS mode'
+
if status == :created
it 'creates package files', :aggregate_failures do
expect(::Packages::Debian::FindOrCreateIncomingService).to receive(:new).with(container, user).and_call_original
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 e534a02e562..8ab820e9d43 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
@@ -64,7 +64,8 @@ RSpec.shared_examples 'group and project boards query' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { }
+ include_context 'no sort argument'
+
let(:first_param) { 2 }
def pagination_results_data(nodes)
diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
index a42a1fda62e..b459e479c91 100644
--- a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb
@@ -22,7 +22,7 @@ RSpec.shared_examples 'snippet edit usage data counters' do
context 'when user is not sessionless', :clean_gitlab_redis_sessions do
before do
- stub_session('warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]])
+ stub_session('warden.user.user.key' => [[current_user.id], current_user.authenticatable_salt])
end
it 'tracks usage data actions', :clean_gitlab_redis_sessions do
diff --git a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb
new file mode 100644
index 00000000000..013945bd578
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb
@@ -0,0 +1,415 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'web-hook API endpoints test hook' do |prefix|
+ describe "POST #{prefix}/:hook_id" do
+ it 'tests the hook' do
+ expect(WebHookService)
+ .to receive(:new).with(hook, anything, String, force: false)
+ .and_return(instance_double(WebHookService, execute: nil))
+
+ post api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+end
+
+RSpec.shared_examples 'web-hook API endpoints with branch-filter' do |prefix|
+ describe "POST #{prefix}/hooks" do
+ it "returns a 422 error if branch filter is not valid" do
+ post api(collection_uri, user),
+ params: { url: "http://example.com", push_events_branch_filter: '~badbranchname/' }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+end
+
+RSpec.shared_examples 'web-hook API endpoints' do |prefix|
+ def hooks_count
+ scope.count
+ end
+
+ def hook_param_overrides
+ if defined?(super)
+ super
+ else
+ { push_events_branch_filter: 'some-feature-branch' }
+ end
+ end
+
+ let(:hook_params) do
+ event_names.to_h { [_1, true] }.merge(hook_param_overrides).merge(
+ url: "http://example.com",
+ url_variables: [
+ { key: 'token', value: 'very-secret' },
+ { key: 'abc', value: 'other value' }
+ ]
+ )
+ end
+
+ let(:update_params) do
+ {
+ push_events: false,
+ job_events: true,
+ push_events_branch_filter: 'updated-branch-filter'
+ }
+ end
+
+ let(:default_values) { {} }
+
+ describe "GET #{prefix}/hooks" do
+ context "authorized user" do
+ it "returns all hooks" do
+ get api(collection_uri, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_collection_schema
+ end
+ end
+
+ context "when user is forbidden" do
+ it "prevents access to hooks" do
+ get api(collection_uri, unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context "when user is unauthorized" do
+ it "prevents access to hooks" do
+ get api(collection_uri, nil)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'the hook has URL variables' do
+ before do
+ hook.update!(url_variables: { 'token' => 'supers3cret' })
+ end
+
+ it 'returns the names of the url variables' do
+ get api(collection_uri, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to contain_exactly(
+ a_hash_including(
+ 'url_variables' => [{ 'key' => 'token' }]
+ )
+ )
+ end
+ end
+ end
+
+ describe "GET #{prefix}/hooks/:hook_id" do
+ context "authorized user" do
+ it "returns a project hook" do
+ get api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_hook_schema
+
+ expect(json_response['url']).to eq(hook.url)
+ end
+
+ it "returns a 404 error if hook id is not available" do
+ get api(hook_uri(non_existing_record_id), user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'the hook is disabled' do
+ before do
+ hook.disable!
+ end
+
+ it "has the correct alert status", :aggregate_failures do
+ get api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to include('alert_status' => 'disabled')
+ end
+ end
+
+ context 'the hook is backed-off' do
+ before do
+ hook.backoff!
+ end
+
+ it "has the correct alert status", :aggregate_failures do
+ get api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to include(
+ 'alert_status' => 'temporarily_disabled',
+ 'disabled_until' => hook.disabled_until.iso8601(3)
+ )
+ end
+ end
+ end
+
+ context "when user is forbidden" do
+ it "does not access an existing hook" do
+ get api(hook_uri, unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context "when user is unauthorized" do
+ it "does not access an existing hook" do
+ get api(hook_uri, nil)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe "POST #{prefix}/hooks" do
+ let(:hook_creation_params) { hook_params }
+
+ it "adds hook", :aggregate_failures do
+ expect do
+ post api(collection_uri, user),
+ params: hook_creation_params
+ end.to change { hooks_count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_hook_schema
+
+ expect(json_response['url']).to eq(hook_creation_params[:url])
+ hook_param_overrides.each do |k, v|
+ expect(json_response[k.to_s]).to eq(v)
+ end
+ event_names.each do |name|
+ expect(json_response[name.to_s]).to eq(true), name
+ end
+ expect(json_response['url_variables']).to match_array [
+ { 'key' => 'token' },
+ { 'key' => 'abc' }
+ ]
+ expect(json_response).not_to include('token')
+ end
+
+ it "adds the token without including it in the response" do
+ token = "secret token"
+
+ expect do
+ post api(collection_uri, user),
+ params: { url: "http://example.com", token: token }
+ end.to change { hooks_count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response["url"]).to eq("http://example.com")
+ expect(json_response).not_to include("token")
+
+ hook = scope.find(json_response["id"])
+
+ expect(hook.url).to eq("http://example.com")
+ expect(hook.token).to eq(token)
+ end
+
+ it "returns a 400 error if url not given" do
+ post api(collection_uri, user), params: { event_names.first => true }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it "returns a 400 error if no parameters are provided" do
+ post api(collection_uri, user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'sets default values for events', :aggregate_failures do
+ post api(collection_uri, user), params: { url: 'http://mep.mep' }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_hook_schema
+ expect(json_response['enable_ssl_verification']).to be true
+ event_names.each do |name|
+ expect(json_response[name.to_s]).to eq(default_values.fetch(name, false)), name
+ end
+ end
+
+ it "returns a 422 error if token not valid" do
+ post api(collection_uri, user),
+ params: { url: "http://example.com", token: "foo\nbar" }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+
+ it "returns a 422 error if url not valid" do
+ post api(collection_uri, user), params: { url: "ftp://example.com" }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ describe "PUT #{prefix}/hooks/:hook_id" do
+ it "updates an existing hook" do
+ put api(hook_uri, user), params: update_params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_hook_schema
+
+ update_params.each do |k, v|
+ expect(json_response[k.to_s]).to eq(v)
+ end
+ end
+
+ it 'updates the URL variables' do
+ hook.update!(url_variables: { 'abc' => 'some value' })
+
+ put api(hook_uri, user),
+ params: { url_variables: [{ key: 'def', value: 'other value' }] }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['url_variables']).to match_array [
+ { 'key' => 'abc' },
+ { 'key' => 'def' }
+ ]
+ end
+
+ it "adds the token without including it in the response" do
+ token = "secret token"
+
+ put api(hook_uri, user), params: { url: "http://example.org", token: token }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response["url"]).to eq("http://example.org")
+ expect(json_response).not_to include("token")
+
+ expect(hook.reload.url).to eq("http://example.org")
+ expect(hook.reload.token).to eq(token)
+ end
+
+ it "returns 404 error if hook id not found" do
+ put api(hook_uri(non_existing_record_id), user), params: { url: 'http://example.org' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns 400 error if no parameters are provided" do
+ put api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it "returns a 422 error if url is not valid" do
+ put api(hook_uri, user), params: { url: 'ftp://example.com' }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+
+ it "returns a 422 error if token is not valid" do
+ put api(hook_uri, user), params: { token: %w[foo bar].join("\n") }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ describe "DELETE /projects/:id/hooks/:hook_id" do
+ it "deletes hook from project" do
+ expect do
+ delete api(hook_uri, user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change { hooks_count }.by(-1)
+ end
+
+ it "returns a 404 error when deleting non existent hook" do
+ delete api(hook_uri(non_existing_record_id), user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns a 404 error if hook id not given" do
+ delete api(collection_uri, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns forbidden if a user attempts to delete hooks they do not own" do
+ delete api(hook_uri, unauthorized_user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(WebHook.exists?(hook.id)).to be_truthy
+ end
+
+ it_behaves_like '412 response' do
+ let(:request) { api(hook_uri, user) }
+ end
+ end
+
+ describe "PUT #{prefix}/hooks/:hook_id/url_variables/:key", :aggregate_failures do
+ it 'sets the variable' do
+ expect do
+ put api("#{hook_uri}/url_variables/abc", user),
+ params: { value: 'some secret value' }
+ end.to change { hook.reload.url_variables }.to(eq('abc' => 'some secret value'))
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'overwrites existing values' do
+ hook.update!(url_variables: { 'abc' => 'xyz', 'def' => 'other value' })
+
+ put api("#{hook_uri}/url_variables/abc", user),
+ params: { value: 'some secret value' }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(hook.reload.url_variables).to eq('abc' => 'some secret value', 'def' => 'other value')
+ end
+
+ it "returns a 404 error when editing non existent hook" do
+ put api("#{hook_uri(non_existing_record_id)}/url_variables/abc", user),
+ params: { value: 'xyz' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns a 422 error when the key is illegal" do
+ put api("#{hook_uri}/url_variables/abc%20def", user),
+ params: { value: 'xyz' }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+
+ it "returns a 422 error when the value is illegal" do
+ put api("#{hook_uri}/url_variables/abc", user),
+ params: { value: '' }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ describe "DELETE #{prefix}/hooks/:hook_id/url_variables/:key", :aggregate_failures do
+ before do
+ hook.update!(url_variables: { 'abc' => 'prior value', 'def' => 'other value' })
+ end
+
+ it 'unsets the variable' do
+ expect do
+ delete api("#{hook_uri}/url_variables/abc", user)
+ end.to change { hook.reload.url_variables }.to(eq({ 'def' => 'other value' }))
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'returns 404 for keys that do not exist' do
+ hook.update!(url_variables: { 'def' => 'other value' })
+
+ delete api("#{hook_uri}/url_variables/abc", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns a 404 error when deleting a variable from a non existent hook" do
+ delete api(hook_uri(non_existing_record_id) + "/url_variables/abc", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ 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 e7e30665b08..a59235486ec 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -275,7 +275,9 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
context 'when request exceeds the rate limit', :freeze_time, :clean_gitlab_redis_rate_limiting do
before do
stub_application_setting(notes_create_limit: 1)
- allow(::Gitlab::ApplicationRateLimiter).to receive(:increment).and_return(2)
+ allow_next_instance_of(Gitlab::ApplicationRateLimiter::BaseStrategy) do |strategy|
+ allow(strategy).to receive(:increment).and_return(2)
+ end
end
it 'prevents user from creating more notes' do
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
index 795545e4ad1..1a248bb04e7 100644
--- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member = true|
+RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member = true, md5_digest = true|
RSpec.shared_examples 'creating pypi package files' do
it 'creates package files' do
expect { subject }
@@ -14,6 +14,17 @@ RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member
expect(package.name).to eq params[:name]
expect(package.version).to eq params[:version]
expect(package.pypi_metadatum.required_python).to eq params[:requires_python]
+
+ if md5_digest
+ expect(package.package_files.first.file_md5).not_to be_nil
+ else
+ expect(package.package_files.first.file_md5).to be_nil
+ end
+ end
+
+ context 'with FIPS mode', :fips_mode do
+ it_behaves_like 'returning response status', :unprocessable_entity if md5_digest
+ it_behaves_like 'returning response status', status unless md5_digest
end
end
diff --git a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
index 70cc9b1e6b5..544a0ed8fdd 100644
--- a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
@@ -52,6 +52,24 @@ RSpec.shared_examples 'an unimplemented route' do
it_behaves_like 'when package feature is disabled'
end
+RSpec.shared_examples 'redirects to version download' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid response' do
+ subject
+
+ expect(request.url).to include 'module-1/system/download'
+ expect(response.headers).to include 'Location'
+ expect(response.headers['Location']).to include 'module-1/system/1.0.1/download'
+ end
+ end
+end
+
RSpec.shared_examples 'grants terraform module download' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
@@ -84,6 +102,22 @@ RSpec.shared_examples 'returns terraform module packages' do |user_type, status,
end
end
+RSpec.shared_examples 'returns terraform module version' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'returning a valid response' do
+ subject
+
+ expect(json_response).to match_schema('public_api/v4/packages/terraform/modules/v1/single_version')
+ end
+ end
+end
+
RSpec.shared_examples 'returns no terraform module packages' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
index 86e7da5bcbe..f8e096297d3 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
@@ -56,7 +56,7 @@ RSpec.shared_examples 'processes recovery alert' do
context 'seen for the first time' do
let(:alert) { AlertManagement::Alert.last }
- include_examples 'processes never-before-seen recovery alert'
+ it_behaves_like 'alerts service responds with an error and takes no actions', :bad_request
end
context 'for an existing alert with the same fingerprint' do
@@ -107,7 +107,7 @@ RSpec.shared_examples 'processes recovery alert' do
context 'which is resolved' do
let_it_be(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: gitlab_fingerprint, monitoring_tool: source) }
- include_examples 'processes never-before-seen recovery alert'
+ it_behaves_like 'alerts service responds with an error and takes no actions', :bad_request
end
end
end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
index 132f1e0422e..3add5485fca 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
@@ -6,18 +6,24 @@
# - `alert`, alert for which related incidents should be closed
# - `project`, project of the alert
RSpec.shared_examples 'closes related incident if enabled' do
- context 'with issue' do
+ context 'with incident' do
before do
- alert.update!(issue: create(:issue, project: project))
+ alert.update!(issue: create(:incident, project: project))
end
- it { expect { subject }.to change { alert.issue.reload.closed? }.from(false).to(true) }
- it { expect { subject }.to change(ResourceStateEvent, :count).by(1) }
+ specify do
+ expect { Sidekiq::Testing.inline! { subject } }
+ .to change { alert.issue.reload.closed? }.from(false).to(true)
+ .and change(ResourceStateEvent, :count).by(1)
+ end
end
- context 'without issue' do
- it { expect { subject }.not_to change { alert.reload.issue } }
- it { expect { subject }.not_to change(ResourceStateEvent, :count) }
+ context 'without incident' do
+ specify do
+ expect(::IncidentManagement::CloseIncidentWorker).not_to receive(:perform_async)
+
+ subject
+ end
end
context 'with incident setting disabled' do
@@ -28,17 +34,23 @@ RSpec.shared_examples 'closes related incident if enabled' do
end
RSpec.shared_examples 'does not close related incident' do
- context 'with issue' do
+ context 'with incident' do
before do
- alert.update!(issue: create(:issue, project: project))
+ alert.update!(issue: create(:incident, project: project))
end
- it { expect { subject }.not_to change { alert.issue.reload.state } }
- it { expect { subject }.not_to change(ResourceStateEvent, :count) }
+ specify do
+ expect { Sidekiq::Testing.inline! { subject } }
+ .to not_change { alert.issue.reload.state }
+ .and not_change(ResourceStateEvent, :count)
+ end
end
- context 'without issue' do
- it { expect { subject }.not_to change { alert.reload.issue } }
- it { expect { subject }.not_to change(ResourceStateEvent, :count) }
+ context 'without incident' do
+ specify do
+ expect(::IncidentManagement::CloseIncidentWorker).not_to receive(:perform_async)
+
+ subject
+ end
end
end
diff --git a/spec/support/shared_examples/services/alert_management_shared_examples.rb b/spec/support/shared_examples/services/alert_management_shared_examples.rb
index f644f1a1687..571cb7dc03d 100644
--- a/spec/support/shared_examples/services/alert_management_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb
@@ -68,14 +68,14 @@ RSpec.shared_examples 'processes one firing and one resolved prometheus alerts'
expect(Gitlab::AppLogger).not_to receive(:warn)
expect { subject }
- .to change(AlertManagement::Alert, :count).by(2)
- .and change(Note, :count).by(4)
+ .to change(AlertManagement::Alert, :count).by(1)
+ .and change(Note, :count).by(1)
expect(subject).to be_success
expect(subject.payload[:alerts]).to all(be_a_kind_of(AlertManagement::Alert))
- expect(subject.payload[:alerts].size).to eq(2)
+ expect(subject.payload[:alerts].size).to eq(1)
end
it_behaves_like 'processes incident issues'
- it_behaves_like 'sends alert notification emails', count: 2
+ it_behaves_like 'sends alert notification emails'
end
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 f18869fb380..3be59af6a37 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
@@ -1,10 +1,11 @@
# frozen_string_literal: true
RSpec.shared_context 'container registry auth service context' do
+ let_it_be(:rsa_key) { OpenSSL::PKey::RSA.generate(3072) }
+
let(:current_project) { nil }
let(:current_user) { nil }
let(:current_params) { {} }
- let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
let(:payload) { JWT.decode(subject[:token], rsa_key, true, { algorithm: 'RS256' }).first }
let(:authentication_abilities) do
diff --git a/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb b/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb
new file mode 100644
index 00000000000..a62cffc0e1b
--- /dev/null
+++ b/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+shared_examples_for 'update feature flag client' do
+ let!(:client) { create(:operations_feature_flags_client, project: project) }
+
+ it 'updates last feature flag updated at' do
+ freeze_time do
+ expect { subject }.to change { client.reload.last_feature_flag_updated_at }.from(nil).to(Time.current)
+ end
+ end
+end
+
+shared_examples_for 'does not update feature flag client' do
+ let!(:client) { create(:operations_feature_flags_client, project: project) }
+
+ it 'does not update last feature flag updated at' do
+ expect { subject }.not_to change { client.reload.last_feature_flag_updated_at }
+ end
+end
diff --git a/spec/support/shared_examples/usage_data_counters/work_item_activity_unique_counter_shared_examples.rb b/spec/support/shared_examples/usage_data_counters/work_item_activity_unique_counter_shared_examples.rb
new file mode 100644
index 00000000000..4655585a092
--- /dev/null
+++ b/spec/support/shared_examples/usage_data_counters/work_item_activity_unique_counter_shared_examples.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'counter that does not track the event' do
+ it 'does not track the event' do
+ expect { 3.times { track_event } }.to not_change {
+ Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(
+ event_names: event_name,
+ start_date: 2.weeks.ago,
+ end_date: 2.weeks.from_now
+ )
+ }
+ end
+end
+
+RSpec.shared_examples 'work item unique counter' do
+ context 'when track_work_items_activity FF is enabled' do
+ it 'tracks a unique event only once' do
+ expect { 3.times { track_event } }.to change {
+ Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(
+ event_names: event_name,
+ start_date: 2.weeks.ago,
+ end_date: 2.weeks.from_now
+ )
+ }.by(1)
+ end
+
+ context 'when author is nil' do
+ let(:user) { nil }
+
+ it_behaves_like 'counter that does not track the event'
+ end
+ end
+
+ context 'when track_work_items_activity FF is disabled' do
+ before do
+ stub_feature_flags(track_work_items_activity: false)
+ end
+
+ it_behaves_like 'counter that does not track the event'
+ end
+end
diff --git a/spec/support/shared_examples/views/themed_layout_examples.rb b/spec/support/shared_examples/views/themed_layout_examples.rb
new file mode 100644
index 00000000000..b6c53dce4cb
--- /dev/null
+++ b/spec/support/shared_examples/views/themed_layout_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples "a layout which reflects the application theme setting", :themed_layout do
+ context 'as a themed layout' do
+ let(:default_theme_class) { ::Gitlab::Themes.default.css_class }
+
+ context 'when no theme is explicitly selected' do
+ it 'renders with the default theme' do
+ render
+
+ expect(rendered).to have_selector("body.#{default_theme_class}")
+ end
+ end
+
+ context 'when user is authenticated & has selected a specific theme' do
+ before do
+ allow(view).to receive(:user_application_theme).and_return(chosen_theme.css_class)
+ end
+
+ where(chosen_theme: ::Gitlab::Themes.available_themes)
+
+ with_them do
+ it "renders with the #{params[:chosen_theme].name} theme" do
+ render
+
+ if chosen_theme.css_class != default_theme_class
+ expect(rendered).not_to have_selector("body.#{default_theme_class}")
+ end
+
+ expect(rendered).to have_selector("body.#{chosen_theme.css_class}")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/work_items/widgetable_service_shared_examples.rb b/spec/support/shared_examples/work_items/widgetable_service_shared_examples.rb
new file mode 100644
index 00000000000..491662d17d3
--- /dev/null
+++ b/spec/support/shared_examples/work_items/widgetable_service_shared_examples.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'work item widgetable service' do
+ it 'executes callbacks for expected widgets' do
+ supported_widgets.each do |widget|
+ expect_next_instance_of(widget[:klass]) do |widget_instance|
+ expect(widget_instance).to receive(widget[:callback]).with(params: widget[:params])
+ end
+ end
+
+ service_execute
+ 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 54962eac100..1da21633504 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
@@ -229,6 +229,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
describe 'executing an entire migration', :freeze_time, if: Gitlab::Database.has_config?(tracking_database) do
include Gitlab::Database::DynamicModelHelpers
+ include Database::DatabaseHelpers
let(:migration_class) do
Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
@@ -347,5 +348,20 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
it 'does not update non-matching records in the range' do
expect { full_migration_run }.not_to change { example_data.where('status <> 1 AND some_column <> 0').count }
end
+
+ context 'health status' do
+ subject(:migration_run) { described_class.new.perform }
+
+ it 'puts migration on hold when there is autovaccum activity on related tables' do
+ swapout_view_for_table(:postgres_autovacuum_activity, connection: connection)
+ create(
+ :postgres_autovacuum_activity,
+ table: migration.table_name,
+ table_identifier: "public.#{migration.table_name}"
+ )
+
+ expect { migration_run }.to change { migration.reload.on_hold? }.from(false).to(true)
+ end
+ end
end
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 77c4a3431e2..503e331ea2e 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
@@ -1,10 +1,6 @@
# frozen_string_literal: true
-require 'fileutils'
-
RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
- include GitHelpers
-
let!(:lease_uuid) { SecureRandom.uuid }
let!(:lease_key) { "resource_housekeeping:#{resource.id}" }
let(:params) { [resource.id, task, lease_key, lease_uuid] }
@@ -246,39 +242,6 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
subject.perform(resource.id, 'prune', lease_key, lease_uuid)
end
-
- # Create a new commit on a random new branch
- def create_objects(resource)
- rugged = rugged_repo(resource.repository)
- old_commit = rugged.branches.first.target
- new_commit_sha = Rugged::Commit.create(
- rugged,
- message: "hello world #{SecureRandom.hex(6)}",
- author: { email: 'foo@bar', name: 'baz' },
- committer: { email: 'foo@bar', name: 'baz' },
- tree: old_commit.tree,
- parents: [old_commit]
- )
- rugged.references.create("refs/heads/#{SecureRandom.hex(6)}", new_commit_sha)
- end
-
- def packs(resource)
- Dir["#{path_to_repo}/objects/pack/*.pack"]
- end
-
- def packed_refs(resource)
- path = File.join(path_to_repo, 'packed-refs')
- FileUtils.touch(path)
- File.read(path)
- end
-
- def path_to_repo
- @path_to_repo ||= File.join(TestEnv.repos_path, resource.repository.relative_path)
- end
-
- def bitmap_path(pack)
- pack.sub(/\.pack\z/, '.bitmap')
- end
end
context 'with bitmaps enabled' do