diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-18 11:17:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-18 11:17:02 +0300 |
commit | b39512ed755239198a9c294b6a45e65c05900235 (patch) | |
tree | d234a3efade1de67c46b9e5a38ce813627726aa7 /spec/support/shared_examples | |
parent | d31474cf3b17ece37939d20082b07f6657cc79a9 (diff) |
Add latest changes from gitlab-org/gitlab@15-3-stable-eev15.3.0-rc42
Diffstat (limited to 'spec/support/shared_examples')
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 |