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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-08-18 11:17:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-18 11:17:02 +0300
commitb39512ed755239198a9c294b6a45e65c05900235 (patch)
treed234a3efade1de67c46b9e5a38ce813627726aa7 /spec/support/shared_examples
parentd31474cf3b17ece37939d20082b07f6657cc79a9 (diff)
Add latest changes from gitlab-org/gitlab@15-3-stable-eev15.3.0-rc42
Diffstat (limited to 'spec/support/shared_examples')
-rw-r--r--spec/support/shared_examples/attention_request_cache_invalidation_examples.rb15
-rw-r--r--spec/support/shared_examples/boards/destroy_service_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/components/pajamas_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb21
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb80
-rw-r--r--spec/support/shared_examples/features/access_tokens_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/features/inviting_members_shared_examples.rb110
-rw-r--r--spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/trial_email_validation_shared_example.rb59
-rw-r--r--spec/support/shared_examples/features/user_views_tag_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/features/variable_list_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb97
-rw-r--r--spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/graphql/notes_creation_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb64
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/issuable_link_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/models/project_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/models/taskable_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/namespaces/traversal_scope_examples.rb16
-rw-r--r--spec/support/shared_examples/policies/group_project_namespace_policy_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/requests/api/discussions_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb47
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/boards/lists_move_service_shared_examples.rb120
-rw-r--r--spec/support/shared_examples/services/issuable_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb89
-rw-r--r--spec/support/shared_examples/services/work_items/create_task_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb31
50 files changed, 938 insertions, 343 deletions
diff --git a/spec/support/shared_examples/attention_request_cache_invalidation_examples.rb b/spec/support/shared_examples/attention_request_cache_invalidation_examples.rb
deleted file mode 100644
index 7fe696abc69..00000000000
--- a/spec/support/shared_examples/attention_request_cache_invalidation_examples.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'invalidates attention request cache' do
- it 'invalidates the merge requests requiring attention count' do
- cache_mock = double
-
- users.each do |user|
- expect(cache_mock).to receive(:delete).with(['users', user.id, 'attention_requested_open_merge_requests_count'])
- end
-
- allow(Rails).to receive(:cache).and_return(cache_mock)
-
- service.execute
- end
-end
diff --git a/spec/support/shared_examples/boards/destroy_service_shared_examples.rb b/spec/support/shared_examples/boards/destroy_service_shared_examples.rb
index 33bae3da44b..b1cb58a736f 100644
--- a/spec/support/shared_examples/boards/destroy_service_shared_examples.rb
+++ b/spec/support/shared_examples/boards/destroy_service_shared_examples.rb
@@ -20,10 +20,10 @@ RSpec.shared_examples 'board destroy service' do
end
context 'when there is only one board' do
- it 'does not remove board' do
+ it 'does remove board' do
expect do
- expect(service.execute(board)).to be_error
- end.not_to change(boards, :count)
+ service.execute(board)
+ end.to change(boards, :count).by(-1)
end
end
end
diff --git a/spec/support/shared_examples/components/pajamas_shared_examples.rb b/spec/support/shared_examples/components/pajamas_shared_examples.rb
index 5c0ad1a1bc9..bcf7df24fd9 100644
--- a/spec/support/shared_examples/components/pajamas_shared_examples.rb
+++ b/spec/support/shared_examples/components/pajamas_shared_examples.rb
@@ -2,12 +2,18 @@
RSpec.shared_examples 'it renders help text' do
it 'renders help text' do
- expect(rendered_component).to have_selector('[data-testid="pajamas-component-help-text"]', text: help_text)
+ expect(page).to have_css('[data-testid="pajamas-component-help-text"]', text: help_text)
end
end
RSpec.shared_examples 'it does not render help text' do
it 'does not render help text' do
- expect(rendered_component).not_to have_selector('[data-testid="pajamas-component-help-text"]')
+ expect(page).not_to have_css('[data-testid="pajamas-component-help-text"]')
+ end
+end
+
+RSpec.shared_examples 'it renders unchecked checkbox with value of `1`' do
+ it 'renders unchecked checkbox with value of `1`' do
+ expect(page).to have_unchecked_field(label, with: '1')
end
end
diff --git a/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb b/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb
new file mode 100644
index 00000000000..9421561aea4
--- /dev/null
+++ b/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'when the user cannot read cross project' do |action, params|
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(false)
+ end
+
+ it 'blocks access without a project_id' do
+ get action, params: params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'allows access with a project_id' do
+ get action, params: params.merge(project_id: create(:project, :public).id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb
new file mode 100644
index 00000000000..6b72988b3e6
--- /dev/null
+++ b/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'with external authorization service enabled' do |action, params|
+ include ExternalAuthorizationServiceHelpers
+
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:note) { create(:note_on_issue, project: project) }
+
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'renders a 403 when no project is given' do
+ get action, params: params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'renders a 200 when a project was set' do
+ get action, params: params.merge(project_id: project.id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
index 98fc52add51..2e691d1b36f 100644
--- a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
+++ b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
@@ -2,22 +2,26 @@
#
# Requires a context containing:
# - subject
-# - project
# - feature_flag_name
# - category
# - action
# - namespace
+# Optionaly, the context can contain:
+# - project
+# - property
# - user
+# - label
+# - **extra
-shared_examples 'Snowplow event tracking' do
- let(:label) { nil }
+shared_examples 'Snowplow event tracking' do |overrides: {}|
+ let(:extra) { {} }
it 'is not emitted if FF is disabled' do
stub_feature_flags(feature_flag_name => false)
subject
- expect_no_snowplow_event
+ expect_no_snowplow_event(category: category, action: action)
end
it 'is emitted' do
@@ -25,10 +29,11 @@ shared_examples 'Snowplow event tracking' do
category: category,
action: action,
namespace: namespace,
- user: user,
- project: project,
- label: label
- }.compact
+ user: try(:user),
+ project: try(:project),
+ label: try(:label),
+ property: try(:property)
+ }.merge(overrides).compact.merge(extra)
subject
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 6dca94ecf0a..0792ac14e47 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -205,41 +205,13 @@ RSpec.shared_examples 'handle uploads' do
allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
end
- context "enforce_auth_checks_on_uploads feature flag" do
- context "with flag enabled" do
- before do
- stub_feature_flags(enforce_auth_checks_on_uploads: true)
- end
+ it "responds with the appropriate status code" do
+ show_upload
- it "responds with appropriate status" do
- show_upload
-
- # We're switching here based on the class due to the feature
- # flag :enforce_auth_checks_on_uploads switching on project.
- # When it is enabled fully, we will apply the code it guards
- # to both Projects::UploadsController as well as
- # Groups::UploadsController.
- #
- # https://gitlab.com/gitlab-org/gitlab/-/issues/352291
- #
- if model.instance_of?(Group)
- expect(response).to have_gitlab_http_status(:ok)
- else
- expect(response).to have_gitlab_http_status(:redirect)
- end
- end
- end
-
- context "with flag disabled" do
- before do
- stub_feature_flags(enforce_auth_checks_on_uploads: false)
- end
-
- it "responds with status 200" do
- show_upload
-
- expect(response).to have_gitlab_http_status(:ok)
- end
+ if model.instance_of?(Group)
+ expect(response).to have_gitlab_http_status(:ok)
+ else
+ expect(response).to have_gitlab_http_status(:redirect)
end
end
end
@@ -308,41 +280,13 @@ RSpec.shared_examples 'handle uploads' do
allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
end
- context "enforce_auth_checks_on_uploads feature flag" do
- context "with flag enabled" do
- before do
- stub_feature_flags(enforce_auth_checks_on_uploads: true)
- end
-
- it "responds with status 404" do
- show_upload
-
- # We're switching here based on the class due to the feature
- # flag :enforce_auth_checks_on_uploads switching on
- # project. When it is enabled fully, we will apply the
- # code it guards to both Projects::UploadsController as
- # well as Groups::UploadsController.
- #
- # https://gitlab.com/gitlab-org/gitlab/-/issues/352291
- #
- if model.instance_of?(Group)
- expect(response).to have_gitlab_http_status(:ok)
- else
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context "with flag disabled" do
- before do
- stub_feature_flags(enforce_auth_checks_on_uploads: false)
- end
-
- it "responds with status 200" do
- show_upload
+ it "responds with the appropriate status code" do
+ show_upload
- expect(response).to have_gitlab_http_status(:ok)
- end
+ if model.instance_of?(Group)
+ expect(response).to have_gitlab_http_status(:ok)
+ else
+ expect(response).to have_gitlab_http_status(:not_found)
end
end
end
diff --git a/spec/support/shared_examples/features/access_tokens_shared_examples.rb b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
index c162ed36881..0fc45b154d8 100644
--- a/spec/support/shared_examples/features/access_tokens_shared_examples.rb
+++ b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
@@ -38,7 +38,7 @@ RSpec.shared_examples 'resource access tokens creation' do |resource_type|
expect(active_resource_access_tokens).to have_text('in')
expect(active_resource_access_tokens).to have_text('read_api')
expect(active_resource_access_tokens).to have_text('read_repository')
- expect(active_resource_access_tokens).to have_text('Maintainer')
+ expect(active_resource_access_tokens).to have_text('Guest')
expect(created_resource_access_token).not_to be_empty
end
end
diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb
index 0ea82f37db0..3fa7beea97e 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -13,9 +13,8 @@ RSpec.shared_examples 'edits content using the content editor' do
expect(page).to have_css('[data-testid="formatting-bubble-menu"]')
end
- it 'does not show a formatting bubble menu for code' do
- find(content_editor_testid).send_keys 'This is a `code`'
- find(content_editor_testid).send_keys [:shift, :left]
+ it 'does not show a formatting bubble menu for code blocks' do
+ find(content_editor_testid).send_keys '```js '
expect(page).not_to have_css('[data-testid="formatting-bubble-menu"]')
end
diff --git a/spec/support/shared_examples/features/inviting_members_shared_examples.rb b/spec/support/shared_examples/features/inviting_members_shared_examples.rb
index bca0e02fcdd..277ec6a7fa7 100644
--- a/spec/support/shared_examples/features/inviting_members_shared_examples.rb
+++ b/spec/support/shared_examples/features/inviting_members_shared_examples.rb
@@ -147,9 +147,9 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
invite_member(user2.name, role: role, refresh: false)
- expect(page).to have_selector(invite_modal_selector)
- expect(page).to have_content "#{user2.name}: Access level should be greater than or equal to Developer " \
- "inherited membership from group #{group.name}"
+ invite_modal = page.find(invite_modal_selector)
+ expect(invite_modal).to have_content "#{user2.name}: Access level should be greater than or equal to " \
+ "Developer inherited membership from group #{group.name}"
page.refresh
@@ -166,31 +166,85 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
group.add_maintainer(user3)
end
- it 'shows the user errors and then removes them from the form', :js do
- visit subentity_members_page_path
+ it 'shows the partial user error and success and then removes them from the form', :js do
+ user4 = create(:user)
+ user5 = create(:user)
+ user6 = create(:user)
+ user7 = create(:user)
+
+ group.add_maintainer(user6)
+ group.add_maintainer(user7)
- invite_member([user2.name, user3.name], role: role, refresh: false)
+ visit subentity_members_page_path
- expect(page).to have_selector(invite_modal_selector)
- expect(page).to have_selector(member_token_error_selector(user2.id))
- expect(page).to have_selector(member_token_error_selector(user3.id))
- expect(page).to have_text("The following 2 members couldn't be invited")
- expect(page).to have_text("#{user2.name}: Access level should be greater than or equal to")
- expect(page).to have_text("#{user3.name}: Access level should be greater than or equal to")
+ invite_member([user2.name, user3.name, user4.name, user6.name, user7.name], role: role, refresh: false)
+
+ # we have more than 2 errors, so one will be hidden
+ invite_modal = page.find(invite_modal_selector)
+ expect(invite_modal).to have_text("The following 4 members couldn't be invited")
+ expect(invite_modal).to have_selector(limited_invite_error_selector, count: 2, visible: :visible)
+ expect(invite_modal).to have_selector(expanded_invite_error_selector, count: 2, visible: :hidden)
+ # unpredictability of return order means we can't rely on message showing in any order here
+ # so we will not expect on the message
+ expect_to_have_invalid_invite_indicator(invite_modal, user2, message: false)
+ expect_to_have_invalid_invite_indicator(invite_modal, user3, message: false)
+ expect_to_have_invalid_invite_indicator(invite_modal, user6, message: false)
+ expect_to_have_invalid_invite_indicator(invite_modal, user7, message: false)
+ expect_to_have_successful_invite_indicator(invite_modal, user4)
+ expect(invite_modal).to have_button('Show more (2)')
+
+ # now we want to test the show more errors count logic
+ remove_token(user7.id)
+
+ # count decreases from 4 to 3 and 2 to 1
+ expect(invite_modal).to have_text("The following 3 members couldn't be invited")
+ expect(invite_modal).to have_button('Show more (1)')
+
+ # we want to show this error now for user6
+ invite_modal.find(more_invite_errors_button_selector).click
+
+ # now we should see the error for all users and our collapse button text
+ expect(invite_modal).to have_selector(limited_invite_error_selector, count: 2, visible: :visible)
+ expect(invite_modal).to have_selector(expanded_invite_error_selector, count: 1, visible: :visible)
+ expect_to_have_invalid_invite_indicator(invite_modal, user2, message: true)
+ expect_to_have_invalid_invite_indicator(invite_modal, user3, message: true)
+ expect_to_have_invalid_invite_indicator(invite_modal, user6, message: true)
+ expect(invite_modal).to have_button('Show less')
+
+ # adds new token, but doesn't submit
+ select_members(user5.name)
+
+ expect_to_have_normal_invite_indicator(invite_modal, user5)
remove_token(user2.id)
- expect(page).not_to have_selector(member_token_error_selector(user2.id))
- expect(page).to have_selector(member_token_error_selector(user3.id))
- expect(page).to have_text("The following member couldn't be invited")
- expect(page).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
+ expect(invite_modal).to have_text("The following 2 members couldn't be invited")
+ expect(invite_modal).not_to have_selector(more_invite_errors_button_selector)
+ expect_to_have_invite_removed(invite_modal, user2)
+ expect_to_have_invalid_invite_indicator(invite_modal, user3)
+ expect_to_have_invalid_invite_indicator(invite_modal, user6)
+ expect_to_have_successful_invite_indicator(invite_modal, user4)
+ expect_to_have_normal_invite_indicator(invite_modal, user5)
+
+ remove_token(user6.id)
+
+ expect(invite_modal).to have_text("The following member couldn't be invited")
+ expect_to_have_invite_removed(invite_modal, user6)
+ expect_to_have_invalid_invite_indicator(invite_modal, user3)
+ expect_to_have_successful_invite_indicator(invite_modal, user4)
+ expect_to_have_normal_invite_indicator(invite_modal, user5)
remove_token(user3.id)
- expect(page).not_to have_selector(member_token_error_selector(user3.id))
- expect(page).not_to have_text("The following member couldn't be invited")
- expect(page).not_to have_text("Review the invite errors and try again")
- expect(page).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
+ expect(invite_modal).not_to have_text("The following member couldn't be invited")
+ expect(invite_modal).not_to have_text("Review the invite errors and try again")
+ expect_to_have_invite_removed(invite_modal, user3)
+ expect_to_have_successful_invite_indicator(invite_modal, user4)
+ expect_to_have_normal_invite_indicator(invite_modal, user5)
+
+ submit_invites
+
+ expect(page).not_to have_selector(invite_modal_selector)
page.refresh
@@ -203,6 +257,10 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
expect(page).to have_content('Maintainer')
expect(page).not_to have_button('Maintainer')
end
+
+ page.within find_invited_member_row(user4.name) do
+ expect(page).to have_button(role)
+ end
end
it 'only shows the error for an invalid formatted email and does not display other member errors', :js do
@@ -210,12 +268,12 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
invite_member([user2.name, user3.name, 'bad@email'], role: role, refresh: false)
- expect(page).to have_selector(invite_modal_selector)
- expect(page).to have_text('email contains an invalid email address')
- expect(page).not_to have_text("The following 2 members couldn't be invited")
- expect(page).not_to have_text("Review the invite errors and try again")
- expect(page).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
- expect(page).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
+ invite_modal = page.find(invite_modal_selector)
+ expect(invite_modal).to have_text('email contains an invalid email address')
+ expect(invite_modal).not_to have_text("The following 2 members couldn't be invited")
+ expect(invite_modal).not_to have_text("Review the invite errors and try again")
+ expect(invite_modal).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
+ expect(invite_modal).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
end
end
end
diff --git a/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb b/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
index bbde448a1a1..ef2683d6424 100644
--- a/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
+++ b/spec/support/shared_examples/features/multiple_assignees_widget_mr_shared_examples.rb
@@ -32,7 +32,7 @@ RSpec.shared_examples 'multiple assignees widget merge request' do |action, save
end
page.within '.dropdown-menu-user' do
- click_link user.name
+ click_button user.name
end
page.within '.issuable-sidebar' do
diff --git a/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
index 345dfbce423..95c0a76d726 100644
--- a/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb
@@ -16,7 +16,9 @@ RSpec.shared_examples 'date sidebar widget' do
page.within('[data-testid="sidebar-due-date"]') do
today = Date.today.day
- click_button 'Edit'
+ button = find_button('Edit')
+ scroll_to(button)
+ button.click
click_button today.to_s
diff --git a/spec/support/shared_examples/features/trial_email_validation_shared_example.rb b/spec/support/shared_examples/features/trial_email_validation_shared_example.rb
new file mode 100644
index 00000000000..8304a91af86
--- /dev/null
+++ b/spec/support/shared_examples/features/trial_email_validation_shared_example.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'user email validation' do
+ let(:email_hint_message) { 'We recommend a work email address.' }
+ let(:email_error_message) { 'Please provide a valid email address.' }
+
+ let(:email_warning_message) do
+ 'This email address does not look right, are you sure you typed it correctly?'
+ end
+
+ context 'with trial_email_validation flag enabled' do
+ it 'shows an error message until a correct email is entered' do
+ visit path
+ expect(page).to have_content(email_hint_message)
+ expect(page).not_to have_content(email_error_message)
+ expect(page).not_to have_content(email_warning_message)
+
+ fill_in 'new_user_email', with: 'foo@'
+ fill_in 'new_user_first_name', with: ''
+
+ expect(page).not_to have_content(email_hint_message)
+ expect(page).to have_content(email_error_message)
+ expect(page).not_to have_content(email_warning_message)
+
+ fill_in 'new_user_email', with: 'foo@bar'
+ fill_in 'new_user_first_name', with: ''
+
+ expect(page).not_to have_content(email_hint_message)
+ expect(page).not_to have_content(email_error_message)
+ expect(page).to have_content(email_warning_message)
+
+ fill_in 'new_user_email', with: 'foo@gitlab.com'
+ fill_in 'new_user_first_name', with: ''
+
+ expect(page).not_to have_content(email_hint_message)
+ expect(page).not_to have_content(email_error_message)
+ expect(page).not_to have_content(email_warning_message)
+ end
+ end
+
+ context 'when trial_email_validation flag disabled' do
+ before do
+ stub_feature_flags trial_email_validation: false
+ end
+
+ it 'does not show an error message' do
+ visit path
+ expect(page).to have_content(email_hint_message)
+ expect(page).not_to have_content(email_error_message)
+ expect(page).not_to have_content(email_warning_message)
+
+ fill_in 'new_user_email', with: 'foo@'
+
+ expect(page).to have_content(email_hint_message)
+ expect(page).not_to have_content(email_error_message)
+ expect(page).not_to have_content(email_warning_message)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/user_views_tag_shared_examples.rb b/spec/support/shared_examples/features/user_views_tag_shared_examples.rb
new file mode 100644
index 00000000000..989de1dbfbb
--- /dev/null
+++ b/spec/support/shared_examples/features/user_views_tag_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'user views tag' do
+ context 'when user views with the tag' do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+ let(:tag_name) { "stable" }
+ let!(:release) { create(:release, project: project, tag: tag_name, name: "ReleaseName") }
+
+ before do
+ project.add_developer(user)
+ project.repository.add_tag(user, tag_name, project.default_branch_or_main)
+
+ sign_in(user)
+ end
+
+ shared_examples 'shows tag' do
+ it do
+ visit tag_page
+
+ expect(page).to have_content tag_name
+ expect(page).to have_link("ReleaseName", href: project_release_path(project, release))
+ end
+ end
+
+ it_behaves_like 'shows tag'
+
+ context 'when tag name contains a slash' do
+ let(:tag_name) { "stable/v0.1" }
+
+ it_behaves_like 'shows tag'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb
index c63faace6b2..9d81c0e9a3e 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'variable list' do
+RSpec.shared_examples 'variable list' do |is_admin|
it 'shows a list of variables' do
page.within('[data-testid="ci-variable-table"]') do
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key)
@@ -166,7 +166,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
expect(find('.flash-container')).to be_present
- expect(find('[data-testid="alert-danger"]').text).to have_content('Variables key (key) has already been taken')
+ expect(find('[data-testid="alert-danger"]').text).to have_content('(key) has already been taken')
end
it 'prevents a variable to be added if no values are provided when a variable is set to masked' do
@@ -257,7 +257,11 @@ RSpec.shared_examples 'variable list' do
end
it 'shows a message regarding the changed default' do
- expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default'
+ if is_admin
+ expect(page).to have_content 'Environment variables on this GitLab instance are configured to be protected by default'
+ else
+ expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default'
+ end
end
end
diff --git a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb
index 0ef1ccdfe57..8d1502bed84 100644
--- a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb
@@ -12,8 +12,8 @@ RSpec.shared_examples 'wiki file attachments' do
end
context 'before uploading' do
- it 'shows "Attach a file" button' do
- expect(page).to have_button('Attach a file')
+ it 'shows "Attach a file or image" button' do
+ expect(page).to have_selector('[data-testid="button-attach-file"]')
expect(page).not_to have_selector('.uploading-progress-container', visible: true)
end
end
@@ -26,7 +26,7 @@ RSpec.shared_examples 'wiki file attachments' do
click_button 'Cancel'
end
- expect(page).to have_button('Attach a file')
+ expect(page).to have_selector('[data-testid="button-attach-file"]')
expect(page).not_to have_button('Cancel')
expect(page).not_to have_selector('.uploading-progress-container', visible: true)
end
@@ -41,11 +41,11 @@ RSpec.shared_examples 'wiki file attachments' do
end
context 'uploading is complete' do
- it 'shows "Attach a file" button on uploading complete' do
+ it 'shows "Attach a file or image" button on uploading complete' do
attach_with_dropzone
wait_for_requests
- expect(page).to have_button('Attach a file')
+ expect(page).to have_selector('[data-testid="button-attach-file"]')
expect(page).not_to have_selector('.uploading-progress-container', visible: true)
end
diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
index 79c7c1891ac..87067336a36 100644
--- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
@@ -140,7 +140,7 @@ RSpec.shared_examples 'User updates wiki page' do
context 'when using the content editor' do
context 'with feature flag on' do
before do
- click_button 'Edit rich text'
+ find('[data-testid="toggle-editing-mode-button"] label', text: 'Rich text').click
end
it_behaves_like 'edits content using the content editor'
diff --git a/spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb
new file mode 100644
index 00000000000..f28348fb945
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'issuable supports timelog creation mutation' do
+ context 'when the user is anonymous' do
+ before do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when the user is a guest member of the namespace' do
+ let(:current_user) { create(:user) }
+
+ before do
+ users_container.add_guest(current_user)
+
+ post_graphql_mutation(mutation, current_user: current_user)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when user has permissions to create a timelog' do
+ let(:current_user) { author }
+
+ before do
+ users_container.add_reporter(current_user)
+ end
+
+ context 'with valid data' do
+ it 'creates the timelog' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change(Timelog, :count).by(1)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to be_empty
+ expect(mutation_response['timelog']).to include(
+ 'timeSpent' => 3600,
+ 'spentAt' => '2022-07-08T00:00:00Z',
+ 'summary' => 'Test summary'
+ )
+ end
+ end
+
+ context 'with invalid time_spent' do
+ let(:time_spent) { '3h e' }
+
+ it 'returns an error' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change(Timelog, :count).by(0)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to match_array(['Time spent can\'t be blank'])
+ expect(mutation_response['timelog']).to be_nil
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'issuable does not support timelog creation mutation' do
+ context 'when the user is anonymous' do
+ before do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when the user is a guest member of the namespace' do
+ let(:current_user) { create(:user) }
+
+ before do
+ users_container.add_guest(current_user)
+
+ post_graphql_mutation(mutation, current_user: current_user)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors' do
+ let(:match_errors) { contain_exactly(include('is not a valid ID for')) }
+ end
+ end
+
+ context 'when user has permissions to create a timelog' do
+ let(:current_user) { author }
+
+ before do
+ users_container.add_reporter(current_user)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors' do
+ let(:match_errors) { contain_exactly(include('is not a valid ID for')) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb
deleted file mode 100644
index 3c32b7e0310..00000000000
--- a/spec/support/shared_examples/graphql/mutations/work_items/update_weight_widget_shared_examples.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'update work item weight widget' do
- it 'updates the weight widget' do
- expect do
- post_graphql_mutation(mutation, current_user: current_user)
- work_item.reload
- end.to change(work_item, :weight).from(nil).to(new_weight)
-
- expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['workItem']['widgets']).to include(
- {
- 'weight' => new_weight,
- 'type' => 'WEIGHT'
- }
- )
- end
-
- context 'when the updated work item is not valid' do
- it 'returns validation errors without the work item' do
- errors = ActiveModel::Errors.new(work_item).tap { |e| e.add(:weight, 'error message') }
-
- allow_next_found_instance_of(::WorkItem) do |instance|
- allow(instance).to receive(:valid?).and_return(false)
- allow(instance).to receive(:errors).and_return(errors)
- end
-
- post_graphql_mutation(mutation, current_user: current_user)
-
- expect(mutation_response['workItem']).to be_nil
- expect(mutation_response['errors']).to match_array(['Weight error message'])
- end
- end
-end
diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
index 2c6118779e6..0aa3bf8944f 100644
--- a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
@@ -94,5 +94,6 @@ RSpec.shared_examples 'a Note mutation with confidential notes' do
expect(mutation_response).to have_key('note')
expect(mutation_response['note']['confidential']).to eq(true)
+ expect(mutation_response['note']['internal']).to eq(true)
end
end
diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
index 7fd54408b11..2d7da9f9f00 100644
--- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
@@ -69,4 +69,21 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
'This feature is in Alpha. It can be changed or removed at any time. Introduced in 1.10.'
)
end
+
+ it 'supports :alpha' do
+ deprecable = subject(alpha: { milestone: '1.10' })
+
+ expect(deprecable.deprecation_reason).to eq(
+ 'This feature is in Alpha. It can be changed or removed at any time. Introduced in 1.10.'
+ )
+ end
+
+ it 'does not allow :alpha and :deprecated together' do
+ expect do
+ subject(alpha: { milestone: '1.10' }, deprecated: { milestone: '1.10', reason: 'my reason' } )
+ end.to raise_error(
+ ArgumentError,
+ eq("`alpha` and `deprecated` arguments cannot be passed at the same time")
+ )
+ end
end
diff --git a/spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb b/spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb
index c2c27fb65ca..61c8a3f47df 100644
--- a/spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb
+++ b/spec/support/shared_examples/helpers/wiki_helpers_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'wiki endpoint helpers' do
let(:resource_path) { page.wiki.container.class.to_s.pluralize.downcase }
- let(:url) { "/api/v4/#{resource_path}/#{page.wiki.container.id}/wikis/#{page.slug}?version=#{page.version.id}"}
+ let(:url) { "/api/v4/#{resource_path}/#{page.wiki.container.id}/wikis/#{page.slug}?version=#{page.version.id}" }
it 'returns the full endpoint url' do
expect(helper.wiki_page_render_api_endpoint(page)).to end_with(url)
diff --git a/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb
index 95772b1774a..5eae8777a20 100644
--- a/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb
@@ -86,7 +86,10 @@ RSpec.shared_examples 'with inheritable CI config' do
expect do
# we ignore exceptions as `#overwrite_entry`
# can raise exception on duplicates
- entry.send(:inherit!, deps) rescue described_class::InheritError
+
+ entry.send(:inherit!, deps)
+ rescue described_class::InheritError
+ nil
end.not_to change { entry[entry_key] }
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
index 9d280d9404a..481e11bcf0e 100644
--- a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
@@ -33,7 +33,7 @@ RSpec.shared_examples 'does not track when feature flag is disabled' do |feature
end
end
-RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events' do
+RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events for given event params' do
before do
stub_application_setting(usage_ping_enabled: true)
end
@@ -44,22 +44,21 @@ RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events
specify do
aggregate_failures do
- expect(track_action(author: user1, project: project)).to be_truthy
- expect(track_action(author: user1, project: project)).to be_truthy
- expect(track_action(author: user2, project: project)).to be_truthy
+ expect(track_action({ author: user1 }.merge(track_params))).to be_truthy
+ expect(track_action({ author: user1 }.merge(track_params))).to be_truthy
+ expect(track_action({ author: user2 }.merge(track_params))).to be_truthy
expect(count_unique).to eq(2)
end
end
it 'does not track edit actions if author is not present' do
- expect(track_action(author: nil, project: project)).to be_nil
+ expect(track_action({ author: nil }.merge(track_params))).to be_nil
end
it 'emits snowplow event' do
- track_action(author: user1, project: project)
+ track_action({ author: user1 }.merge(track_params))
- expect_snowplow_event(category: 'issues_edit', action: action, user: user1,
- namespace: project.namespace, project: project)
+ expect_snowplow_event(**{ category: category, action: event_action, user: user1 }.merge(event_params))
end
context 'with route_hll_to_snowplow_phase2 disabled' do
@@ -68,9 +67,33 @@ RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events
end
it 'does not emit snowplow event' do
- track_action(author: user1, project: project)
+ track_action({ author: user1 }.merge(track_params))
expect_no_snowplow_event
end
end
end
+
+RSpec.shared_examples 'daily tracked issuable snowplow and service ping events with project' do
+ it_behaves_like 'a daily tracked issuable snowplow and service ping events for given event params' do
+ let(:track_params) { { project: project } }
+ let(:event_params) { track_params.merge(label: event_label, property: event_property, namespace: project.namespace) }
+ end
+end
+
+RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events with namespace' do
+ it_behaves_like 'a daily tracked issuable snowplow and service ping events for given event params' do
+ let(:track_params) { { namespace: namespace } }
+ let(:event_params) { track_params.merge(label: event_label, property: event_property) }
+ end
+end
+
+RSpec.shared_examples 'does not track with namespace when feature flag is disabled' do |feature_flag|
+ context "when feature flag #{feature_flag} is disabled" do
+ it 'does not track action' do
+ stub_feature_flags(feature_flag => false)
+
+ expect(track_action(author: user1, namespace: namespace)).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
index d189e91effd..fb08784f34f 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -3,7 +3,6 @@
RSpec.shared_examples "chat integration" do |integration_name|
describe "Associations" do
it { is_expected.to belong_to :project }
- it { is_expected.to have_one :service_hook }
end
describe "Validations" do
@@ -13,6 +12,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
end
it { is_expected.to validate_presence_of(:webhook) }
+
it_behaves_like "issue tracker integration URL attribute", :webhook
end
diff --git a/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb b/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb
new file mode 100644
index 00000000000..0c71ebe7a4d
--- /dev/null
+++ b/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'has ID tokens' do |ci_type|
+ subject(:ci) { FactoryBot.build(ci_type) }
+
+ describe 'delegations' do
+ it { is_expected.to delegate_method(:id_tokens).to(:metadata).allow_nil }
+ end
+
+ describe '#id_tokens?' do
+ subject { ci.id_tokens? }
+
+ context 'without metadata' do
+ let(:ci) { FactoryBot.build(ci_type) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with metadata' do
+ let(:ci) { FactoryBot.build(ci_type, metadata: FactoryBot.build(:ci_build_metadata, id_tokens: id_tokens)) }
+
+ context 'when ID tokens exist' do
+ let(:id_tokens) { { TEST_JOB_JWT: { id_token: { aud: 'developers ' } } } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when ID tokens do not exist' do
+ let(:id_tokens) { {} }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+ end
+
+ describe '#id_tokens=' do
+ it 'assigns the ID tokens to the CI job' do
+ id_tokens = [{ 'JOB_ID_TOKEN' => { 'id_token' => { 'aud' => 'https://gitlab.test ' } } }]
+ ci.id_tokens = id_tokens
+
+ expect(ci.id_tokens).to match_array(id_tokens)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
index f92ed3d7396..f4d5ab3d5c6 100644
--- a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
@@ -2,6 +2,10 @@
require 'spec_helper'
RSpec.shared_examples_for CounterAttribute do |counter_attributes|
+ before do
+ Gitlab::ApplicationContext.push(feature_category: 'test', caller_id: 'caller')
+ end
+
it 'defines a Redis counter_key' do
expect(model.counter_key(:counter_name))
.to eq("project:{#{model.project_id}}:counters:CounterAttributeModel:#{model.id}:counter_name")
@@ -22,7 +26,21 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
where(:increment) { [10, -3] }
with_them do
- it 'increments the counter in Redis' do
+ it 'increments the counter in Redis and logs it' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Increment counter attribute',
+ attribute: attribute,
+ project_id: model.project_id,
+ increment: increment,
+ new_counter_value: 0 + increment,
+ current_db_value: model.read_attribute(attribute),
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
+ )
+
subject
Gitlab::Redis::SharedState.with do |redis|
@@ -86,7 +104,21 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
model.delayed_increment_counter(incremented_attribute, -3)
end
- it 'updates the record' do
+ it 'updates the record and logs it' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Flush counter attribute to database',
+ attribute: incremented_attribute,
+ project_id: model.project_id,
+ increment: 7,
+ previous_db_value: 0,
+ new_db_value: 7,
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
+ )
+
expect { subject }.to change { model.reset.read_attribute(incremented_attribute) }.by(7)
end
@@ -153,4 +185,32 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
end
end
end
+
+ describe '#clear_counter!' do
+ let(:attribute) { counter_attributes.first }
+
+ before do
+ model.increment_counter(attribute, 10)
+ end
+
+ it 'deletes the counter key for the given attribute and logs it' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Clear counter attribute',
+ attribute: attribute,
+ project_id: model.project_id,
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
+ )
+
+ model.clear_counter!(attribute)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ key_exists = redis.exists(model.counter_key(attribute))
+ expect(key_exists).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
index d80be5be3b3..7512a9f2855 100644
--- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
@@ -13,7 +13,6 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
describe "Associations" do
it { is_expected.to belong_to :project }
- it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
@@ -23,6 +22,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
it { is_expected.to validate_presence_of(:webhook) }
+
it_behaves_like 'issue tracker integration URL attribute', :webhook
end
diff --git a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
index ae72cb6ec5d..2f693edeb53 100644
--- a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
+++ b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb
@@ -3,6 +3,10 @@
RSpec.shared_examples Integrations::HasWebHook do
include AfterNextHelpers
+ describe 'associations' do
+ it { is_expected.to have_one(:service_hook).inverse_of(:integration).with_foreign_key(:service_id) }
+ end
+
describe 'callbacks' do
it 'calls #update_web_hook! when enabled' do
expect(integration).to receive(:update_web_hook!)
diff --git a/spec/support/shared_examples/models/issuable_link_shared_examples.rb b/spec/support/shared_examples/models/issuable_link_shared_examples.rb
index 9892e66b582..42c7be5ddc3 100644
--- a/spec/support/shared_examples/models/issuable_link_shared_examples.rb
+++ b/spec/support/shared_examples/models/issuable_link_shared_examples.rb
@@ -16,6 +16,7 @@ RSpec.shared_examples 'issuable link' do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:target) }
+
it do
is_expected.to validate_uniqueness_of(:source)
.scoped_to(:target_id)
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index aa40a2c7135..287b046cbec 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -63,16 +63,23 @@ RSpec.shared_examples '#valid_level_roles' do |entity_name|
let(:entity) { create(entity_name) } # rubocop:disable Rails/SaveBang
let(:entity_member) { create("#{entity_name}_member", :developer, source: entity, user: member_user) }
let(:presenter) { described_class.new(entity_member, current_user: member_user) }
- let(:expected_roles) { { 'Developer' => 30, 'Maintainer' => 40, 'Reporter' => 20 } }
- it 'returns all roles when no parent member is present' do
- expect(presenter.valid_level_roles).to eq(entity_member.class.access_level_roles)
+ context 'when no parent member is present' do
+ let(:all_permissible_roles) { entity_member.class.permissible_access_level_roles(member_user, entity) }
+
+ it 'returns all permissible roles' do
+ expect(presenter.valid_level_roles).to eq(all_permissible_roles)
+ end
end
- it 'returns higher roles when a parent member is present' do
- group.add_reporter(member_user)
+ context 'when parent member is present' do
+ before do
+ group.add_reporter(member_user)
+ end
- expect(presenter.valid_level_roles).to eq(expected_roles)
+ it 'returns higher roles when a parent member is present' do
+ expect(presenter.valid_level_roles).to eq(expected_roles)
+ end
end
end
diff --git a/spec/support/shared_examples/models/project_shared_examples.rb b/spec/support/shared_examples/models/project_shared_examples.rb
index 475ac1da04b..0b880f00a22 100644
--- a/spec/support/shared_examples/models/project_shared_examples.rb
+++ b/spec/support/shared_examples/models/project_shared_examples.rb
@@ -25,3 +25,38 @@ RSpec.shared_examples 'returns true if project is inactive' do
end
end
end
+
+RSpec.shared_examples 'checks parent group feature flag' do
+ let(:group) { subject_project.group }
+ let(:root_group) { group.parent }
+
+ subject { subject_project.public_send(feature_flag_method) }
+
+ context 'when feature flag is disabled globally' do
+ before do
+ stub_feature_flags(feature_flag => false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when feature flag is enabled globally' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when feature flag is enabled for the root group' do
+ before do
+ stub_feature_flags(feature_flag => root_group)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when feature flag is enabled for the group' do
+ before do
+ stub_feature_flags(feature_flag => group)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+end
diff --git a/spec/support/shared_examples/models/taskable_shared_examples.rb b/spec/support/shared_examples/models/taskable_shared_examples.rb
index 34b1d735bcd..3ae240c8da8 100644
--- a/spec/support/shared_examples/models/taskable_shared_examples.rb
+++ b/spec/support/shared_examples/models/taskable_shared_examples.rb
@@ -18,9 +18,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('2 of')
- expect(subject.task_status).to match('5 tasks completed')
+ expect(subject.task_status).to match('5 checklist items completed')
expect(subject.task_status_short).to match('2/')
- expect(subject.task_status_short).to match('5 tasks')
+ expect(subject.task_status_short).to match('5 checklist items')
end
describe '#tasks?' do
@@ -53,9 +53,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('3 of')
- expect(subject.task_status).to match('9 tasks completed')
+ expect(subject.task_status).to match('9 checklist items completed')
expect(subject.task_status_short).to match('3/')
- expect(subject.task_status_short).to match('9 tasks')
+ expect(subject.task_status_short).to match('9 checklist items')
end
end
@@ -68,9 +68,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('0 of')
- expect(subject.task_status).to match('1 task completed')
+ expect(subject.task_status).to match('1 checklist item completed')
expect(subject.task_status_short).to match('0/')
- expect(subject.task_status_short).to match('1 task')
+ expect(subject.task_status_short).to match('1 checklist item')
end
end
@@ -87,9 +87,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('0 of')
- expect(subject.task_status).to match('0 tasks completed')
+ expect(subject.task_status).to match('0 checklist items completed')
expect(subject.task_status_short).to match('0/')
- expect(subject.task_status_short).to match('0 task')
+ expect(subject.task_status_short).to match('0 checklist items')
end
end
@@ -102,9 +102,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('1 of')
- expect(subject.task_status).to match('1 task completed')
+ expect(subject.task_status).to match('1 checklist item completed')
expect(subject.task_status_short).to match('1/')
- expect(subject.task_status_short).to match('1 task')
+ expect(subject.task_status_short).to match('1 checklist item')
end
end
@@ -123,9 +123,9 @@ RSpec.shared_examples 'a Taskable' do
it 'returns the correct task status' do
expect(subject.task_status).to match('2 of')
- expect(subject.task_status).to match('4 tasks completed')
+ expect(subject.task_status).to match('4 checklist items completed')
expect(subject.task_status_short).to match('2/')
- expect(subject.task_status_short).to match('4 tasks')
+ expect(subject.task_status_short).to match('4 checklist items')
end
end
end
diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
index 45da1d382c1..807295f8442 100644
--- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
@@ -273,14 +273,6 @@ RSpec.shared_examples 'namespace traversal scopes' do
include_examples '.self_and_descendants'
end
-
- context 'with linear_scopes_superset feature flag disabled' do
- before do
- stub_feature_flags(linear_scopes_superset: false)
- end
-
- include_examples '.self_and_descendants'
- end
end
shared_examples '.self_and_descendant_ids' do
@@ -324,14 +316,6 @@ RSpec.shared_examples 'namespace traversal scopes' do
include_examples '.self_and_descendant_ids'
end
-
- context 'with linear_scopes_superset feature flag disabled' do
- before do
- stub_feature_flags(linear_scopes_superset: false)
- end
-
- include_examples '.self_and_descendant_ids'
- end
end
shared_examples '.self_and_hierarchy' do
diff --git a/spec/support/shared_examples/policies/group_project_namespace_policy_shared_examples.rb b/spec/support/shared_examples/policies/group_project_namespace_policy_shared_examples.rb
new file mode 100644
index 00000000000..9a1f0e685be
--- /dev/null
+++ b/spec/support/shared_examples/policies/group_project_namespace_policy_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'checks timelog categories permissions' do
+ context 'with no user' do
+ let_it_be(:current_user) { nil }
+
+ it { is_expected.to be_disallowed(:read_timelog_category) }
+ end
+
+ context 'with a regular user' do
+ let_it_be(:current_user) { create(:user) }
+
+ it { is_expected.to be_disallowed(:read_timelog_category) }
+ end
+
+ context 'with a reporter user' do
+ let_it_be(:current_user) { create(:user) }
+
+ before do
+ users_container.add_reporter(current_user)
+ end
+
+ context 'when timelog_categories is enabled' do
+ it { is_expected.to be_allowed(:read_timelog_category) }
+ end
+
+ context 'when timelog_categories is disabled' do
+ before do
+ stub_feature_flags(timelog_categories: false)
+ end
+
+ it { is_expected.to be_disallowed(:read_timelog_category) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
index a12cb24a513..32562aef8d2 100644
--- a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
@@ -128,10 +128,10 @@ RSpec.shared_examples 'discussions API' do |parent_type, noteable_type, id_name,
stub_feature_flags(notes_create_service_tracking: false)
end
- it 'does not track any events', :snowplow do
+ it 'does not track Notes::CreateService events', :snowplow do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), params: { body: 'hi!' }
- expect_no_snowplow_event
+ expect_no_snowplow_event(category: 'Notes::CreateService', action: 'execute')
end
end
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index a59235486ec..8479493911b 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -376,13 +376,28 @@ RSpec.shared_examples 'noteable API with confidential notes' do |parent_type, no
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params
end
- it "creates a confidential note if confidential is set to true" do
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(confidential: true)
+ context 'with internal param' do
+ it "creates a confidential note if internal is set to true" do
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(internal: true)
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response['body']).to eq('hi!')
- expect(json_response['confidential']).to be_truthy
- expect(json_response['author']['username']).to eq(user.username)
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_truthy
+ expect(json_response['internal']).to be_truthy
+ expect(json_response['author']['username']).to eq(user.username)
+ end
+ end
+
+ context 'with deprecated confidential param' do
+ it "creates a confidential note if confidential is set to true" do
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(confidential: true)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_truthy
+ expect(json_response['internal']).to be_truthy
+ expect(json_response['author']['username']).to eq(user.username)
+ end
end
end
end
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index 8d6d85732be..b651ffc8996 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -244,7 +244,7 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
let(:headers) do
case auth
when :oauth
- build_token_auth_header(token.token)
+ build_token_auth_header(token.plaintext_token)
when :personal_access_token
build_token_auth_header(personal_access_token.token)
when :job_token
@@ -404,7 +404,7 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project|
shared_examples 'handling all conditions' do
context 'with oauth token' do
- let(:headers) { build_token_auth_header(token.token) }
+ let(:headers) { build_token_auth_header(token.plaintext_token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
@@ -514,7 +514,7 @@ RSpec.shared_examples 'handling create dist tag requests' do |scope: :project|
shared_examples 'handling all conditions' do
context 'with oauth token' do
- let(:headers) { build_token_auth_header(token.token) }
+ let(:headers) { build_token_auth_header(token.plaintext_token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
@@ -622,7 +622,7 @@ RSpec.shared_examples 'handling delete dist tag requests' do |scope: :project|
shared_examples 'handling all conditions' do
context 'with oauth token' do
- let(:headers) { build_token_auth_header(token.token) }
+ let(:headers) { build_token_auth_header(token.plaintext_token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
index ca86cb082a7..6cae7d8e00f 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
@@ -23,7 +23,7 @@ RSpec.shared_examples 'creates an alert management alert or errors' do
end
context 'and fails to save' do
- let(:errors) { double(messages: { hosts: ['hosts array is over 255 chars'] })}
+ let(:errors) { double(messages: { hosts: ['hosts array is over 255 chars'] }, '[]': [] )}
before do
allow(service).to receive(:alert).and_call_original
@@ -35,9 +35,10 @@ RSpec.shared_examples 'creates an alert management alert or errors' do
it 'writes a warning to the log' do
expect(Gitlab::AppLogger).to receive(:warn).with(
- message: "Unable to create AlertManagement::Alert from #{source}",
+ message: "Unable to create AlertManagement::Alert",
project_id: project.id,
- alert_errors: { hosts: ['hosts array is over 255 chars'] }
+ alert_errors: { hosts: ['hosts array is over 255 chars'] },
+ alert_source: source
)
subject
@@ -45,6 +46,46 @@ RSpec.shared_examples 'creates an alert management alert or errors' do
end
end
+RSpec.shared_examples 'handles race condition in alert creation' do
+ let(:other_alert) { create(:alert_management_alert, project: project) }
+
+ context 'when another alert is saved at the same time' do
+ before do
+ allow_next_instance_of(::AlertManagement::Alert) do |alert|
+ allow(alert).to receive(:save) do
+ other_alert.update!(fingerprint: alert.fingerprint)
+
+ raise ActiveRecord::RecordNotUnique
+ end
+ end
+ end
+
+ it 'finds the other alert and increments the counter' do
+ subject
+
+ expect(other_alert.reload.events).to eq(2)
+ end
+ end
+
+ context 'when another alert is saved before the validation runes' do
+ before do
+ allow_next_instance_of(::AlertManagement::Alert) do |alert|
+ allow(alert).to receive(:save).and_wrap_original do |method, *args|
+ other_alert.update!(fingerprint: alert.fingerprint)
+
+ method.call(*args)
+ end
+ end
+ end
+
+ it 'finds the other alert and increments the counter' do
+ subject
+
+ expect(other_alert.reload.events).to eq(2)
+ end
+ end
+end
+
# This shared_example requires the following variables:
# - last_alert_attributes, last created alert
# - project, project that alert created
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
index f8e096297d3..eb9f76d8626 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb
@@ -4,8 +4,6 @@
# - `alert`, the alert to be resolved
RSpec.shared_examples 'resolves an existing alert management alert' do
it 'sets the end time and status' do
- expect(Gitlab::AppLogger).not_to receive(:warn)
-
expect { subject }
.to change { alert.reload.resolved? }.to(true)
.and change { alert.ended_at.present? }.to(true)
@@ -22,36 +20,6 @@ RSpec.shared_examples 'does not change the alert end time' do
end
end
-# This shared_example requires the following variables:
-# - `project`, expected project for an incoming alert
-# - `service`, a service which includes AlertManagement::AlertProcessing
-# - `alert` (optional), the alert which should fail to resolve. If not
-# included, the log is expected to correspond to a new alert
-RSpec.shared_examples 'writes a warning to the log for a failed alert status update' do
- before do
- allow(service).to receive(:alert).and_call_original
- allow(service).to receive_message_chain(:alert, :resolve).and_return(false)
- end
-
- specify do
- expect(Gitlab::AppLogger).to receive(:warn).with(
- message: 'Unable to update AlertManagement::Alert status to resolved',
- project_id: project.id,
- alert_id: alert ? alert.id : (last_alert_id + 1)
- )
-
- # Failure to resolve a recovery alert is not a critical failure
- expect(subject).to be_success
- end
-
- private
-
- def last_alert_id
- AlertManagement::Alert.connection
- .select_value("SELECT nextval('#{AlertManagement::Alert.sequence_name}')")
- end
-end
-
RSpec.shared_examples 'processes recovery alert' do
context 'seen for the first time' do
let(:alert) { AlertManagement::Alert.last }
@@ -69,7 +37,6 @@ RSpec.shared_examples 'processes recovery alert' do
it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert
it_behaves_like 'sends alert notification emails if enabled'
it_behaves_like 'closes related incident if enabled'
- it_behaves_like 'writes a warning to the log for a failed alert status update'
it_behaves_like 'does not create an alert management alert'
it_behaves_like 'does not process incident issues'
@@ -83,7 +50,6 @@ RSpec.shared_examples 'processes recovery alert' do
it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert
it_behaves_like 'sends alert notification emails if enabled'
it_behaves_like 'closes related incident if enabled'
- it_behaves_like 'writes a warning to the log for a failed alert status update'
it_behaves_like 'does not create an alert management alert'
it_behaves_like 'does not process incident issues'
@@ -97,7 +63,6 @@ RSpec.shared_examples 'processes recovery alert' do
it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert
it_behaves_like 'sends alert notification emails if enabled'
it_behaves_like 'closes related incident if enabled'
- it_behaves_like 'writes a warning to the log for a failed alert status update'
it_behaves_like 'does not create an alert management alert'
it_behaves_like 'does not process incident issues'
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb
index 98834f01ce2..6becc3dc071 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# Expects usage of 'incident settings enabled' context.
+# Expects usage of 'incident management settings enabled' context.
#
# This shared_example includes the following option:
# - with_issue: includes a test for when the defined `alert` has an associated issue
@@ -8,7 +8,7 @@
# This shared_example requires the following variables:
# - `alert`, required if :with_issue is true
RSpec.shared_examples 'processes incident issues if enabled' do |with_issue: false|
- include_examples 'processes incident issues', with_issue
+ include_examples 'processes incident issues', with_issue: with_issue
context 'with incident setting disabled' do
let(:create_issue) { false }
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
index 3add5485fca..1973577d742 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# Expects usage of 'incident settings enabled' context.
+# Expects usage of 'incident management settings enabled' context.
#
# This shared_example requires the following variables:
# - `alert`, alert for which related incidents should be closed
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb
index 5f30b58176b..92e7dee7533 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-# Expects usage of 'incident settings enabled' context.
+# Expects usage of 'incident management settings enabled' context.
#
# This shared_example includes the following option:
# - count: number of notifications expected to be sent
RSpec.shared_examples 'sends alert notification emails if enabled' do |count: 1|
- include_examples 'sends alert notification emails', count
+ include_examples 'sends alert notification emails', count: count
context 'with email setting disabled' do
let(:send_email) { false }
diff --git a/spec/support/shared_examples/services/boards/lists_move_service_shared_examples.rb b/spec/support/shared_examples/services/boards/lists_move_service_shared_examples.rb
index bf84b912610..97d0bae3552 100644
--- a/spec/support/shared_examples/services/boards/lists_move_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/boards/lists_move_service_shared_examples.rb
@@ -1,95 +1,103 @@
# frozen_string_literal: true
RSpec.shared_examples 'lists move service' do
- let!(:planning) { create(:list, board: board, position: 0) }
- let!(:development) { create(:list, board: board, position: 1) }
- let!(:review) { create(:list, board: board, position: 2) }
- let!(:staging) { create(:list, board: board, position: 3) }
- let!(:closed) { create(:closed_list, board: board) }
+ shared_examples 'correct movement behavior' do
+ context 'when list type is set to label' do
+ it 'does not reorder lists when new position is nil' do
+ service = described_class.new(parent, user, position: nil)
- context 'when list type is set to label' do
- it 'keeps position of lists when new position is nil' do
- service = described_class.new(parent, user, position: nil)
+ service.execute(planning)
- service.execute(planning)
+ expect(ordered_lists).to eq([planning, development, review, staging])
+ end
- expect(current_list_positions).to eq [0, 1, 2, 3]
- end
-
- it 'keeps position of lists when new position is equal to old position' do
- service = described_class.new(parent, user, position: planning.position)
+ it 'does not reorder lists when new position is equal to old position' do
+ service = described_class.new(parent, user, position: planning.position)
- service.execute(planning)
+ service.execute(planning)
- expect(current_list_positions).to eq [0, 1, 2, 3]
- end
+ expect(ordered_lists).to eq([planning, development, review, staging])
+ end
- it 'keeps position of lists when new position is negative' do
- service = described_class.new(parent, user, position: -1)
+ it 'does not reorder lists when new position is negative' do
+ service = described_class.new(parent, user, position: -1)
- service.execute(planning)
+ service.execute(planning)
- expect(current_list_positions).to eq [0, 1, 2, 3]
- end
+ expect(ordered_lists).to eq([planning, development, review, staging])
+ end
- it 'keeps position of lists when new position is equal to number of labels lists' do
- service = described_class.new(parent, user, position: board.lists.label.size)
+ it 'does not reorder lists when new position is bigger then last position' do
+ service = described_class.new(parent, user, position: ordered_lists.last.position + 1)
- service.execute(planning)
+ service.execute(planning)
- expect(current_list_positions).to eq [0, 1, 2, 3]
- end
+ expect(ordered_lists).to eq([planning, development, review, staging])
+ end
- it 'keeps position of lists when new position is greater than number of labels lists' do
- service = described_class.new(parent, user, position: board.lists.label.size + 1)
+ it 'moves the list to the first position when new position is equal to first position' do
+ service = described_class.new(parent, user, position: 0)
- service.execute(planning)
+ service.execute(staging)
- expect(current_list_positions).to eq [0, 1, 2, 3]
- end
+ expect(ordered_lists).to eq([staging, planning, development, review])
+ end
- it 'increments position of intermediate lists when new position is equal to first position' do
- service = described_class.new(parent, user, position: 0)
+ it 'moves the list to the last position when new position is equal to last position' do
+ service = described_class.new(parent, user, position: board.lists.label.last.position)
- service.execute(staging)
+ service.execute(planning)
- expect(current_list_positions).to eq [1, 2, 3, 0]
- end
+ expect(ordered_lists).to eq([development, review, staging, planning])
+ end
- it 'decrements position of intermediate lists when new position is equal to last position' do
- service = described_class.new(parent, user, position: board.lists.label.last.position)
+ it 'moves the list to the correct position when new position is greater than old position (third list)' do
+ service = described_class.new(parent, user, position: review.position)
- service.execute(planning)
+ service.execute(planning)
- expect(current_list_positions).to eq [3, 0, 1, 2]
- end
+ expect(ordered_lists).to eq([development, review, planning, staging])
+ end
- it 'decrements position of intermediate lists when new position is greater than old position' do
- service = described_class.new(parent, user, position: 2)
+ it 'moves the list to the correct position when new position is lower than old position (second list)' do
+ service = described_class.new(parent, user, position: development.position)
- service.execute(planning)
+ service.execute(staging)
- expect(current_list_positions).to eq [2, 0, 1, 3]
+ expect(ordered_lists).to eq([planning, staging, development, review])
+ end
end
- it 'increments position of intermediate lists when new position is lower than old position' do
- service = described_class.new(parent, user, position: 1)
+ it 'keeps position of lists when list type is closed' do
+ service = described_class.new(parent, user, position: 2)
- service.execute(staging)
+ service.execute(closed)
- expect(current_list_positions).to eq [0, 2, 3, 1]
+ expect(ordered_lists).to eq([planning, development, review, staging])
end
end
- it 'keeps position of lists when list type is closed' do
- service = described_class.new(parent, user, position: 2)
+ context 'with complete position sequence' do
+ let!(:planning) { create(:list, board: board, position: 0) }
+ let!(:development) { create(:list, board: board, position: 1) }
+ let!(:review) { create(:list, board: board, position: 2) }
+ let!(:staging) { create(:list, board: board, position: 3) }
+ let!(:closed) { create(:closed_list, board: board) }
+
+ it_behaves_like 'correct movement behavior'
+ end
- service.execute(closed)
+ context 'with corrupted position sequence' do
+ let!(:planning) { create(:list, board: board, position: 0) }
+ let!(:staging) { create(:list, board: board, position: 6) }
+ let!(:development) { create(:list, board: board, position: 1) }
+ let!(:review) { create(:list, board: board, position: 4) }
+ let!(:closed) { create(:closed_list, board: board) }
- expect(current_list_positions).to eq [0, 1, 2, 3]
+ it_behaves_like 'correct movement behavior'
end
- def current_list_positions
- [planning, development, review, staging].map { |list| list.reload.position }
+ def ordered_lists
+ board.lists.where.not(position: nil)
end
end
diff --git a/spec/support/shared_examples/services/issuable_shared_examples.rb b/spec/support/shared_examples/services/issuable_shared_examples.rb
index a50a386afe1..142d4ae8531 100644
--- a/spec/support/shared_examples/services/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable_shared_examples.rb
@@ -45,7 +45,7 @@ RSpec.shared_examples 'updating a single task' do
end
it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 1** as completed')
+ note1 = find_note('marked the checklist item **Task 1** as completed')
expect(note1).not_to be_nil
@@ -61,7 +61,7 @@ RSpec.shared_examples 'updating a single task' do
end
it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 2** as incomplete')
+ note1 = find_note('marked the checklist item **Task 2** as incomplete')
expect(note1).not_to be_nil
@@ -92,7 +92,7 @@ RSpec.shared_examples 'updating a single task' do
end
it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 2** as incomplete')
+ note1 = find_note('marked the checklist item **Task 2** as incomplete')
expect(note1).not_to be_nil
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
index 6bc4f171d9c..704a4bbe0b8 100644
--- a/spec/support/shared_examples/services/packages_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -81,6 +81,26 @@ RSpec.shared_examples 'returns packages' do |container_type, user_type|
end
end
+RSpec.shared_examples 'returns package' do |container_type, user_type|
+ context "for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it 'returns success response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+
+ it 'returns a valid response schema' do
+ subject
+
+ expect(response).to match_response_schema(single_package_schema)
+ end
+ end
+end
+
RSpec.shared_examples 'returns packages with subgroups' do |container_type, user_type|
context "with subgroups for #{user_type}" do
before do
diff --git a/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb b/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb
new file mode 100644
index 00000000000..0687be6f429
--- /dev/null
+++ b/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+shared_examples 'issue_edit snowplow tracking' do
+ let(:category) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_CATEGORY }
+ let(:action) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_ACTION }
+ let(:label) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_LABEL }
+ let(:namespace) { project.namespace }
+ let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
+
+ it_behaves_like 'Snowplow event tracking'
+end
diff --git a/spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb b/spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb
new file mode 100644
index 00000000000..53c42ec0e00
--- /dev/null
+++ b/spec/support/shared_examples/services/timelogs/create_service_shared_examples.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'issuable supports timelog creation service' do
+ shared_examples 'success_response' do
+ it 'sucessfully saves the timelog' do
+ is_expected.to be_success
+
+ timelog = subject.payload[:timelog]
+
+ expect(timelog).to be_persisted
+ expect(timelog.time_spent).to eq(time_spent)
+ expect(timelog.spent_at).to eq('Fri, 08 Jul 2022 00:00:00.000000000 UTC +00:00')
+ expect(timelog.summary).to eq(summary)
+ expect(timelog.issuable).to eq(issuable)
+ end
+ end
+
+ context 'when the user does not have permission' do
+ let(:user) { create(:user) }
+
+ it 'returns an error' do
+ is_expected.to be_error
+
+ expect(subject.message).to eq(
+ "#{issuable.base_class_name} doesn't exist or you don't have permission to add timelog to it.")
+ expect(subject.http_status).to eq(404)
+ end
+ end
+
+ context 'when the user has permissions' do
+ let(:user) { author }
+
+ before do
+ users_container.add_reporter(user)
+ end
+
+ context 'when the timelog save fails' do
+ before do
+ allow_next_instance_of(Timelog) do |timelog|
+ allow(timelog).to receive(:save).and_return(false)
+ end
+ end
+
+ it 'returns an error' do
+ is_expected.to be_error
+ expect(subject.message).to eq('Failed to save timelog')
+ end
+ end
+
+ context 'when the creation completes sucessfully' do
+ it_behaves_like 'success_response'
+ end
+ end
+end
+
+RSpec.shared_examples 'issuable does not support timelog creation service' do
+ shared_examples 'error_response' do
+ it 'returns an error' do
+ is_expected.to be_error
+
+ issuable_type = if issuable.nil?
+ 'Issuable'
+ else
+ issuable.base_class_name
+ end
+
+ expect(subject.message).to eq(
+ "#{issuable_type} doesn't exist or you don't have permission to add timelog to it."
+ )
+ expect(subject.http_status).to eq(404)
+ end
+ end
+
+ context 'when the user does not have permission' do
+ let(:user) { create(:user) }
+
+ it_behaves_like 'error_response'
+ end
+
+ context 'when the user has permissions' do
+ let(:user) { author }
+
+ before do
+ users_container.add_reporter(user)
+ end
+
+ it_behaves_like 'error_response'
+ end
+end
diff --git a/spec/support/shared_examples/services/work_items/create_task_shared_examples.rb b/spec/support/shared_examples/services/work_items/create_task_shared_examples.rb
new file mode 100644
index 00000000000..7771e7f0e21
--- /dev/null
+++ b/spec/support/shared_examples/services/work_items/create_task_shared_examples.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'title with extra spaces' do
+ context 'when title has extra spaces' do
+ before do
+ params[:title] = " Awesome work item "
+ end
+
+ it 'removes extra leading and trailing whitespaces from title' do
+ subject
+
+ created_work_item = WorkItem.last
+ expect(created_work_item.title).to eq('Awesome work item')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
index 1da21633504..3ba5f080a01 100644
--- a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
+++ b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_database|
+RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_database, table_name|
include ExclusiveLeaseHelpers
describe 'defining the job attributes' do
@@ -136,8 +136,10 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
let(:job_interval) { 5.minutes }
let(:lease_timeout) { 15.minutes }
let(:lease_key) { described_class.name.demodulize.underscore }
- let(:migration) { build(:batched_background_migration, :active, interval: job_interval) }
let(:interval_variance) { described_class::INTERVAL_VARIANCE }
+ let(:migration) do
+ build(:batched_background_migration, :active, interval: job_interval, table_name: table_name)
+ end
before do
allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration)
@@ -233,7 +235,9 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
let(:migration_class) do
Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
- def perform(matching_status)
+ job_arguments :matching_status
+
+ def perform
each_sub_batch(
operation_name: :update_all,
batching_scope: -> (relation) { relation.where(status: matching_status) }
@@ -249,7 +253,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
create(
:batched_background_migration,
:active,
- table_name: table_name,
+ table_name: new_table_name,
column_name: :id,
max_value: migration_records,
batch_size: batch_size,
@@ -261,14 +265,14 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
end
let(:base_model) { Gitlab::Database.database_base_models[tracking_database] }
- let(:table_name) { 'example_data' }
+ let(:new_table_name) { '_test_example_data' }
let(:batch_size) { 5 }
let(:sub_batch_size) { 2 }
let(:number_of_batches) { 10 }
let(:migration_records) { batch_size * number_of_batches }
let(:connection) { Gitlab::Database.database_base_models[tracking_database].connection }
- let(:example_data) { define_batchable_model(table_name, connection: connection) }
+ let(:example_data) { define_batchable_model(new_table_name, connection: connection) }
around do |example|
Gitlab::Database::SharedModel.using_connection(connection) do
@@ -283,16 +287,16 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
# - one record beyond the migration's range
# - one record that doesn't match the migration job's batch condition
connection.execute(<<~SQL)
- CREATE TABLE #{table_name} (
+ CREATE TABLE #{new_table_name} (
id integer primary key,
some_column integer,
status smallint);
- INSERT INTO #{table_name} (id, some_column, status)
+ INSERT INTO #{new_table_name} (id, some_column, status)
SELECT generate_series, generate_series, 1
FROM generate_series(1, #{migration_records + 1});
- UPDATE #{table_name}
+ UPDATE #{new_table_name}
SET status = 0
WHERE some_column = #{migration_records - 5};
SQL
@@ -362,6 +366,15 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
expect { migration_run }.to change { migration.reload.on_hold? }.from(false).to(true)
end
+
+ it 'puts migration on hold when the pending WAL count is above the limit' do
+ sql = Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::WriteAheadLog::PENDING_WAL_COUNT_SQL
+ limit = Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::WriteAheadLog::LIMIT
+
+ expect(connection).to receive(:execute).with(sql).and_return([{ 'pending_wal_count' => limit + 1 }])
+
+ expect { migration_run }.to change { migration.reload.on_hold? }.from(false).to(true)
+ end
end
end
end