summaryrefslogtreecommitdiff
path: root/spec/support/shared_examples
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support/shared_examples')
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb4
-rw-r--r--spec/support/shared_examples/ci/retryable_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/controllers/preferred_language_switcher_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/features/access_tokens_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/features/confidential_notes_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/packages_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/runners_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb304
-rw-r--r--spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/features/variable_list_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/finders/issues_finder_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/mutations/incident_management/timeline_events_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/graphql/notes_creation_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/lib/cache_helpers_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/lib/email/email_shared_examples.rb140
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb46
-rw-r--r--spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/sentry/client_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/mailers/notify_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb150
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/models/concerns/limitable_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb2
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb84
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb75
-rw-r--r--spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb119
-rw-r--r--spec/support/shared_examples/requests/api/discussions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb170
-rw-r--r--spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb77
-rw-r--r--spec/support/shared_examples/requests/api/issues_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/members_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/alert_management_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/base_rpm_service_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb112
-rw-r--r--spec/support/shared_examples/services/merge_status_updated_trigger_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/uploaders/object_storage_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb56
60 files changed, 1540 insertions, 357 deletions
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
index fa048b76e18..7df4b7635d3 100644
--- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -3,6 +3,7 @@
RSpec.shared_examples 'multiple issue boards' do
context 'authorized user' do
before do
+ stub_feature_flags(apollo_boards: false)
parent.add_maintainer(user)
login_as(user)
@@ -121,6 +122,7 @@ RSpec.shared_examples 'multiple issue boards' do
context 'unauthorized user' do
before do
+ stub_feature_flags(apollo_boards: false)
visit boards_path
wait_for_requests
end
diff --git a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
index cd4432af4ed..a9edf18d562 100644
--- a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
+++ b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
@@ -35,7 +35,7 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
it 'does not import wiki' do
expect(subject).to receive(:source_wiki_exists?).and_return(false)
- expect(parent.wiki).not_to receive(:ensure_repository)
+ expect(parent.wiki).not_to receive(:create_wiki_repository)
expect(parent.wiki.repository).not_to receive(:ensure_repository)
expect { subject.run }.not_to raise_error
@@ -75,7 +75,7 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
describe 'unsuccessful response' do
shared_examples 'does not raise an error' do
it 'does not raise an error' do
- expect(parent.wiki).not_to receive(:ensure_repository)
+ expect(parent.wiki).not_to receive(:create_wiki_repository)
expect(parent.wiki.repository).not_to receive(:ensure_repository)
expect { subject.run }.not_to raise_error
diff --git a/spec/support/shared_examples/ci/retryable_shared_examples.rb b/spec/support/shared_examples/ci/retryable_shared_examples.rb
new file mode 100644
index 00000000000..4622dbe4e31
--- /dev/null
+++ b/spec/support/shared_examples/ci/retryable_shared_examples.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a retryable job' do
+ describe '#enqueue_immediately?' do
+ it 'defaults to false' do
+ expect(subject.enqueue_immediately?).to eq(false)
+ end
+ end
+
+ describe '#set_enqueue_immediately!' do
+ it 'changes #enqueue_immediately? to true' do
+ expect { subject.set_enqueue_immediately! }
+ .to change(subject, :enqueue_immediately?).from(false).to(true)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/preferred_language_switcher_shared_examples.rb b/spec/support/shared_examples/controllers/preferred_language_switcher_shared_examples.rb
new file mode 100644
index 00000000000..74456e62eb8
--- /dev/null
+++ b/spec/support/shared_examples/controllers/preferred_language_switcher_shared_examples.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'switches to user preferred language' do |msg_id_example|
+ context 'with preferred_language in cookies' do
+ render_views
+ let(:user_preferred_language) { 'zh_CN' }
+
+ subject { get :new }
+
+ before do
+ cookies['preferred_language'] = user_preferred_language
+ end
+
+ it 'renders new template with cookies preferred language' do
+ expect(subject).to render_template(:new)
+ expect(response).to have_gitlab_http_status(:ok)
+
+ expected_text = Gitlab::I18n.with_locale(user_preferred_language) { _(msg_id_example) }
+ expect(response.body).to include(expected_text)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/access_tokens_shared_examples.rb b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
index cd255abd7a8..32a7b32ac72 100644
--- a/spec/support/shared_examples/features/access_tokens_shared_examples.rb
+++ b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
@@ -9,13 +9,7 @@ RSpec.shared_examples 'resource access tokens missing access rights' do
end
RSpec.shared_examples 'resource access tokens creation' do |resource_type|
- def active_resource_access_tokens
- find("[data-testid='active-tokens']")
- end
-
- def created_resource_access_token
- find_field('new-access-token').value
- end
+ include Spec::Support::Helpers::AccessTokenHelpers
it 'allows creation of an access token', :aggregate_failures do
name = 'My access token'
@@ -34,12 +28,12 @@ RSpec.shared_examples 'resource access tokens creation' do |resource_type|
click_on "Create #{resource_type} access token"
- expect(active_resource_access_tokens).to have_text(name)
- expect(active_resource_access_tokens).to have_text('in')
- expect(active_resource_access_tokens).to have_text('read_api')
- expect(active_resource_access_tokens).to have_text('read_repository')
- expect(active_resource_access_tokens).to have_text('Guest')
- expect(created_resource_access_token).not_to be_empty
+ expect(active_access_tokens).to have_text(name)
+ expect(active_access_tokens).to have_text('in')
+ expect(active_access_tokens).to have_text('read_api')
+ expect(active_access_tokens).to have_text('read_repository')
+ expect(active_access_tokens).to have_text('Guest')
+ expect(created_access_token).to match(/[\w-]{20}/)
end
end
@@ -105,14 +99,14 @@ RSpec.shared_examples 'resource access tokens creation disallowed' do |error_mes
end
RSpec.shared_examples 'active resource access tokens' do
- def active_resource_access_tokens
+ def active_access_tokens
find("[data-testid='active-tokens']")
end
it 'shows active access tokens' do
visit resource_settings_access_tokens_path
- expect(active_resource_access_tokens).to have_text(resource_access_token.name)
+ expect(active_access_tokens).to have_text(resource_access_token.name)
end
context 'when User#time_display_relative is false' do
@@ -123,13 +117,13 @@ RSpec.shared_examples 'active resource access tokens' do
it 'shows absolute times for expires_at' do
visit resource_settings_access_tokens_path
- expect(active_resource_access_tokens).to have_text(PersonalAccessToken.last.expires_at.strftime('%b %-d'))
+ expect(active_access_tokens).to have_text(PersonalAccessToken.last.expires_at.strftime('%b %-d'))
end
end
end
RSpec.shared_examples 'inactive resource access tokens' do |no_active_tokens_text|
- def active_resource_access_tokens
+ def active_access_tokens
find("[data-testid='active-tokens']")
end
@@ -137,14 +131,14 @@ RSpec.shared_examples 'inactive resource access tokens' do |no_active_tokens_tex
visit resource_settings_access_tokens_path
accept_gl_confirm(button_text: 'Revoke') { click_on 'Revoke' }
- expect(active_resource_access_tokens).to have_text(no_active_tokens_text)
+ expect(active_access_tokens).to have_text(no_active_tokens_text)
end
it 'removes expired tokens from active section' do
resource_access_token.update!(expires_at: 5.days.ago)
visit resource_settings_access_tokens_path
- expect(active_resource_access_tokens).to have_text(no_active_tokens_text)
+ expect(active_access_tokens).to have_text(no_active_tokens_text)
end
context 'when resource access token creation is not allowed' do
@@ -156,7 +150,7 @@ RSpec.shared_examples 'inactive resource access tokens' do |no_active_tokens_tex
visit resource_settings_access_tokens_path
accept_gl_confirm(button_text: 'Revoke') { click_on 'Revoke' }
- expect(active_resource_access_tokens).to have_text(no_active_tokens_text)
+ expect(active_access_tokens).to have_text(no_active_tokens_text)
end
end
end
diff --git a/spec/support/shared_examples/features/confidential_notes_shared_examples.rb b/spec/support/shared_examples/features/confidential_notes_shared_examples.rb
new file mode 100644
index 00000000000..289da025af6
--- /dev/null
+++ b/spec/support/shared_examples/features/confidential_notes_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.shared_examples 'confidential notes on issuables' do
+ include Spec::Support::Helpers::Features::NotesHelpers
+
+ context 'when user does not have permissions' do
+ it 'does not show confidential note checkbox' do
+ issuable_parent.add_guest(user)
+ sign_in(user)
+ visit(issuable_path)
+
+ page.within('.new-note') do
+ expect(page).not_to have_selector('[data-testid="internal-note-checkbox"]')
+ end
+ end
+ end
+
+ context 'when user has permissions' do
+ it 'creates confidential note' do
+ issuable_parent.add_reporter(user)
+ sign_in(user)
+ visit(issuable_path)
+
+ find('[data-testid="internal-note-checkbox"]').click
+ add_note('Confidential note')
+
+ page.within('.note-header') do
+ expect(page).to have_selector('[data-testid="internal-note-indicator"]')
+ 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 7863548e7f3..f01e3c88dad 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -313,5 +313,21 @@ RSpec.shared_examples 'edits content using the content editor' do
expect(page).not_to have_css(suggestions_dropdown)
end
+
+ it 'scrolls selected item into view when navigating with keyboard' do
+ type_in_content_editor ':'
+
+ expect(find(suggestions_dropdown)).to have_text('hundred points symbol')
+
+ expect(dropdown_scroll_top).to be 0
+
+ send_keys :arrow_up
+
+ expect(dropdown_scroll_top).to be > 100
+ end
+
+ def dropdown_scroll_top
+ evaluate_script("document.querySelector('#{suggestions_dropdown} .gl-new-dropdown-inner').scrollTop")
+ end
end
end
diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
index 456175e7113..2cfe353d5d7 100644
--- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -21,15 +21,10 @@ RSpec.shared_examples 'a creatable merge request' do
expect(page).to have_content user.name
end
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
-
+ click_button 'Select milestone'
+ click_button milestone.title
expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
+ expect(page).to have_button milestone.title
click_button 'Labels'
page.within '.dropdown-menu-labels' do
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index 2fff4137934..ea6d1655694 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -20,14 +20,10 @@ RSpec.shared_examples 'an editable merge request' do
expect(page).to have_content user.name
end
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
+ click_button 'Select milestone'
+ click_button milestone.title
expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
+ expect(page).to have_button milestone.title
click_button 'Labels'
page.within '.dropdown-menu-labels' do
diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb
index 7aad5e2de80..f09cf0613a1 100644
--- a/spec/support/shared_examples/features/packages_shared_examples.rb
+++ b/spec/support/shared_examples/features/packages_shared_examples.rb
@@ -14,7 +14,7 @@ RSpec.shared_examples 'packages list' do |check_project_name: false|
end
def package_table_row(index)
- page.all("#{packages_table_selector} > [data-testid=\"package-row\"]")[index].text
+ page.all("#{packages_table_selector} [data-testid=\"package-row\"]")[index].text
end
end
diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb
index 1d4af944187..a7bc19da45f 100644
--- a/spec/support/shared_examples/features/runners_shared_examples.rb
+++ b/spec/support/shared_examples/features/runners_shared_examples.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
RSpec.shared_examples 'shows and resets runner registration token' do
- include Spec::Support::Helpers::ModalHelpers
include Spec::Support::Helpers::Features::RunnersHelpers
+ include Spec::Support::Helpers::ModalHelpers
before do
click_on dropdown_text
@@ -146,6 +146,23 @@ RSpec.shared_examples 'pauses, resumes and deletes a runner' do
end
end
+RSpec.shared_examples 'deletes runners in bulk' do
+ describe 'when selecting all for deletion', :js do
+ before do
+ check s_('Runners|Select all')
+ click_button s_('Runners|Delete selected')
+
+ within_modal do
+ click_on "Permanently delete #{runner_count} runners"
+ end
+
+ wait_for_requests
+ end
+
+ it_behaves_like 'shows no runners registered'
+ end
+end
+
RSpec.shared_examples 'filters by tag' do
it 'shows correct runner when tag matches' do
expect(page).to have_content found_runner
diff --git a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb
new file mode 100644
index 00000000000..4d242d0e719
--- /dev/null
+++ b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb
@@ -0,0 +1,304 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a redacted search results' do
+ let_it_be(:user) { create(:user) }
+
+ let_it_be(:accessible_group) { create(:group, :private) }
+ let_it_be(:accessible_project) { create(:project, :repository, :private, name: 'accessible_project') }
+
+ let_it_be(:group_member) { create(:group_member, group: accessible_group, user: user) }
+
+ let_it_be(:inaccessible_group) { create(:group, :private) }
+ let_it_be(:inaccessible_project) { create(:project, :repository, :private, name: 'inaccessible_project') }
+
+ let(:search) { 'anything' }
+
+ subject(:result) { search_service.search_objects }
+
+ def found_blob(project)
+ Gitlab::Search::FoundBlob.new(project: project)
+ end
+
+ def found_wiki_page(project)
+ Gitlab::Search::FoundWikiPage.new(found_blob(project))
+ end
+
+ def ar_relation(klass, *objects)
+ klass.id_in(objects.map(&:id))
+ end
+
+ def kaminari_array(*objects)
+ Kaminari.paginate_array(objects).page(1).per(20)
+ end
+
+ before do
+ accessible_project.add_maintainer(user)
+
+ allow(search_service)
+ .to receive_message_chain(:search_results, :objects)
+ .and_return(unredacted_results)
+ end
+
+ context 'for issues' do
+ let(:readable) { create(:issue, project: accessible_project) }
+ let(:unreadable) { create(:issue, project: inaccessible_project) }
+ let(:unredacted_results) { ar_relation(Issue, readable, unreadable) }
+ let(:scope) { 'issues' }
+
+ it 'redacts the inaccessible issue' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'Issue', id: unreadable.id, ability: :read_issue }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+ end
+
+ context 'for notes' do
+ let(:readable_merge_request) do
+ create(:merge_request_with_diffs, target_project: accessible_project, source_project: accessible_project)
+ end
+
+ let(:readable_note_on_commit) { create(:note_on_commit, project: accessible_project) }
+ let(:readable_diff_note) { create(:diff_note_on_commit, project: accessible_project) }
+ let(:readable_note_on_mr) do
+ create(:discussion_note_on_merge_request, noteable: readable_merge_request, project: accessible_project)
+ end
+
+ let(:readable_diff_note_on_mr) do
+ create(:diff_note_on_merge_request, noteable: readable_merge_request, project: accessible_project)
+ end
+
+ let(:readable_note_on_project_snippet) do
+ create(:note_on_project_snippet, noteable: readable_merge_request, project: accessible_project)
+ end
+
+ let(:unreadable_merge_request) do
+ create(:merge_request_with_diffs, target_project: inaccessible_project, source_project: inaccessible_project)
+ end
+
+ let(:unreadable_note_on_commit) { create(:note_on_commit, project: inaccessible_project) }
+ let(:unreadable_diff_note) { create(:diff_note_on_commit, project: inaccessible_project) }
+ let(:unreadable_note_on_mr) do
+ create(:discussion_note_on_merge_request, noteable: unreadable_merge_request, project: inaccessible_project)
+ end
+
+ let(:unreadable_note_on_project_snippet) do
+ create(:note_on_project_snippet, noteable: unreadable_merge_request, project: inaccessible_project)
+ end
+
+ let(:unredacted_results) do
+ ar_relation(Note,
+ readable_note_on_commit,
+ readable_diff_note,
+ readable_note_on_mr,
+ readable_diff_note_on_mr,
+ readable_note_on_project_snippet,
+ unreadable_note_on_commit,
+ unreadable_diff_note,
+ unreadable_note_on_mr,
+ unreadable_note_on_project_snippet)
+ end
+
+ let(:scope) { 'notes' }
+
+ it 'redacts the inaccessible notes' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'Note', id: unreadable_note_on_commit.id, ability: :read_note },
+ { class_name: 'DiffNote', id: unreadable_diff_note.id, ability: :read_note },
+ { class_name: 'DiscussionNote', id: unreadable_note_on_mr.id, ability: :read_note },
+ { class_name: 'Note', id: unreadable_note_on_project_snippet.id, ability: :read_note }
+ ])))
+
+ expect(result).to contain_exactly(readable_note_on_commit,
+ readable_diff_note,
+ readable_note_on_mr,
+ readable_diff_note_on_mr,
+ readable_note_on_project_snippet)
+ end
+ end
+
+ context 'for merge_requests' do
+ let(:readable) { create(:merge_request, source_project: accessible_project) }
+ let(:unreadable) { create(:merge_request, source_project: inaccessible_project) }
+ let(:unredacted_results) { ar_relation(MergeRequest, readable, unreadable) }
+ let(:scope) { 'merge_requests' }
+
+ it 'redacts the inaccessible merge request' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'MergeRequest', id: unreadable.id, ability: :read_merge_request }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+
+ context 'with :with_api_entity_associations' do
+ let(:unredacted_results) { ar_relation(MergeRequest.with_api_entity_associations, readable, unreadable) }
+
+ it_behaves_like "redaction limits N+1 queries", limit: 8
+ end
+ end
+
+ context 'for blobs' do
+ let(:readable) { found_blob(accessible_project) }
+ let(:unreadable) { found_blob(inaccessible_project) }
+ let(:unredacted_results) { kaminari_array(readable, unreadable) }
+ let(:scope) { 'blobs' }
+
+ it 'redacts the inaccessible blob' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'Gitlab::Search::FoundBlob', id: unreadable.id, ability: :read_blob }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+ end
+
+ context 'for wiki blobs' do
+ let(:readable) { found_wiki_page(accessible_project) }
+ let(:unreadable) { found_wiki_page(inaccessible_project) }
+ let(:unredacted_results) { kaminari_array(readable, unreadable) }
+ let(:scope) { 'wiki_blobs' }
+
+ it 'redacts the inaccessible blob' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'Gitlab::Search::FoundWikiPage', id: unreadable.id, ability: :read_wiki_page }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+ end
+
+ context 'for project snippets' do
+ let(:readable) { create(:project_snippet, project: accessible_project) }
+ let(:unreadable) { create(:project_snippet, project: inaccessible_project) }
+ let(:unredacted_results) { ar_relation(ProjectSnippet, readable, unreadable) }
+ let(:scope) { 'snippet_titles' }
+
+ it 'redacts the inaccessible snippet' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'ProjectSnippet', id: unreadable.id, ability: :read_snippet }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+
+ context 'with :with_api_entity_associations' do
+ it_behaves_like "redaction limits N+1 queries", limit: 14
+ end
+ end
+
+ context 'for personal snippets' do
+ let(:readable) { create(:personal_snippet, :private, author: user) }
+ let(:unreadable) { create(:personal_snippet, :private) }
+ let(:unredacted_results) { ar_relation(PersonalSnippet, readable, unreadable) }
+ let(:scope) { 'snippet_titles' }
+
+ it 'redacts the inaccessible snippet' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'PersonalSnippet', id: unreadable.id, ability: :read_snippet }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+
+ context 'with :with_api_entity_associations' do
+ it_behaves_like "redaction limits N+1 queries", limit: 4
+ end
+ end
+
+ context 'for commits' do
+ let(:readable) { accessible_project.commit }
+ let(:unreadable) { inaccessible_project.commit }
+ let(:unredacted_results) { kaminari_array(readable, unreadable) }
+ let(:scope) { 'commits' }
+
+ it 'redacts the inaccessible commit' do
+ expect(search_service.send(:logger))
+ .to receive(:error)
+ .with(hash_including(
+ message: "redacted_search_results",
+ current_user_id: user.id,
+ query: search,
+ filtered: array_including(
+ [
+ { class_name: 'Commit', id: unreadable.id, ability: :read_commit }
+ ])))
+
+ expect(result).to contain_exactly(readable)
+ end
+ end
+
+ context 'for users' do
+ let(:other_user) { create(:user) }
+ let(:unredacted_results) { ar_relation(User, user, other_user) }
+ let(:scope) { 'users' }
+
+ it 'passes the users through' do
+ # Users are always visible to everyone
+ expect(result).to contain_exactly(user, other_user)
+ end
+ end
+end
+
+RSpec.shared_examples "redaction limits N+1 queries" do |limit:|
+ it 'does not exceed the query limit' do
+ # issuing the query to remove the data loading call
+ unredacted_results.to_a
+
+ # only the calls from the redaction are left
+ query = ActiveRecord::QueryRecorder.new { result }
+
+ # these are the project authorization calls, which are not preloaded
+ expect(query.count).to be <= limit
+ end
+end
diff --git a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
index 84dc2b20ddc..cc74c977064 100644
--- a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
+++ b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
@@ -1,23 +1,23 @@
# frozen_string_literal: true
RSpec.shared_examples 'search timeouts' do |scope|
+ let(:additional_params) { {} }
+
context 'when search times out' do
before do
- stub_feature_flags(search_page_vertical_nav: false)
allow_next_instance_of(SearchService) do |service|
allow(service).to receive(:search_objects).and_raise(ActiveRecord::QueryCanceled)
end
- visit(search_path(search: 'test', scope: scope))
+ visit(search_path(search: 'test', scope: scope, **additional_params))
end
it 'renders timeout information' do
- # expect(page).to have_content('This endpoint has been requested too many times.')
expect(page).to have_content('Your search timed out')
end
it 'sets tab count to 0' do
- expect(page.find('.search-filter .active')).to have_text('0')
+ expect(page.find('[data-testid="search-filter"] .active')).to have_text('0')
end
end
end
diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb
index d1e5046a39e..f0b72cfaee3 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -31,8 +31,8 @@ RSpec.shared_examples 'variable list' do |is_admin|
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Protected"] svg[data-testid="mobile-issue-close-icon"]')).to be_present
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']").text).to eq('key')
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Options')}']")).to have_content(s_('CiVariables|Protected'))
end
end
@@ -46,26 +46,26 @@ RSpec.shared_examples 'variable list' do |is_admin|
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="close-icon"]')).to be_present
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']").text).to eq('key')
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Options')}']")).not_to have_content(s_('CiVariables|Masked'))
end
end
it 'reveals and hides variables' do
page.within('[data-testid="ci-variable-table"]') do
expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
- expect(page).to have_content('*' * 17)
+ expect(page).to have_content('*' * 5)
click_button('Reveal value')
expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
expect(first('.js-ci-variable-row td[data-label="Value"]').text).to eq(variable.value)
- expect(page).not_to have_content('*' * 17)
+ expect(page).not_to have_content('*' * 5)
click_button('Hide value')
expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
- expect(page).to have_content('*' * 17)
+ expect(page).to have_content('*' * 5)
end
end
@@ -116,7 +116,8 @@ RSpec.shared_examples 'variable list' do |is_admin|
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="close-icon"]')).to be_present
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Options')}']")).to have_content(s_('CiVariables|Protected'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Options')}']")).not_to have_content(s_('CiVariables|Masked'))
end
end
@@ -144,7 +145,7 @@ RSpec.shared_examples 'variable list' do |is_admin|
end
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="mobile-issue-close-icon"]')).to be_present
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Options')}']")).to have_content(s_('CiVariables|Masked'))
end
end
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 f62c9c00006..8b3a344a841 100644
--- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
+++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
@@ -585,7 +585,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
end
context 'when full-text search is disabled' do
- let(:search_term) { 'somet' }
+ let(:search_term) { 'ometh' }
before do
stub_feature_flags(issues_full_text_search: false)
diff --git a/spec/support/shared_examples/graphql/mutations/incident_management/timeline_events_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/incident_management/timeline_events_shared_examples.rb
new file mode 100644
index 00000000000..fbfd1af2601
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/incident_management/timeline_events_shared_examples.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'timeline event mutation responds with validation error' do |error_message:|
+ it 'responds with a validation error' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to match_array([error_message])
+ end
+end
diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
index 0aa3bf8944f..bdd4dbfe209 100644
--- a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
@@ -78,7 +78,7 @@ RSpec.shared_examples 'a Note mutation when there are rate limit validation erro
context 'when the user is in the allowlist' do
before do
- stub_application_setting(notes_create_limit_allowlist: ["#{current_user.username}"])
+ stub_application_setting(notes_create_limit_allowlist: [current_user.username.to_s])
end
it_behaves_like 'a Note mutation that creates a Note'
diff --git a/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb b/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
index 3017f62a7c9..aadc061f175 100644
--- a/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/resolvers/packages_resolvers_shared_examples.rb
@@ -25,7 +25,7 @@ RSpec.shared_examples 'group and projects packages resolver' do
end
%w[CREATED_DESC NAME_DESC VERSION_DESC TYPE_ASC].each do |order|
- context "#{order}" do
+ context order.to_s do
let(:args) { { sort: order } }
it { is_expected.to eq([maven_package, conan_package]) }
@@ -33,7 +33,7 @@ RSpec.shared_examples 'group and projects packages resolver' do
end
%w[CREATED_ASC NAME_ASC VERSION_ASC TYPE_DESC].each do |order|
- context "#{order}" do
+ context order.to_s do
let(:args) { { sort: order } }
it { is_expected.to eq([conan_package, maven_package]) }
diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
index 2d7da9f9f00..67576a18c80 100644
--- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
@@ -86,4 +86,25 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
eq("`alpha` and `deprecated` arguments cannot be passed at the same time")
)
end
+
+ describe 'visible?' do
+ let(:ctx) { {} }
+
+ it 'defaults to true' do
+ expect(subject).to be_visible(ctx)
+ end
+
+ context 'when subject is deprecated' do
+ let(:arguments) { { deprecated: { milestone: '1.10', reason: :renamed } } }
+
+ it 'defaults to true' do
+ expect(subject(arguments)).to be_visible(ctx)
+ end
+
+ it 'returns false if `remove_deprecated` is true in context' do
+ ctx = { remove_deprecated: true }
+ expect(subject(arguments)).not_to be_visible(ctx)
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
index 2e00abe2f8e..6cdd7954b5f 100644
--- a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
+++ b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb
@@ -129,6 +129,7 @@ RSpec.shared_examples_for 'collection cache helper' do
before do
allow(::Gitlab::Metrics::WebTransaction).to receive(:current).and_return(transaction)
allow(transaction).to receive(:increment)
+ allow(Gitlab::ApplicationContext).to receive(:current_context_attribute).with(any_args).and_call_original
allow(Gitlab::ApplicationContext).to receive(:current_context_attribute).with(:caller_id).and_return(caller_id)
end
diff --git a/spec/support/shared_examples/lib/email/email_shared_examples.rb b/spec/support/shared_examples/lib/email/email_shared_examples.rb
new file mode 100644
index 00000000000..26655a71fec
--- /dev/null
+++ b/spec/support/shared_examples/lib/email/email_shared_examples.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+# Set the particular setting as a key-value pair
+# Setting method is different depending on klass and must be defined in the calling spec
+def stub_email_setting(key_value_pairs)
+ case setting_name
+ when :incoming_email
+ stub_incoming_email_setting(key_value_pairs)
+ when :service_desk_email
+ stub_service_desk_email_setting(key_value_pairs)
+ end
+end
+
+RSpec.shared_examples_for 'enabled? method for email' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.enabled? }
+
+ where(:value, :address, :result) do
+ false | nil | false
+ false | 'replies+%{key}@example.com' | false
+ true | nil | false
+ true | 'replies+%{key}@example.com' | true
+ end
+
+ with_them do
+ before do
+ stub_email_setting(enabled: value)
+ stub_email_setting(address: address)
+ end
+
+ it { is_expected.to eq result }
+ end
+end
+
+RSpec.shared_examples_for 'supports_wildcard? method for email' do
+ subject { described_class.supports_wildcard? }
+
+ before do
+ stub_incoming_email_setting(address: value)
+ end
+
+ context 'when address contains the wildcard placeholder' do
+ let(:value) { 'replies+%{key}@example.com' }
+
+ it 'confirms that wildcard is supported' do
+ expect(subject).to be_truthy
+ end
+ end
+
+ context "when address doesn't contain the wildcard placeholder" do
+ let(:value) { 'replies@example.com' }
+
+ it 'returns that wildcard is not supported' do
+ expect(subject).to be_falsey
+ end
+ end
+
+ context 'when address is nil' do
+ let(:value) { nil }
+
+ it 'returns that wildcard is not supported' do
+ expect(subject).to be_falsey
+ end
+ end
+end
+
+RSpec.shared_examples_for 'unsubscribe_address method for email' do
+ before do
+ stub_incoming_email_setting(address: 'replies+%{key}@example.com')
+ end
+
+ it 'returns the address with interpolated reply key and unsubscribe suffix' do
+ expect(described_class.unsubscribe_address('key'))
+ .to eq("replies+key#{Gitlab::Email::Common::UNSUBSCRIBE_SUFFIX}@example.com")
+ end
+end
+
+RSpec.shared_examples_for 'key_from_fallback_message_id method for email' do
+ it 'returns reply key' do
+ expect(described_class.key_from_fallback_message_id('reply-key@localhost')).to eq('key')
+ end
+end
+
+RSpec.shared_examples_for 'supports_issue_creation? method for email' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.supports_issue_creation? }
+
+ where(:enabled_value, :supports_wildcard_value, :result) do
+ false | false | false
+ false | true | false
+ true | false | false
+ true | true | true
+ end
+
+ with_them do
+ before do
+ allow(described_class).to receive(:enabled?).and_return(enabled_value)
+ allow(described_class).to receive(:supports_wildcard?).and_return(supports_wildcard_value)
+ end
+
+ it { is_expected.to eq result }
+ end
+end
+
+RSpec.shared_examples_for 'reply_address method for email' do
+ before do
+ stub_incoming_email_setting(address: "replies+%{key}@example.com")
+ end
+
+ it "returns the address with an interpolated reply key" do
+ expect(described_class.reply_address("key")).to eq("replies+key@example.com")
+ end
+end
+
+RSpec.shared_examples_for 'scan_fallback_references method for email' do
+ let(:references) do
+ '<issue_1@localhost>' \
+ ' <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost>' \
+ ',<exchange@microsoft.com>'
+ end
+
+ it 'returns reply key' do
+ expect(described_class.scan_fallback_references(references))
+ .to eq(%w[issue_1@localhost
+ reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost
+ exchange@microsoft.com])
+ end
+end
+
+RSpec.shared_examples_for 'common email methods' do
+ it_behaves_like 'enabled? method for email'
+ it_behaves_like 'supports_wildcard? method for email'
+ it_behaves_like 'key_from_fallback_message_id method for email'
+ it_behaves_like 'supports_issue_creation? method for email'
+ it_behaves_like 'reply_address method for email'
+ it_behaves_like 'unsubscribe_address method for email'
+ it_behaves_like 'scan_fallback_references method for email'
+end
diff --git a/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb
index d14216ec5ff..22b4f9f583c 100644
--- a/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/database/reestablished_connection_stack_shared_examples.rb
@@ -3,9 +3,6 @@
RSpec.shared_context 'reconfigures connection stack' do |db_config_name|
before do
skip_if_multiple_databases_not_setup
-
- # Due to lib/gitlab/database/load_balancing/configuration.rb:92 requiring RequestStore
- # we cannot use stub_feature_flags(force_no_sharing_primary_model: true)
Gitlab::Database.database_base_models.each do |_, model_class|
allow(model_class.load_balancer.configuration).to receive(:use_dedicated_connection?).and_return(true)
end
diff --git a/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb
index db2f2f2d0f0..e97b9ad969f 100644
--- a/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/event_store_shared_examples.rb
@@ -15,6 +15,16 @@ RSpec.shared_examples 'subscribes to event' do
it_behaves_like 'an idempotent worker'
end
+RSpec.shared_examples 'ignores the published event' do
+ include AfterNextHelpers
+
+ it 'does not consume the published event', :sidekiq_inline do
+ expect_next(described_class).not_to receive(:handle_event)
+
+ ::Gitlab::EventStore.publish(event)
+ end
+end
+
def consume_event(subscriber:, event:)
subscriber.new.perform(event.class.name, event.data)
end
diff --git a/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb
deleted file mode 100644
index fdca326dbea..00000000000
--- a/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'tracks assignment and records the subject' do |experiment, subject_type|
- before do
- stub_experiments(experiment => true)
- end
-
- it 'tracks the assignment', :experiment do
- expect(experiment(experiment))
- .to track(:assignment)
- .with_context(subject_type => subject)
- .on_next_instance
-
- action
- end
-
- it 'records the subject' do
- expect(Experiment).to receive(:add_subject).with(experiment.to_s, variant: anything, subject: subject)
-
- action
- end
-end
diff --git a/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb
new file mode 100644
index 00000000000..f26b9a4a7bd
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+def raw_repo_without_container(repository)
+ Gitlab::Git::Repository.new(repository.shard,
+ "#{repository.disk_path}.git",
+ repository.repo_type.identifier_for_container(repository.container),
+ repository.container.full_path)
+end
+
+RSpec.shared_examples 'Gitaly feature flag actors are inferred from repository' do
+ it 'captures correct actors' do
+ service.repository_actor = repository
+
+ expect(service.repository_actor.flipper_id).to eql(repository.flipper_id)
+
+ if expected_project.nil?
+ expect(service.project_actor).to be(nil)
+ else
+ expect(service.project_actor.flipper_id).to eql(expected_project.flipper_id)
+ end
+
+ if expected_group.nil?
+ expect(service.group_actor).to be(nil)
+ else
+ expect(service.group_actor.flipper_id).to eql(expected_group.flipper_id)
+ end
+ end
+
+ it 'does not issues SQL queries after the first invocation' do
+ service.repository_actor = repository
+
+ service.repository_actor
+ service.project_actor
+ service.group_actor
+
+ recorder = ActiveRecord::QueryRecorder.new do
+ 3.times do
+ service.repository_actor
+ service.project_actor
+ service.group_actor
+ end
+ end
+
+ expect(recorder.count).to be(0)
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb
index 4b4a7f4ce9d..a2a4c57d62e 100644
--- a/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/template/template_shared_examples.rb
@@ -52,7 +52,7 @@ RSpec.shared_examples 'acts as branch pipeline' do |jobs|
context 'when branch pipeline' do
let(:pipeline_branch) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch) }
- let(:pipeline) { service.execute!(:push).payload }
+ let(:pipeline) { service.execute(:push).payload }
it 'includes a job' do
expect(pipeline.builds.pluck(:name)).to match_array(jobs)
diff --git a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
index 71b32005c55..e0b411e1e2a 100644
--- a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
+++ b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
@@ -78,8 +78,8 @@ end
# Expects to following variables:
# - subject
# - sentry_api_response
-# - sentry_url, token - only if enabled_by_default: false
-RSpec.shared_examples 'Sentry API response size limit' do |enabled_by_default: false|
+# - sentry_url, token
+RSpec.shared_examples 'Sentry API response size limit' do
let(:invalid_deep_size) { instance_double(Gitlab::Utils::DeepSize, valid?: false) }
before do
@@ -89,35 +89,8 @@ RSpec.shared_examples 'Sentry API response size limit' do |enabled_by_default: f
.and_return(invalid_deep_size)
end
- if enabled_by_default
- it 'raises an exception when response is too large' do
- expect { subject }.to raise_error(ErrorTracking::SentryClient::ResponseInvalidSizeError,
- 'Sentry API response is too big. Limit is 1 MB.')
- end
- else
- context 'when guarded by feature flag' do
- let(:client) do
- ErrorTracking::SentryClient.new(sentry_url, token, validate_size_guarded_by_feature_flag: feature_flag)
- end
-
- context 'with feature flag enabled' do
- let(:feature_flag) { true }
-
- it 'raises an exception when response is too large' do
- expect { subject }.to raise_error(ErrorTracking::SentryClient::ResponseInvalidSizeError,
- 'Sentry API response is too big. Limit is 1 MB.')
- end
- end
-
- context 'with feature flag disabled' do
- let(:feature_flag) { false }
-
- it 'does not check the limit and thus not raise' do
- expect { subject }.not_to raise_error
-
- expect(Gitlab::Utils::DeepSize).not_to have_received(:new)
- end
- end
- end
+ it 'raises an exception when response is too large' do
+ expect { subject }.to raise_error(ErrorTracking::SentryClient::ResponseInvalidSizeError,
+ 'Sentry API response is too big. Limit is 1 MB.')
end
end
diff --git a/spec/support/shared_examples/mailers/notify_shared_examples.rb b/spec/support/shared_examples/mailers/notify_shared_examples.rb
index 20ed380fb18..919311adc96 100644
--- a/spec/support/shared_examples/mailers/notify_shared_examples.rb
+++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb
@@ -44,12 +44,12 @@ end
RSpec.shared_examples 'an email with X-GitLab headers containing IDs' do
it 'has X-GitLab-*-ID header' do
- is_expected.to have_header "X-GitLab-#{model.class.name}-ID", "#{model.id}"
+ is_expected.to have_header "X-GitLab-#{model.class.name}-ID", model.id.to_s
end
it 'has X-GitLab-*-IID header if model has iid defined' do
if model.respond_to?(:iid)
- is_expected.to have_header "X-GitLab-#{model.class.name}-IID", "#{model.iid}"
+ is_expected.to have_header "X-GitLab-#{model.class.name}-IID", model.iid.to_s
else
expect(subject.header["X-GitLab-#{model.class.name}-IID"]).to eq nil
end
diff --git a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
index 3f187a7e9e4..ef4b08c7865 100644
--- a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
+++ b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
@@ -37,7 +37,8 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
Gitlab::WithRequestStore.with_request_store do
subscriber.sql(event)
- expected = if db_role == :primary
+ expected = case db_role
+ when :primary
transform_hash(expected_payload_defaults, {
db_count: record_query ? 1 : 0,
db_write_count: record_write_query ? 1 : 0,
@@ -53,7 +54,7 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
db_primary_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
"db_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
})
- elsif db_role == :replica
+ when :replica
transform_hash(expected_payload_defaults, {
db_count: record_query ? 1 : 0,
db_write_count: record_write_query ? 1 : 0,
diff --git a/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb
new file mode 100644
index 00000000000..f0581333b28
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::BaseSlackNotification do |factory:|
+ describe '#execute' do
+ let_it_be(:project) { create(:project, :repository, :wiki_repo) }
+ let_it_be(:integration) { create(factory, branches_to_be_notified: 'all', project: project) }
+
+ before do
+ stub_request(:post, integration.webhook)
+ end
+
+ it 'uses only known events', :aggregate_failures do
+ described_class::SUPPORTED_EVENTS_FOR_USAGE_LOG.each do |action|
+ expect(
+ Gitlab::UsageDataCounters::HLLRedisCounter.known_event?("i_ecosystem_slack_service_#{action}_notification")
+ ).to be true
+ end
+ end
+
+ context 'when hook data includes a user object' do
+ let_it_be(:user) { create_default(:user) }
+
+ shared_examples 'increases the usage data counter' do |event_name|
+ subject(:execute) { integration.execute(data) }
+
+ it 'increases the usage data counter' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:track_event).with(event_name, values: user.id).and_call_original
+
+ execute
+ end
+
+ it_behaves_like 'Snowplow event tracking' do
+ let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
+ let(:category) { described_class.to_s }
+ let(:action) { 'perform_integrations_action' }
+ let(:namespace) { project.namespace }
+ let(:label) { 'redis_hll_counters.ecosystem.ecosystem_total_unique_counts_monthly' }
+ let(:property) { event_name }
+ end
+ end
+
+ context 'when event is not supported for usage log' do
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
+
+ it 'does not increase the usage data counter' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .not_to receive(:track_event).with('i_ecosystem_slack_service_pipeline_notification', values: user.id)
+
+ integration.execute(data)
+ end
+ end
+
+ context 'for issue notification' do
+ let_it_be(:issue) { create(:issue, project: project) }
+
+ let(:data) { issue.to_hook_data(user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_issue_notification'
+ end
+
+ context 'for push notification' do
+ let(:data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_push_notification'
+ end
+
+ context 'for deployment notification' do
+ let_it_be(:deployment) { create(:deployment, project: project, user: user) }
+
+ let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.current) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_deployment_notification'
+ end
+
+ context 'for wiki_page notification' do
+ let_it_be(:wiki_page) do
+ create(:wiki_page, wiki: project.wiki, message: 'user created page: Awesome wiki_page')
+ end
+
+ let(:data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') }
+
+ before do
+ # Skip this method that is not relevant to this test to prevent having
+ # to update project which is frozen
+ allow(project.wiki).to receive(:after_wiki_activity)
+ end
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_wiki_page_notification'
+ end
+
+ context 'for merge_request notification' do
+ let_it_be(:merge_request) { create(:merge_request, source_project: project) }
+
+ let(:data) { merge_request.to_hook_data(user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_merge_request_notification'
+ end
+
+ context 'for note notification' do
+ let_it_be(:issue_note) { create(:note_on_issue, project: project, note: 'issue note') }
+
+ let(:data) { Gitlab::DataBuilder::Note.build(issue_note, user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_note_notification'
+ end
+
+ context 'for tag_push notification' do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+ let(:newrev) { '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b' } # gitlab-test: git rev-parse refs/tags/v1.1.0
+ let(:ref) { 'refs/tags/v1.1.0' }
+ let(:data) do
+ Git::TagHooksService.new(project, user, change: { oldrev: oldrev, newrev: newrev, ref: ref }).send(:push_data)
+ end
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_tag_push_notification'
+ end
+
+ context 'for confidential note notification' do
+ let_it_be(:confidential_issue_note) do
+ create(:note_on_issue, project: project, note: 'issue note', confidential: true)
+ end
+
+ let(:data) { Gitlab::DataBuilder::Note.build(confidential_issue_note, user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_note_notification'
+ end
+
+ context 'for confidential issue notification' do
+ let_it_be(:issue) { create(:issue, project: project, confidential: true) }
+
+ let(:data) { issue.to_hook_data(user) }
+
+ it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_issue_notification'
+ end
+ end
+
+ context 'when hook data does not include a user' do
+ let(:data) { Gitlab::DataBuilder::Pipeline.build(create(:ci_pipeline, project: project)) }
+
+ it 'does not increase the usage data counter' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+
+ integration.execute(data)
+ 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 7512a9f2855..974fc8f402a 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
@@ -152,7 +152,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'issue events' do
- let_it_be(:issue) { create(:issue) }
+ let_it_be(:issue) { create(:issue, project: project) }
let(:data) { issue.to_hook_data(user) }
@@ -192,7 +192,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'merge request events' do
- let_it_be(:merge_request) { create(:merge_request) }
+ let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let(:data) { merge_request.to_hook_data(user) }
@@ -210,7 +210,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'wiki page events' do
- let_it_be(:wiki_page) { create(:wiki_page, wiki: project.wiki, message: 'user created page: Awesome wiki_page') }
+ let_it_be(:wiki_page) { create(:wiki_page, wiki: project.wiki, project: project, message: 'user created page: Awesome wiki_page') }
let(:data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') }
@@ -228,7 +228,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'deployment events' do
- let_it_be(:deployment) { create(:deployment) }
+ let_it_be(:deployment) { create(:deployment, project: project) }
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, 'created', Time.current) }
@@ -275,8 +275,8 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
describe 'Push events' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository, creator: user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
before do
allow(chat_integration).to receive_messages(
@@ -327,7 +327,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'on a protected branch' do
- before do
+ before(:all) do
create(:protected_branch, :create_branch_on_repository, project: project, name: 'a-protected-branch')
end
@@ -369,7 +369,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'on a protected branch with protected branches defined using wildcards' do
- before do
+ before(:all) do
create(:protected_branch, :create_branch_on_repository, repository_branch_name: '1-stable', project: project, name: '*-stable')
end
@@ -450,8 +450,8 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
describe 'Note events' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository, creator: user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
before do
allow(chat_integration).to receive_messages(
@@ -519,8 +519,8 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
describe 'Pipeline events' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository, creator: user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
let(:pipeline) do
create(:ci_pipeline,
project: project, status: status,
@@ -582,7 +582,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'on a protected branch' do
- before do
+ before(:all) do
create(:protected_branch, :create_branch_on_repository, project: project, name: 'a-protected-branch')
end
@@ -612,7 +612,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'on a protected branch with protected branches defined usin wildcards' do
- before do
+ before(:all) do
create(:protected_branch, :create_branch_on_repository, repository_branch_name: '1-stable', project: project, name: '*-stable')
end
@@ -673,7 +673,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
- let(:deployment) do
+ let_it_be(:deployment) do
create(:deployment, :success, project: project, sha: project.commit.sha, ref: project.default_branch)
end
@@ -692,11 +692,11 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
it_behaves_like "triggered #{integration_name} integration", event_type: "deployment"
context 'on a protected branch' do
- before do
+ before(:all) do
create(:protected_branch, :create_branch_on_repository, project: project, name: 'a-protected-branch')
end
- let(:deployment) do
+ let_it_be(:deployment) do
create(:deployment, :success, project: project, sha: project.commit.sha, ref: 'a-protected-branch')
end
diff --git a/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb b/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb
index 3d393e6dcb5..c6d6e00c781 100644
--- a/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'includes Limitable concern' do
describe '#exceeds_limits?' do
- let(:plan_limits) { create(:plan_limits, :default_plan) }
+ let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) }
context 'without plan limits configured' do
it { expect(subject.exceeds_limits?).to eq false }
@@ -26,7 +26,7 @@ RSpec.shared_examples 'includes Limitable concern' do
end
describe 'validations' do
- let(:plan_limits) { create(:plan_limits, :default_plan) }
+ let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) }
it { is_expected.to be_a(Limitable) }
diff --git a/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb b/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
index 174b8609337..ac34ee32c6d 100644
--- a/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
@@ -7,6 +7,11 @@ RSpec.shared_examples 'ttl_expirable' do
it_behaves_like 'having unique enum values'
+ describe 'default values', :freeze_time do
+ it { expect(described_class.new.read_at).to be_like_time(Time.zone.now) }
+ it { expect(described_class.new(read_at: 1.day.ago).read_at).to be_like_time(1.day.ago) }
+ end
+
describe 'validations' do
it { is_expected.to validate_presence_of(:status) }
end
@@ -38,7 +43,7 @@ RSpec.shared_examples 'ttl_expirable' do
end
end
- describe '#read', :freeze_time do
+ describe '#read!', :freeze_time do
let_it_be(:old_read_at) { 1.day.ago }
let_it_be(:item1) { create(class_symbol, read_at: old_read_at) }
diff --git a/spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb
new file mode 100644
index 00000000000..08fab45e41b
--- /dev/null
+++ b/spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::BaseCi do
+ describe 'default values' do
+ it { expect(subject.category).to eq(:ci) }
+ end
+end
diff --git a/spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb
new file mode 100644
index 00000000000..5d7e7633a23
--- /dev/null
+++ b/spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::BaseMonitoring do
+ describe 'default values' do
+ it { expect(subject.category).to eq(:monitoring) }
+ end
+end
diff --git a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
index e35ac9c0d0d..7dfdd24177e 100644
--- a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
@@ -6,6 +6,10 @@ RSpec.shared_examples Integrations::BaseSlashCommands do
it { is_expected.to have_many :chat_names }
end
+ describe 'default values' do
+ it { expect(subject.category).to eq(:chat) }
+ end
+
describe '#valid_token?' do
subject { described_class.new }
diff --git a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
index 31ec25249d7..a764d47d7c0 100644
--- a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
+++ b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
@@ -38,7 +38,7 @@ RSpec.shared_examples Integrations::HasWebHook do
end
describe '#url_variables' do
- it 'returns a string' do
+ it 'returns a hash' do
expect(integration.url_variables).to be_a(Hash)
end
end
diff --git a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb
index 23026167b19..5be0f6349ea 100644
--- a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb
+++ b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb
@@ -199,7 +199,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze|
expect(component_file)
.to receive(:update_column)
- .with(:file_store, ::Packages::PackageFileUploader::Store::LOCAL)
+ .with('file_store', ::Packages::PackageFileUploader::Store::LOCAL)
.and_call_original
expect { subject }.to change { component_file.size }.from(nil).to(74)
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index b1aa90449e1..7e69a6663d5 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -161,9 +161,10 @@ RSpec.shared_examples 'wiki model' do
let(:wiki_pages) { subject.list_pages }
before do
- subject.create_page('index', 'This is an index')
+ # The order is intentional
subject.create_page('index2', 'This is an index2')
- subject.create_page('an index3', 'This is an index3')
+ subject.create_page('index', 'This is an index')
+ subject.create_page('index3', 'This is an index3')
end
it 'returns an array of WikiPage instances' do
@@ -183,13 +184,47 @@ RSpec.shared_examples 'wiki model' do
context 'with limit option' do
it 'returns limited set of pages' do
- expect(subject.list_pages(limit: 1).count).to eq(1)
+ expect(
+ subject.list_pages(limit: 1).map(&:title)
+ ).to eql(%w[index])
+ end
+
+ it 'returns all set of pages if limit is more than the total pages' do
+ expect(subject.list_pages(limit: 4).count).to eq(3)
+ end
+
+ it 'returns all set of pages if limit is 0' do
+ expect(subject.list_pages(limit: 0).count).to eq(3)
+ end
+ end
+
+ context 'with offset option' do
+ it 'returns offset-ed set of pages' do
+ expect(
+ subject.list_pages(offset: 1).map(&:title)
+ ).to eq(%w[index2 index3])
+
+ expect(
+ subject.list_pages(offset: 2).map(&:title)
+ ).to eq(["index3"])
+ expect(subject.list_pages(offset: 3).count).to eq(0)
+ expect(subject.list_pages(offset: 4).count).to eq(0)
+ end
+
+ it 'returns all set of pages if offset is 0' do
+ expect(subject.list_pages(offset: 0).count).to eq(3)
+ end
+
+ it 'can combines with limit' do
+ expect(
+ subject.list_pages(offset: 1, limit: 1).map(&:title)
+ ).to eq(["index2"])
end
end
context 'with sorting options' do
it 'returns pages sorted by title by default' do
- pages = ['an index3', 'index', 'index2']
+ pages = %w[index index2 index3]
expect(subject.list_pages.map(&:title)).to eq(pages)
expect(subject.list_pages(direction: 'desc').map(&:title)).to eq(pages.reverse)
@@ -200,24 +235,14 @@ RSpec.shared_examples 'wiki model' do
let(:pages) { subject.list_pages(load_content: true) }
it 'loads WikiPage content' do
- expect(pages.first.content).to eq('This is an index3')
- expect(pages.second.content).to eq('This is an index')
- expect(pages.third.content).to eq('This is an index2')
+ expect(pages.first.content).to eq('This is an index')
+ expect(pages.second.content).to eq('This is an index2')
+ expect(pages.third.content).to eq('This is an index3')
end
end
end
- context 'list pages with legacy wiki rpcs' do
- before do
- stub_feature_flags(wiki_list_page_with_normal_repository_rpcs: false)
- end
-
- it_behaves_like 'wiki model #list_pages'
- end
-
- context 'list pages with normal repository rpcs' do
- it_behaves_like 'wiki model #list_pages'
- end
+ it_behaves_like 'wiki model #list_pages'
end
describe '#sidebar_entries' do
@@ -821,29 +846,6 @@ RSpec.shared_examples 'wiki model' do
end
end
- describe '#ensure_repository' do
- context 'if the repository exists' do
- it 'does not create the repository' do
- expect(subject.repository.exists?).to eq(true)
- expect(subject.repository.raw).not_to receive(:create_repository)
-
- subject.ensure_repository
- end
- end
-
- context 'if the repository does not exist' do
- let(:wiki_container) { wiki_container_without_repo }
-
- it 'creates the repository' do
- expect(subject.repository.exists?).to eq(false)
-
- subject.ensure_repository
-
- expect(subject.repository.exists?).to eq(true)
- end
- end
- end
-
describe '#hook_attrs' do
it 'returns a hash with values' do
expect(subject.hook_attrs).to be_a Hash
diff --git a/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb b/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb
index e725de8ad31..f5431b29ee2 100644
--- a/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/max_issuable_examples.rb
@@ -12,49 +12,60 @@ RSpec.shared_examples 'does not exceed the issuable size limit' do
project.add_maintainer(user3)
end
- context 'when feature flag is turned on' do
- context "when the number of users of issuable does exceed the limit" do
- before do
- stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 2)
+ context "when the number of users of issuable does exceed the limit" do
+ before do
+ stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 2)
+ end
+
+ it 'will not add more than the allowed number of users' do
+ allow_next_instance_of(update_service) do |service|
+ expect(service).not_to receive(:execute)
end
- it 'will not add more than the allowed number of users' do
- allow_next_instance_of(update_service) do |service|
- expect(service).not_to receive(:execute)
- end
+ note = described_class.new(project, user, opts.merge(
+ note: note_text,
+ noteable_type: noteable_type,
+ noteable_id: issuable.id,
+ confidential: false
+ )).execute
- note = described_class.new(project, user, opts.merge(
- note: note_text,
- noteable_type: noteable_type,
- noteable_id: issuable.id,
- confidential: false
- )).execute
+ expect(note.errors[:validation]).to match_array([validation_message])
+ end
+ end
- expect(note.errors[:validation]).to match_array([validation_message])
- end
+ context "when the number of users does not exceed the limit" do
+ before do
+ stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 6)
end
- context "when the number of users does not exceed the limit" do
- before do
- stub_const("Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS", 6)
+ it 'calls execute and does not return an error' do
+ allow_next_instance_of(update_service) do |service|
+ expect(service).to receive(:execute).and_call_original
end
- it 'calls execute and does not return an error' do
- allow_next_instance_of(update_service) do |service|
- expect(service).to receive(:execute).and_call_original
- end
-
- note = described_class.new(project, user, opts.merge(
- note: note_text,
- noteable_type: noteable_type,
- noteable_id: issuable.id,
- confidential: false
- )).execute
+ note = described_class.new(project, user, opts.merge(
+ note: note_text,
+ noteable_type: noteable_type,
+ noteable_id: issuable.id,
+ confidential: false
+ )).execute
- expect(note.errors[:validation]).to be_empty
- end
+ expect(note.errors[:validation]).to be_empty
end
end
+end
+
+RSpec.shared_examples 'does not exceed the issuable size limit with ff off' do
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:user3) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ project.add_maintainer(user1)
+ project.add_maintainer(user2)
+ project.add_maintainer(user3)
+ end
context 'when feature flag is off' do
before do
diff --git a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
index 59e641e2af6..2170025824f 100644
--- a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
+++ b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
@@ -1,21 +1,98 @@
# frozen_string_literal: true
RSpec.shared_examples 'GET resource access tokens available' do
- let_it_be(:active_resource_access_token) { create(:personal_access_token, user: bot_user) }
+ let_it_be(:active_resource_access_token) { create(:personal_access_token, user: access_token_user) }
- it 'retrieves active resource access tokens' do
- subject
+ it 'retrieves active access tokens' do
+ get_access_tokens
- token_entities = assigns(:active_resource_access_tokens)
+ token_entities = assigns(:active_access_tokens)
expect(token_entities.length).to eq(1)
expect(token_entities[0][:name]).to eq(active_resource_access_token.name)
end
it 'lists all available scopes' do
- subject
+ get_access_tokens
expect(assigns(:scopes)).to eq(Gitlab::Auth.resource_bot_scopes)
end
+
+ it 'returns for json response' do
+ get_access_tokens_json
+
+ expect(json_response.count).to eq(1)
+ end
+end
+
+RSpec.shared_examples 'GET access tokens are paginated and ordered' do
+ before do
+ create(:personal_access_token, user: access_token_user)
+ end
+
+ context "when multiple access tokens are returned" do
+ before do
+ allow(Kaminari.config).to receive(:default_per_page).and_return(1)
+ create(:personal_access_token, user: access_token_user)
+ end
+
+ it "returns paginated response", :aggregate_failures do
+ get_access_tokens_with_page
+ expect(assigns(:active_access_tokens).count).to eq(1)
+
+ expect_header('X-Per-Page', '1')
+ expect_header('X-Page', '1')
+ expect_header('X-Next-Page', '2')
+ expect_header('X-Total', '2')
+ end
+ end
+
+ context "when access_token_pagination feature flag is disabled" do
+ before do
+ stub_feature_flags(access_token_pagination: false)
+ create(:personal_access_token, user: access_token_user)
+ end
+
+ it "returns all tokens in system" do
+ get_access_tokens_with_page
+ expect(assigns(:active_access_tokens).count).to eq(2)
+ end
+ end
+
+ context "when tokens returned are ordered" do
+ let(:expires_1_day_from_now) { 1.day.from_now.to_date }
+ let(:expires_2_day_from_now) { 2.days.from_now.to_date }
+
+ before do
+ create(:personal_access_token, user: access_token_user, name: "Token1", expires_at: expires_1_day_from_now)
+ create(:personal_access_token, user: access_token_user, name: "Token2", expires_at: expires_2_day_from_now)
+ end
+
+ it "orders token list ascending on expires_at" do
+ get_access_tokens
+
+ first_token = assigns(:active_access_tokens).first.as_json
+ expect(first_token['name']).to eq("Token1")
+ expect(first_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+ end
+
+ it "orders tokens on id in case token has same expires_at" do
+ create(:personal_access_token, user: access_token_user, name: "Token3", expires_at: expires_1_day_from_now)
+
+ get_access_tokens
+
+ first_token = assigns(:active_access_tokens).first.as_json
+ expect(first_token['name']).to eq("Token3")
+ expect(first_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+
+ second_token = assigns(:active_access_tokens).second.as_json
+ expect(second_token['name']).to eq("Token1")
+ expect(second_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+ end
+ end
+
+ def expect_header(header_name, header_val)
+ expect(response.headers[header_name]).to eq(header_val)
+ end
end
RSpec.shared_examples 'POST resource access tokens available' do
@@ -83,7 +160,7 @@ end
RSpec.shared_examples 'PUT resource access tokens available' do
it 'calls delete user worker' do
- expect(DeleteUserWorker).to receive(:perform_async).with(user.id, bot_user.id, skip_authorization: true)
+ expect(DeleteUserWorker).to receive(:perform_async).with(user.id, access_token_user.id, skip_authorization: true)
subject
end
@@ -91,34 +168,12 @@ RSpec.shared_examples 'PUT resource access tokens available' do
it 'removes membership of bot user' do
subject
- expect(resource.reload.bots).not_to include(bot_user)
+ expect(resource.reload.bots).not_to include(access_token_user)
end
- context 'when user_destroy_with_limited_execution_time_worker is enabled' do
- it 'creates GhostUserMigration records to handle migration in a worker' do
- expect { subject }.to(
- change { Users::GhostUserMigration.count }.from(0).to(1))
- end
- end
-
- context 'when user_destroy_with_limited_execution_time_worker is disabled' do
- before do
- stub_feature_flags(user_destroy_with_limited_execution_time_worker: false)
- end
-
- it 'converts issuables of the bot user to ghost user' do
- issue = create(:issue, author: bot_user)
-
- subject
-
- expect(issue.reload.author.ghost?).to be true
- end
-
- it 'deletes project bot user' do
- subject
-
- expect(User.exists?(bot_user.id)).to be_falsy
- end
+ it 'creates GhostUserMigration records to handle migration in a worker' do
+ expect { subject }.to(
+ change { Users::GhostUserMigration.count }.from(0).to(1))
end
context 'when unsuccessful' do
diff --git a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
index 32562aef8d2..f577e2ad323 100644
--- a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
@@ -15,7 +15,7 @@ RSpec.shared_examples 'with cross-reference system notes' do
new_merge_request.project.add_developer(user)
hidden_merge_request = create(:merge_request)
- new_cross_reference = "test commit #{hidden_merge_request.project.commit}"
+ new_cross_reference = "test commit #{hidden_merge_request.project.commit.to_reference(project)}"
new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference)
create(:system_note_metadata, note: new_note, action: 'cross_reference')
end
diff --git a/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb
index 22805cf7aed..bb492425fd7 100644
--- a/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb
@@ -1,13 +1,15 @@
# frozen_string_literal: true
# Requires `query(params)` , `user`, `issuable_data` and `issuable` bindings
-RSpec.shared_examples 'query with a search term' do
+RSpec.shared_examples 'query with a search term' do |fields = [:DESCRIPTION]|
+ let(:search_term) { 'bar' }
+ let(:ids) { graphql_dig_at(issuable_data, :node, :id) }
+
it 'returns only matching issuables' do
- filter_params = { search: 'bar', in: [:DESCRIPTION] }
+ filter_params = { search: search_term, in: fields }
graphql_query = query(filter_params)
post_graphql(graphql_query, current_user: user)
- ids = graphql_dig_at(issuable_data, :node, :id)
expect(ids).to contain_exactly(issuable.to_global_id.to_s)
end
diff --git a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
new file mode 100644
index 00000000000..5469fd80a4f
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
@@ -0,0 +1,170 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'graphql issue list request spec' do
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_query
+ end
+ end
+
+ describe 'filters' do
+ context 'when filtering by assignees' do
+ context 'when both assignee_username filters are provided' do
+ let(:issue_filter_params) do
+ { assignee_username: current_user.username, assignee_usernames: [current_user.username] }
+ end
+
+ it 'returns a mutually exclusive param error' do
+ post_query
+
+ expect_graphql_errors_to_include(
+ 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.'
+ )
+ end
+ end
+
+ context 'when filtering by a negated argument' do
+ let(:issue_filter_params) { { not: { assignee_usernames: [current_user.username] } } }
+
+ it 'returns correctly filtered issues' do
+ post_query
+
+ expect(issue_ids).to match_array(expected_negated_assignee_issues.map { |i| i.to_gid.to_s })
+ end
+ end
+ end
+
+ context 'when filtering by unioned arguments' do
+ let(:issue_filter_params) { { or: { assignee_usernames: [current_user.username, another_user.username] } } }
+
+ it 'returns correctly filtered issues' do
+ post_query
+
+ expect(issue_ids).to match_array(expected_unioned_assignee_issues.map { |i| i.to_gid.to_s })
+ end
+
+ context 'when argument is blank' do
+ let(:issue_filter_params) { { or: {} } }
+
+ it 'does not raise an error' do
+ post_query
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ context 'when feature flag is disabled' do
+ it 'returns an error' do
+ stub_feature_flags(or_issuable_queries: false)
+
+ post_query
+
+ expect_graphql_errors_to_include(
+ "'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled."
+ )
+ end
+ end
+ end
+
+ context 'when filtering by a blank negated argument' do
+ let(:issue_filter_params) { { not: {} } }
+
+ it 'does not raise an error' do
+ post_query
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ context 'when filtering by reaction emoji' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:value, :issue_list) do
+ 'thumbsup' | lazy { voted_issues }
+ 'ANY' | lazy { voted_issues }
+ 'any' | lazy { voted_issues }
+ 'AnY' | lazy { voted_issues }
+ 'NONE' | lazy { no_award_issues }
+ 'thumbsdown' | lazy { [] }
+ end
+
+ with_them do
+ let(:issue_filter_params) { { my_reaction_emoji: value } }
+ let(:gids) { to_gid_list(issue_list) }
+
+ it 'returns correctly filtered issues' do
+ post_query
+
+ expect(issue_ids).to match_array(gids)
+ end
+ end
+ end
+
+ context 'when filtering by search' do
+ it_behaves_like 'query with a search term', [:TITLE] do
+ let(:search_term) { search_title_term }
+ let(:issuable_data) { issues_data }
+ let(:user) { current_user }
+ let(:issuable) { title_search_issue }
+ let(:ids) { issue_ids }
+ end
+ end
+ end
+
+ describe 'sorting and pagination' do
+ context 'when sorting by severity' do
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :SEVERITY_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_severity_sorted_asc) }
+ end
+ end
+
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :SEVERITY_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_severity_sorted_asc.reverse) }
+ end
+ end
+ end
+
+ context 'when sorting by priority' do
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :PRIORITY_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_priority_sorted_asc) }
+ end
+ end
+
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :PRIORITY_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { to_gid_list(expected_priority_sorted_desc) }
+ end
+ end
+ end
+ end
+
+ it 'includes a web_url' do
+ post_query
+
+ expect(issues_data[0]['webUrl']).to be_present
+ end
+
+ it 'includes discussion locked' do
+ post_query
+
+ expect(issues_data).to contain_exactly(
+ *locked_discussion_issues.map { |i| hash_including('id' => i.to_gid.to_s, 'discussionLocked' => true) },
+ *unlocked_discussion_issues.map { |i| hash_including('id' => i.to_gid.to_s, 'discussionLocked' => false) }
+ )
+ end
+
+ def to_gid_list(instance_list)
+ instance_list.map { |instance| instance.to_gid.to_s }
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
index 1b609915f32..fb4aacfd7a9 100644
--- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
@@ -114,7 +114,7 @@ RSpec.shared_examples 'group and project packages query' do
end
[:CREATED_ASC, :NAME_ASC, :VERSION_ASC, :TYPE_ASC, :CREATED_DESC, :NAME_DESC, :VERSION_DESC, :TYPE_DESC].each do |order|
- context "#{order}" do
+ context order.to_s do
let(:sorted_packages) { packages_order_map.fetch(order) }
it_behaves_like 'sorted paginated query' do
diff --git a/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb
new file mode 100644
index 00000000000..54cc13fac94
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'perform graphql requests for AccessLevel type objects' do |access_level_kind|
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user, maintainer_projects: [project]) }
+ let_it_be(:variables) { { path: project.full_path } }
+
+ let(:fields) { all_graphql_fields_for("#{access_level_kind.to_s.classify}AccessLevel", max_depth: 2) }
+ let(:access_levels) { protected_branch.public_send("#{access_level_kind}_access_levels") }
+ let(:access_levels_count) { access_levels.size }
+ let(:maintainer_access_level) { access_levels.for_role.first }
+ let(:maintainer_access_level_data) { access_levels_data.first }
+ let(:access_levels_data) do
+ graphql_data_at('project',
+ 'branchRules',
+ 'nodes',
+ 0,
+ 'branchProtection',
+ "#{access_level_kind.to_s.camelize(:lower)}AccessLevels",
+ 'nodes')
+ end
+
+ let(:query) do
+ <<~GQL
+ query($path: ID!) {
+ project(fullPath: $path) {
+ branchRules(first: 1) {
+ nodes {
+ branchProtection {
+ #{access_level_kind.to_s.camelize(:lower)}AccessLevels {
+ nodes {
+ #{fields}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ context 'when request AccessLevel type objects as a guest user' do
+ let_it_be(:protected_branch) { create(:protected_branch, project: project) }
+
+ before do
+ project.add_guest(current_user)
+
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(access_levels_data).not_to be_present }
+ end
+
+ context 'when request AccessLevel type objects as a maintainer' do
+ let_it_be(:protected_branch) do
+ create(:protected_branch, "maintainers_can_#{access_level_kind}", project: project)
+ end
+
+ before do
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns all the access level attributes' do
+ expect(maintainer_access_level_data['accessLevel']).to eq(maintainer_access_level.access_level)
+ expect(maintainer_access_level_data['accessLevelDescription']).to eq(maintainer_access_level.humanize)
+ expect(maintainer_access_level_data.dig('group', 'name')).to be_nil
+ expect(maintainer_access_level_data.dig('user', 'name')).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/issues_shared_examples.rb b/spec/support/shared_examples/requests/api/issues_shared_examples.rb
index 991dbced02d..6328fb9cd8a 100644
--- a/spec/support/shared_examples/requests/api/issues_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/issues_shared_examples.rb
@@ -37,7 +37,7 @@ RSpec.shared_examples 'labeled issues with labels and label_name params' do
context 'negation' do
context 'array of labeled issues when all labels match with negation' do
- let(:params) { { labels: "#{label.title},#{label_b.title}", not: { labels: "#{label_c.title}" } } }
+ let(:params) { { labels: "#{label.title},#{label_b.title}", not: { labels: label_c.title.to_s } } }
it_behaves_like 'returns negated label names'
end
diff --git a/spec/support/shared_examples/requests/api/members_shared_examples.rb b/spec/support/shared_examples/requests/api/members_shared_examples.rb
index fce75c29971..9136f60eb93 100644
--- a/spec/support/shared_examples/requests/api/members_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/members_shared_examples.rb
@@ -11,3 +11,11 @@ RSpec.shared_examples 'a 404 response when source is private' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+RSpec.shared_examples 'a 403 response when user does not have rights to manage members of a specific access level' do
+ it 'returns 403' do
+ route
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
index fa111ca5811..d749479544d 100644
--- a/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
@@ -5,6 +5,7 @@ RSpec.shared_examples 'multiple and scoped issue boards' do |route_definition|
context 'multiple issue boards' do
before do
+ stub_feature_flags(apollo_boards: false)
board_parent.add_reporter(user)
stub_licensed_features(multiple_group_issue_boards: true)
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 8479493911b..11f9565989f 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -179,7 +179,8 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end
end
- if parent_type == 'projects'
+ case parent_type
+ when 'projects'
context 'by a project owner' do
let(:user) { project.first_owner }
@@ -211,7 +212,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
expect(Time.parse(json_response['updated_at'])).to be_like_time(creation_time)
end
end
- elsif parent_type == 'groups'
+ when 'groups'
context 'by a group owner' do
it 'sets the creation time on the new note' do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params
@@ -288,7 +289,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end
it 'allows user in allow-list to create notes' do
- stub_application_setting(notes_create_limit_allowlist: ["#{user.username}"])
+ stub_application_setting(notes_create_limit_allowlist: [user.username.to_s])
subject
expect(response).to have_gitlab_http_status(:created)
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 11e19d8d067..a9b44015206 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
@@ -221,6 +221,27 @@ RSpec.shared_examples 'rejects PyPI access with unknown group id' do
end
end
+RSpec.shared_examples 'allow access for everyone with public package_registry_access_level' do
+ context 'with private project but public access to package registry' do
+ before do
+ project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+ project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC)
+ end
+
+ context 'as non-member user' do
+ let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'as anonymous' do
+ let(:headers) { {} }
+
+ it_behaves_like 'returning response status', :success
+ end
+ end
+end
+
RSpec.shared_examples 'pypi simple API endpoint' do
using RSpec::Parameterized::TableSyntax
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 544a0ed8fdd..bdff2c65691 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
@@ -63,9 +63,9 @@ RSpec.shared_examples 'redirects to version download' do |user_type, status, add
it 'returns a valid response' do
subject
- expect(request.url).to include 'module-1/system/download'
+ expect(request.url).to include "#{package.name}/download"
expect(response.headers).to include 'Location'
- expect(response.headers['Location']).to include 'module-1/system/1.0.1/download'
+ expect(response.headers['Location']).to include "#{package.name}/1.0.1/download"
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 571cb7dc03d..b46ace1824a 100644
--- a/spec/support/shared_examples/services/alert_management_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb
@@ -72,8 +72,8 @@ RSpec.shared_examples 'processes one firing and one resolved prometheus alerts'
.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(1)
+ expect(subject.payload).to eq({})
+ expect(subject.http_status).to eq(:created)
end
it_behaves_like 'processes incident issues'
diff --git a/spec/support/shared_examples/services/base_rpm_service_shared_examples.rb b/spec/support/shared_examples/services/base_rpm_service_shared_examples.rb
deleted file mode 100644
index c9520852a5b..00000000000
--- a/spec/support/shared_examples/services/base_rpm_service_shared_examples.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'handling rpm xml file' do
- include_context 'with rpm package data'
-
- let(:xml) { nil }
- let(:data) { {} }
-
- context 'when generate empty xml' do
- it 'generate expected xml' do
- expect(subject).to eq(empty_xml)
- end
- end
-
- context 'when updating existing xml' do
- let(:xml) { empty_xml }
- let(:data) { xml_update_params }
-
- shared_examples 'changing root tag attribute' do
- it "increment previous 'packages' value by 1" do
- previous_value = Nokogiri::XML(xml).at(described_class::ROOT_TAG).attributes["packages"].value.to_i
- new_value = Nokogiri::XML(subject).at(described_class::ROOT_TAG).attributes["packages"].value.to_i
-
- expect(previous_value + 1).to eq(new_value)
- end
- end
-
- it 'generate valid xml add expected xml node to existing xml' do
- # Have one root attribute
- result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
- expect(result.children.count).to eq(1)
-
- # Root node has 1 child with generated node
- expect(result.xpath("//#{described_class::ROOT_TAG}/package").count).to eq(1)
- end
-
- context 'when empty xml' do
- it_behaves_like 'changing root tag attribute'
- end
-
- context 'when xml has children' do
- let(:xml) { described_class.new(xml: empty_xml, data: data).execute }
-
- it 'has children nodes' do
- result = Nokogiri::XML::Document.parse(xml).remove_namespaces!
- expect(result.children.count).to be > 0
- end
-
- it_behaves_like 'changing root tag attribute'
- end
- end
-end
diff --git a/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb b/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb
new file mode 100644
index 00000000000..c38ca6a3bf0
--- /dev/null
+++ b/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'listing issuable discussions' do |user_role, internal_discussion_count, total_discussions_count|
+ before_all do
+ create_notes(issuable, "some user comment")
+ end
+
+ context 'when user cannot read issue' do
+ it "returns no notes" do
+ expect(discussions_service.execute).to be_empty
+ end
+ end
+
+ context 'when user can read issuable' do
+ before do
+ group.add_developer(current_user)
+ end
+
+ context 'with paginated results' do
+ let(:finder_params_for_issuable) { { per_page: 2 } }
+ let(:next_page_cursor) { { cursor: discussions_service.paginator.cursor_for_next_page } }
+
+ it "returns next page notes" do
+ next_page_discussions_service = described_class.new(current_user, issuable,
+ finder_params_for_issuable.merge(next_page_cursor))
+ discussions = next_page_discussions_service.execute
+
+ expect(discussions.count).to eq(2)
+ expect(discussions.first.notes.map(&:note)).to match_array(["added #{label.to_reference} label"])
+ expect(discussions.second.notes.map(&:note)).to match_array(["removed #{label.to_reference} label"])
+ end
+ end
+
+ # confidential notes are currently available only on issues and epics
+ context 'and cannot read confidential notes' do
+ before do
+ group.add_member(current_user, user_role)
+ end
+
+ it "returns non confidential notes" do
+ discussions = discussions_service.execute
+
+ non_conf_discussion_count = total_discussions_count - internal_discussion_count
+ expect(discussions.count).to eq(non_conf_discussion_count)
+ expect(discussions.count { |disc| disc.notes.any?(&:confidential) }).to eq(0)
+ expect(discussions.count { |disc| !disc.notes.any?(&:confidential) }).to eq(non_conf_discussion_count)
+ end
+ end
+
+ # confidential notes are currently available only on issues and epics
+ context 'and can read confidential notes' do
+ it "returns all notes" do
+ discussions = discussions_service.execute
+
+ expect(discussions.count).to eq(total_discussions_count)
+ expect(discussions.count { |disc| disc.notes.any?(&:confidential) }).to eq(internal_discussion_count)
+ non_conf_discussion_count = total_discussions_count - internal_discussion_count
+ expect(discussions.count { |disc| !disc.notes.any?(&:confidential) }).to eq(non_conf_discussion_count)
+ end
+ end
+
+ context 'and system notes only' do
+ let(:finder_params_for_issuable) { { notes_filter: UserPreference::NOTES_FILTERS[:only_activity] } }
+
+ it "returns system notes" do
+ discussions = discussions_service.execute
+
+ expect(discussions.count { |disc| disc.notes.any?(&:system) }).to be > 0
+ expect(discussions.count { |disc| !disc.notes.any?(&:system) }).to eq(0)
+ end
+ end
+
+ context 'and user comments only' do
+ let(:finder_params_for_issuable) { { notes_filter: UserPreference::NOTES_FILTERS[:only_comments] } }
+
+ it "returns user comments" do
+ discussions = discussions_service.execute
+
+ expect(discussions.count { |disc| disc.notes.any?(&:system) }).to eq(0)
+ expect(discussions.count { |disc| !disc.notes.any?(&:system) }).to be > 0
+ end
+ end
+ end
+end
+
+def create_notes(issuable, note_body)
+ assoc_name = issuable.to_ability_name
+
+ create(:note, system: true, project: issuable.project, noteable: issuable)
+
+ first_discussion = create(:discussion_note_on_issue, noteable: issuable, project: issuable.project, note: note_body)
+ create(:note,
+ discussion_id: first_discussion.discussion_id, noteable: issuable,
+ project: issuable.project, note: "reply on #{note_body}")
+
+ create(:resource_label_event, user: current_user, "#{assoc_name}": issuable, label: label, action: 'add')
+ create(:resource_label_event, user: current_user, "#{assoc_name}": issuable, label: label, action: 'remove')
+
+ unless issuable.is_a?(Epic)
+ create(:resource_milestone_event, "#{assoc_name}": issuable, milestone: milestone, action: 'add')
+ create(:resource_milestone_event, "#{assoc_name}": issuable, milestone: milestone, action: 'remove')
+ end
+
+ # confidential notes are currently available only on issues and epics
+ return unless issuable.is_a?(Issue) || issuable.is_a?(Epic)
+
+ first_internal_discussion = create(:discussion_note_on_issue, :confidential,
+ noteable: issuable, project: issuable.project, note: "confidential #{note_body}")
+ create(:note, :confidential,
+ discussion_id: first_internal_discussion.discussion_id, noteable: issuable,
+ project: issuable.project, note: "reply on confidential #{note_body}")
+end
diff --git a/spec/support/shared_examples/services/merge_status_updated_trigger_shared_examples.rb b/spec/support/shared_examples/services/merge_status_updated_trigger_shared_examples.rb
new file mode 100644
index 00000000000..97e3b0a44a7
--- /dev/null
+++ b/spec/support/shared_examples/services/merge_status_updated_trigger_shared_examples.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
+ specify do
+ expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(merge_request)
+
+ action
+ end
+end
+
+RSpec.shared_examples 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
+ specify do
+ expect(GraphqlTriggers).not_to receive(:merge_request_merge_status_updated)
+
+ action
+ end
+end
diff --git a/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb b/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb
index 09820593cdb..46a1f4b6598 100644
--- a/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/users/dismiss_user_callout_service_shared_examples.rb
@@ -20,7 +20,7 @@ RSpec.shared_examples_for 'dismissing user callout' do |model|
old_time = 1.day.ago
new_time = Time.current
attributes = params.merge(dismissed_at: old_time, user: user)
- existing_callout = create("#{model.name.split('::').last.underscore}".to_sym, attributes)
+ existing_callout = create(model.name.split('::').last.underscore.to_s.to_sym, attributes)
expect { execute }.to change { existing_callout.reload.dismissed_at }.from(old_time).to(new_time)
end
diff --git a/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb b/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb
new file mode 100644
index 00000000000..ac17915c15a
--- /dev/null
+++ b/spec/support/shared_examples/services/work_items/widgets/milestone_service_shared_examples.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples "setting work item's milestone" do
+ context "when 'milestone' param does not exist" do
+ let(:params) { {} }
+
+ it "does not set the work item's milestone" do
+ expect { execute_callback }.to not_change(work_item, :milestone)
+ end
+ end
+
+ context "when 'milestone' is not in the work item's project's hierarchy" do
+ let(:another_group_milestone) { create(:milestone, group: create(:group)) }
+ let(:params) { { milestone_id: another_group_milestone.id } }
+
+ it "does not set the work item's milestone" do
+ expect { execute_callback }.to not_change(work_item, :milestone)
+ end
+ end
+
+ context 'when assigning a group milestone' do
+ let(:params) { { milestone_id: group_milestone.id } }
+
+ it "sets the work item's milestone" do
+ expect { execute_callback }
+ .to change(work_item, :milestone)
+ .from(nil)
+ .to(group_milestone)
+ end
+ end
+
+ context 'when assigning a project milestone' do
+ let(:params) { { milestone_id: project_milestone.id } }
+
+ it "sets the work item's milestone" do
+ expect { execute_callback }
+ .to change(work_item, :milestone)
+ .from(nil)
+ .to(project_milestone)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
index 3c977e62a10..af56f8ffac7 100644
--- a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
@@ -26,9 +26,10 @@ RSpec.shared_examples "migrates" do |to_store:, from_store: nil|
expect(subject).to be_an(CarrierWave::Uploader::Base)
expect(subject).to be_a(ObjectStorage::Concern)
- if from == described_class::Store::REMOTE
+ case from
+ when described_class::Store::REMOTE
expect(subject.file).to be_a(CarrierWave::Storage::Fog::File)
- elsif from == described_class::Store::LOCAL
+ when described_class::Store::LOCAL
expect(subject.file).to be_a(CarrierWave::SanitizedFile)
else
raise 'Unexpected file type'
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 3ba5f080a01..0be55fd2a3e 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
@@ -137,8 +137,12 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
let(:lease_timeout) { 15.minutes }
let(:lease_key) { described_class.name.demodulize.underscore }
let(:interval_variance) { described_class::INTERVAL_VARIANCE }
+ let(:migration_id) { 123 }
let(:migration) do
- build(:batched_background_migration, :active, interval: job_interval, table_name: table_name)
+ build(
+ :batched_background_migration, :active,
+ id: migration_id, interval: job_interval, table_name: table_name
+ )
end
before do
@@ -150,45 +154,6 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
allow(migration).to receive(:reload)
end
- context 'when the reloaded migration is no longer active' do
- it 'does not run the migration' do
- expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout)
-
- expect(migration).to receive(:reload)
- expect(migration).to receive(:active?).and_return(false)
-
- expect(worker).not_to receive(:run_active_migration)
-
- worker.perform
- end
- end
-
- context 'when the interval has not elapsed' do
- it 'does not run the migration' do
- expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout)
-
- expect(migration).to receive(:interval_elapsed?).with(variance: interval_variance).and_return(false)
-
- expect(worker).not_to receive(:run_active_migration)
-
- worker.perform
- end
- end
-
- context 'when the reloaded migration is still active and the interval has elapsed' do
- it 'runs the migration' do
- expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout)
-
- expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |instance|
- expect(instance).to receive(:run_migration_job).with(migration)
- end
-
- expect(worker).to receive(:run_active_migration).and_call_original
-
- worker.perform
- end
- end
-
context 'when the calculated timeout is less than the minimum allowed' do
let(:minimum_timeout) { described_class::MINIMUM_LEASE_TIMEOUT }
let(:job_interval) { 2.minutes }
@@ -196,8 +161,8 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
it 'sets the lease timeout to the minimum value' do
expect_to_obtain_exclusive_lease(lease_key, timeout: minimum_timeout)
- expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |instance|
- expect(instance).to receive(:run_migration_job).with(migration)
+ expect_next_instance_of(Database::BatchedBackgroundMigration::ExecutionWorker) do |worker|
+ expect(worker).to receive(:perform).with(tracking_database, migration_id)
end
expect(worker).to receive(:run_active_migration).and_call_original
@@ -217,10 +182,13 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
expect { worker.perform }.to raise_error(RuntimeError, 'I broke')
end
- it 'receives the correct connection' do
+ it 'delegetes the execution to ExecutionWorker' do
base_model = Gitlab::Database.database_base_models[tracking_database]
expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(base_model.connection).and_yield
+ expect_next_instance_of(Database::BatchedBackgroundMigration::ExecutionWorker) do |worker|
+ expect(worker).to receive(:perform).with(tracking_database, migration_id)
+ end
worker.perform
end
@@ -236,10 +204,10 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
let(:migration_class) do
Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
job_arguments :matching_status
+ operation_name :update_all
def perform
each_sub_batch(
- operation_name: :update_all,
batching_scope: -> (relation) { relation.where(status: matching_status) }
) do |sub_batch|
sub_batch.update_all(some_column: 0)