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