diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-18 11:18:50 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-18 11:18:50 +0000 |
commit | 8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch) | |
tree | a77e7fe7a93de11213032ed4ab1f33a3db51b738 /spec/support/shared_examples | |
parent | 00b35af3db1abfe813a778f643dad221aad51fca (diff) | |
download | gitlab-ce-8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781.tar.gz |
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'spec/support/shared_examples')
40 files changed, 1121 insertions, 220 deletions
diff --git a/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb b/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb new file mode 100644 index 00000000000..88ad1f6cde2 --- /dev/null +++ b/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'import controller with new_import_ui feature flag' do + include ImportSpecHelper + + context 'with new_import_ui feature flag enabled' do + let(:group) { create(:group) } + + before do + stub_feature_flags(new_import_ui: true) + group.add_owner(user) + end + + it "returns variables for json request" do + project = create(:project, import_type: provider_name, creator_id: user.id) + stub_client(client_repos_field => [repo]) + + get :status, format: :json + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id) + expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id) + expect(json_response.dig("namespaces", 0, "id")).to eq(group.id) + end + + it "does not show already added project" do + project = create(:project, import_type: provider_name, namespace: user.namespace, import_status: :finished, import_source: import_source) + stub_client(client_repos_field => [repo]) + + get :status, format: :json + + expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id) + expect(json_response.dig("provider_repos")).to eq([]) + end + end +end diff --git a/spec/support/shared_examples/controllers/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/controllers/issuables_list_metadata_shared_examples.rb index 2dbaea57c44..62a1a07b6c1 100644 --- a/spec/support/shared_examples/controllers/issuables_list_metadata_shared_examples.rb +++ b/spec/support/shared_examples/controllers/issuables_list_metadata_shared_examples.rb @@ -34,7 +34,7 @@ RSpec.shared_examples 'issuables list meta-data' do |issuable_type, action = nil aggregate_failures do expect(meta_data.keys).to match_array(issuables.map(&:id)) - expect(meta_data.values).to all(be_kind_of(Issuable::IssuableMeta)) + expect(meta_data.values).to all(be_kind_of(Gitlab::IssuableMetadata::IssuableMeta)) end end diff --git a/spec/support/shared_examples/controllers/milestone_tabs_shared_examples.rb b/spec/support/shared_examples/controllers/milestone_tabs_shared_examples.rb index d9656824452..925c45005f0 100644 --- a/spec/support/shared_examples/controllers/milestone_tabs_shared_examples.rb +++ b/spec/support/shared_examples/controllers/milestone_tabs_shared_examples.rb @@ -2,15 +2,7 @@ RSpec.shared_examples 'milestone tabs' do def go(path, extra_params = {}) - params = - case milestone - when DashboardMilestone - { id: milestone.safe_title, title: milestone.title } - when GroupMilestone - { group_id: group.to_param, id: milestone.safe_title, title: milestone.title } - else - { namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid } - end + params = { namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid } get path, params: params.merge(extra_params) end diff --git a/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb b/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb new file mode 100644 index 00000000000..7885eb6c1f8 --- /dev/null +++ b/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'namespace storage limit alert' do + let(:alert_level) { :info } + + before do + allow_next_instance_of(Namespaces::CheckStorageSizeService, namespace, user) do |check_storage_size_service| + expect(check_storage_size_service).to receive(:execute).and_return( + ServiceResponse.success( + payload: { + alert_level: alert_level, + usage_message: "Usage", + explanation_message: "Explanation", + root_namespace: namespace + } + ) + ) + end + + allow(controller).to receive(:current_user).and_return(user) + end + + render_views + + it 'does render' do + subject + + expect(response.body).to match(/Explanation/) + expect(response.body).to have_css('.js-namespace-storage-alert-dismiss') + end + + context 'when alert_level is error' do + let(:alert_level) { :error } + + it 'does not render a dismiss button' do + subject + + expect(response.body).not_to have_css('.js-namespace-storage-alert-dismiss') + end + end + + context 'when cookie is set' do + before do + cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true' + end + + it 'does not render alert' do + subject + + expect(response.body).not_to match(/Explanation/) + end + end +end diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb new file mode 100644 index 00000000000..c128bbe5e02 --- /dev/null +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -0,0 +1,302 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'wiki controller actions' do + let(:container) { raise NotImplementedError } + let(:routing_params) { raise NotImplementedError } + + let_it_be(:user) { create(:user) } + let(:wiki) { Wiki.for_container(container, user) } + let(:wiki_title) { 'page title test' } + + before do + create(:wiki_page, wiki: wiki, title: wiki_title, content: 'hello world') + + sign_in(user) + end + + describe 'GET #new' do + subject { get :new, params: routing_params } + + it 'redirects to #show and appends a `random_title` param' do + subject + + expect(response).to be_redirect + expect(response.redirect_url).to match(%r{ + #{Regexp.quote(wiki.wiki_base_path)} # wiki base path + /[-\h]{36} # page slug + \?random_title=true\Z # random_title param + }x) + end + + context 'when the wiki repository cannot be created' do + before do + expect(Wiki).to receive(:for_container).and_return(wiki) + expect(wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError } + end + + it 'redirects to the wiki container and displays an error message' do + subject + + expect(response).to redirect_to(container) + expect(flash[:notice]).to eq('Could not create Wiki Repository at this time. Please try again later.') + end + end + end + + describe 'GET #pages' do + before do + get :pages, params: routing_params.merge(id: wiki_title) + end + + it 'assigns the page collections' do + expect(assigns(:wiki_pages)).to contain_exactly(an_instance_of(WikiPage)) + expect(assigns(:wiki_entries)).to contain_exactly(an_instance_of(WikiPage)) + end + + it 'does not load the page content' do + expect(assigns(:page)).to be_nil + end + + it 'does not load the sidebar' do + expect(assigns(:sidebar_wiki_entries)).to be_nil + expect(assigns(:sidebar_limited)).to be_nil + end + end + + describe 'GET #history' do + before do + allow(controller) + .to receive(:can?) + .with(any_args) + .and_call_original + + # The :create_wiki permission is irrelevant to reading history. + expect(controller) + .not_to receive(:can?) + .with(anything, :create_wiki, any_args) + + allow(controller) + .to receive(:can?) + .with(anything, :read_wiki, any_args) + .and_return(allow_read_wiki) + end + + shared_examples 'fetching history' do |expected_status| + before do + get :history, params: routing_params.merge(id: wiki_title) + end + + it "returns status #{expected_status}" do + expect(response).to have_gitlab_http_status(expected_status) + end + end + + it_behaves_like 'fetching history', :ok do + let(:allow_read_wiki) { true } + + it 'assigns @page_versions' do + expect(assigns(:page_versions)).to be_present + end + end + + it_behaves_like 'fetching history', :not_found do + let(:allow_read_wiki) { false } + end + end + + describe 'GET #show' do + render_views + + let(:random_title) { nil } + + subject { get :show, params: routing_params.merge(id: id, random_title: random_title) } + + context 'when page exists' do + let(:id) { wiki_title } + + it 'renders the page' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(assigns(:page).title).to eq(wiki_title) + expect(assigns(:sidebar_wiki_entries)).to contain_exactly(an_instance_of(WikiPage)) + expect(assigns(:sidebar_limited)).to be(false) + end + + context 'when page content encoding is invalid' do + it 'sets flash error' do + allow(controller).to receive(:valid_encoding?).and_return(false) + + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(flash[:notice]).to eq(_('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.')) + end + end + end + + context 'when the page does not exist' do + let(:id) { 'does not exist' } + + before do + subject + end + + it 'builds a new wiki page with the id as the title' do + expect(assigns(:page).title).to eq(id) + end + + context 'when a random_title param is present' do + let(:random_title) { true } + + it 'builds a new wiki page with no title' do + expect(assigns(:page).title).to be_empty + end + end + end + + context 'when page is a file' do + include WikiHelpers + + let(:id) { upload_file_to_wiki(container, user, file_name) } + + context 'when file is an image' do + let(:file_name) { 'dk.png' } + + it 'delivers the image' do + subject + + expect(response.headers['Content-Disposition']).to match(/^inline/) + expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" + end + + context 'when file is a svg' do + let(:file_name) { 'unsanitized.svg' } + + it 'delivers the image' do + subject + + expect(response.headers['Content-Disposition']).to match(/^inline/) + expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" + end + end + + it_behaves_like 'project cache control headers' do + let(:project) { container } + end + end + + context 'when file is a pdf' do + let(:file_name) { 'git-cheat-sheet.pdf' } + + it 'sets the content type to sets the content response headers' do + subject + + expect(response.headers['Content-Disposition']).to match(/^inline/) + expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" + end + + it_behaves_like 'project cache control headers' do + let(:project) { container } + end + end + end + end + + describe 'POST #preview_markdown' do + it 'renders json in a correct format' do + post :preview_markdown, params: routing_params.merge(id: 'page/path', text: '*Markdown* text') + + expect(json_response.keys).to match_array(%w(body references)) + end + end + + describe 'GET #edit' do + subject { get(:edit, params: routing_params.merge(id: wiki_title)) } + + context 'when page content encoding is invalid' do + it 'redirects to show' do + allow(controller).to receive(:valid_encoding?).and_return(false) + + subject + + expect(response).to redirect_to_wiki(wiki, wiki.list_pages.first) + end + end + + context 'when the page has nil content' do + let(:page) { create(:wiki_page) } + + it 'redirects to show' do + allow(page).to receive(:content).and_return(nil) + allow(controller).to receive(:page).and_return(page) + + subject + + expect(response).to redirect_to_wiki(wiki, page) + end + end + + context 'when page content encoding is valid' do + render_views + + it 'shows the edit page' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to include(s_('Wiki|Edit Page')) + end + end + end + + describe 'PATCH #update' do + let(:new_title) { 'New title' } + let(:new_content) { 'New content' } + + subject do + patch(:update, + params: routing_params.merge( + id: wiki_title, + wiki: { title: new_title, content: new_content } + )) + end + + context 'when page content encoding is invalid' do + it 'redirects to show' do + allow(controller).to receive(:valid_encoding?).and_return(false) + + subject + expect(response).to redirect_to_wiki(wiki, wiki.list_pages.first) + end + end + + context 'when page content encoding is valid' do + render_views + + it 'updates the page' do + subject + + wiki_page = wiki.list_pages(load_content: true).first + + expect(wiki_page.title).to eq new_title + expect(wiki_page.content).to eq new_content + end + end + + context 'when user does not have edit permissions' do + before do + sign_out(:user) + end + + it 'renders the empty state' do + subject + + expect(response).to render_template('shared/wikis/empty') + end + end + end + + def redirect_to_wiki(wiki, page) + redirect_to(controller.wiki_page_path(wiki, page)) + end +end diff --git a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb index fb3b17d05ee..e0a032b1a43 100644 --- a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb +++ b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb @@ -6,13 +6,14 @@ RSpec.shared_examples 'comment on merge request file' do page.within('.js-discussion-note-form') do fill_in(:note_note, with: 'Line is wrong') - click_button('Comment') + find('.js-comment-button').click end wait_for_requests page.within('.notes_holder') do expect(page).to have_content('Line is wrong') + expect(page).not_to have_content('Comment on lines') end visit(merge_request_path(merge_request)) 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 81433d124c9..6007798c290 100644 --- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb +++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb @@ -15,7 +15,7 @@ RSpec.shared_examples 'thread comments' do |resource_name| find("#{form_selector} .note-textarea").send_keys(comment) - click_button 'Comment' + find('.js-comment-button').click expect(page).to have_content(comment) @@ -30,6 +30,8 @@ RSpec.shared_examples 'thread comments' do |resource_name| click_button 'Comment & close issue' + wait_for_all_requests + expect(page).to have_content(comment) expect(page).to have_content "@#{user.username} closed" @@ -144,7 +146,7 @@ RSpec.shared_examples 'thread comments' do |resource_name| find("#{comments_selector} .js-vue-discussion-reply").click find("#{comments_selector} .note-textarea").send_keys(text) - click_button "Comment" + find("#{comments_selector} .js-comment-button").click wait_for_requests end diff --git a/spec/support/shared_examples/graphql/container_expiration_policy_shared_examples.rb b/spec/support/shared_examples/graphql/container_expiration_policy_shared_examples.rb new file mode 100644 index 00000000000..9914de7c847 --- /dev/null +++ b/spec/support/shared_examples/graphql/container_expiration_policy_shared_examples.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'exposing container expiration policy option' do |model_option| + it 'exposes all options' do + expect(described_class.values.keys).to contain_exactly(*expected_values) + end + + it 'uses all possible options from model' do + all_options = ContainerExpirationPolicy.public_send("#{model_option}_options").keys + expect(described_class::OPTIONS_MAPPING.keys).to contain_exactly(*all_options) + end +end diff --git a/spec/support/shared_examples/graphql/label_fields.rb b/spec/support/shared_examples/graphql/label_fields.rb new file mode 100644 index 00000000000..b1bfb395bc6 --- /dev/null +++ b/spec/support/shared_examples/graphql/label_fields.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a GraphQL type with labels' do + it 'has label fields' do + expected_fields = %w[label labels] + + expect(described_class).to include_graphql_fields(*expected_fields) + end + + describe 'label field' do + subject { described_class.fields['label'] } + + it { is_expected.to have_graphql_type(Types::LabelType) } + it { is_expected.to have_graphql_arguments(:title) } + end + + describe 'labels field' do + subject { described_class.fields['labels'] } + + it { is_expected.to have_graphql_type(Types::LabelType.connection_type) } + it { is_expected.to have_graphql_arguments(:search_term) } + end +end + +RSpec.shared_examples 'querying a GraphQL type with labels' do + let_it_be(:current_user) { create(:user) } + + let_it_be(:label_a) { create(label_factory, :described, **label_attrs) } + let_it_be(:label_b) { create(label_factory, :described, **label_attrs) } + let_it_be(:label_c) { create(label_factory, :described, :scoped, prefix: 'matching', **label_attrs) } + let_it_be(:label_d) { create(label_factory, :described, :scoped, prefix: 'matching', **label_attrs) } + + let(:label_title) { label_b.title } + + let(:label_params) { { title: label_title } } + let(:labels_params) { nil } + + let(:label_response) { graphql_data.dig(*path_prefix, 'label') } + let(:labels_response) { graphql_data.dig(*path_prefix, 'labels', 'nodes') } + + let(:query) do + make_query( + [ + query_graphql_field(:label, label_params, all_graphql_fields_for(Label)), + query_graphql_field(:labels, labels_params, [ + query_graphql_field(:nodes, nil, all_graphql_fields_for(Label)) + ]) + ] + ) + end + + context 'running a query' do + before do + run_query(query) + end + + context 'minimum required arguments' do + it 'returns the label information' do + expect(label_response).to include( + 'title' => label_title, + 'description' => label_b.description + ) + end + + it 'returns the labels information' do + expect(labels_response.pluck('title')).to contain_exactly( + label_a.title, + label_b.title, + label_c.title, + label_d.title + ) + end + end + + context 'with a search param' do + let(:labels_params) { { search_term: 'matching' } } + + it 'finds the matching labels' do + expect(labels_response.pluck('title')).to contain_exactly( + label_c.title, + label_d.title + ) + end + end + + context 'the label does not exist' do + let(:label_title) { 'not-a-label' } + + it 'returns nil' do + expect(label_response).to be_nil + end + end + end + + describe 'performance' do + def query_for(*labels) + selections = labels.map do |label| + %Q[#{label.title.gsub(/:+/, '_')}: label(title: "#{label.title}") { description }] + end + + make_query(selections) + end + + before do + run_query(query_for(label_a)) + end + + it 'batches queries for labels by title' do + pending('See: https://gitlab.com/gitlab-org/gitlab/-/issues/217767') + + multi_selection = query_for(label_b, label_c) + single_selection = query_for(label_d) + + expect { run_query(multi_selection) } + .to issue_same_number_of_queries_as { run_query(single_selection) } + end + end + + # Run a known good query with the current user + def run_query(query) + post_graphql(query, current_user: current_user) + expect(graphql_errors).not_to be_present + end +end diff --git a/spec/support/shared_examples/graphql/members_shared_examples.rb b/spec/support/shared_examples/graphql/members_shared_examples.rb new file mode 100644 index 00000000000..a58e716efd2 --- /dev/null +++ b/spec/support/shared_examples/graphql/members_shared_examples.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a working membership object query' do |model_option| + let_it_be(:member_source) { member.source } + let_it_be(:member_source_type) { member_source.class.to_s.downcase } + + it 'contains edge to expected project' do + expect( + graphql_data.dig('user', "#{member_source_type}Memberships", 'nodes', 0, member_source_type, 'id') + ).to eq(member.send(member_source_type).to_global_id.to_s) + end + + it 'contains correct access level' do + expect( + graphql_data.dig('user', "#{member_source_type}Memberships", 'nodes', 0, 'accessLevel', 'integerValue') + ).to eq(30) + + expect( + graphql_data.dig('user', "#{member_source_type}Memberships", 'nodes', 0, 'accessLevel', 'stringValue') + ).to eq('DEVELOPER') + end +end diff --git a/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb b/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb index b56181371c3..58cd3d21f66 100644 --- a/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb +++ b/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb @@ -22,26 +22,16 @@ RSpec.shared_examples 'resolving an issuable in GraphQL' do |type| .with(full_path: parent.full_path) .and_return(resolved_parent) - expect(resolver_class).to receive(:new) + expect(resolver_class.single).to receive(:new) .with(object: resolved_parent, context: context, field: nil) .and_call_original subject end - it 'uses correct Resolver to resolve issuable parent' do - resolver_class = type == :epic ? 'Resolvers::GroupResolver' : 'Resolvers::ProjectResolver' - - expect(resolver_class.constantize).to receive(:new) - .with(object: nil, context: context, field: nil) - .and_call_original - - subject - end - it 'returns nil if issuable is not found' do result = mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: "100") - result = type == :merge_request ? result.sync : result + result = result.respond_to?(:sync) ? result.sync : result expect(result).to be_nil 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 fb7e24eecf2..2ef71d275a2 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 @@ -23,7 +23,7 @@ # graphql_query_for( # 'project', # { 'fullPath' => sort_project.full_path }, -# "issues(#{params}) { #{page_info} edges { node { iid weight } } }" +# query_graphql_field('issues', params, "#{page_info} edges { node { id } }") # ) # end # @@ -47,11 +47,13 @@ RSpec.shared_examples 'sorted paginated query' do end describe do - let(:params) { "sort: #{sort_param}" } - let(:start_cursor) { graphql_data_at(*data_path, :pageInfo, :startCursor) } - let(:end_cursor) { graphql_data_at(*data_path, :pageInfo, :endCursor) } - let(:sorted_edges) { graphql_data_at(*data_path, :edges) } - let(:page_info) { "pageInfo { startCursor endCursor }" } + let(:sort_argument) { "sort: #{sort_param}" if sort_param.present? } + let(:first_argument) { "first: #{first_param}" if first_param.present? } + let(:params) { sort_argument } + let(:start_cursor) { graphql_data_at(*data_path, :pageInfo, :startCursor) } + let(:end_cursor) { graphql_data_at(*data_path, :pageInfo, :endCursor) } + let(:sorted_edges) { graphql_data_at(*data_path, :edges) } + let(:page_info) { "pageInfo { startCursor endCursor }" } def pagination_query(params, page_info) raise('pagination_query(params, page_info) must be defined in the test, see example in comment') unless defined?(super) @@ -75,12 +77,12 @@ RSpec.shared_examples 'sorted paginated query' do end context 'when paginating' do - let(:params) { "sort: #{sort_param}, first: #{first_param}" } + let(:params) { [sort_argument, first_argument].compact.join(',') } it 'paginates correctly' do expect(pagination_results_data(sorted_edges)).to eq expected_results.first(first_param) - cursored_query = pagination_query("sort: #{sort_param}, after: \"#{end_cursor}\"", page_info) + cursored_query = pagination_query([sort_argument, "after: \"#{end_cursor}\""].compact.join(','), page_info) post_graphql(cursored_query, current_user: current_user) response_data = graphql_dig_at(Gitlab::Json.parse(response.body), :data, *data_path, :edges) diff --git a/spec/support/shared_examples/integrations/test_examples.rb b/spec/support/shared_examples/integrations/test_examples.rb new file mode 100644 index 00000000000..eb2e83ce5d1 --- /dev/null +++ b/spec/support/shared_examples/integrations/test_examples.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'tests for integration with pipeline data' do + it 'tests the integration with pipeline data' do + create(:ci_empty_pipeline, project: project) + allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb new file mode 100644 index 00000000000..97f4341340d --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'parsing gl_repository identifier' do + subject { described_class.new(identifier) } + + it 'returns correct information' do + aggregate_failures do + expect(subject.repo_type).to eq(expected_type) + expect(subject.fetch_container!).to eq(expected_container) + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb new file mode 100644 index 00000000000..06ea540706a --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +shared_examples 'stuck import job detection' do + context 'when the job has completed' do + context 'when the import status was already updated' do + before do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids) do + import_state.start + import_state.finish + + [import_state.jid] + end + end + + it 'does not mark the import as failed' do + worker.perform + + expect(import_state.reload.status).to eq('finished') + end + end + + context 'when the import status was not updated' do + before do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([import_state.jid]) + end + + it 'marks the import as failed' do + worker.perform + + expect(import_state.reload.status).to eq('failed') + end + end + end + + context 'when the job is still in Sidekiq' do + before do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([]) + end + + it 'does not mark the import as failed' do + expect { worker.perform }.not_to change { import_state.reload.status } + end + end +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 c9300aff3e6..326800e6dc2 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,7 +32,21 @@ RSpec.shared_examples "position formatter" do subject { formatter.to_h } - it { is_expected.to eq(formatter_hash) } + 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 end describe '#==' do diff --git a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb index 69ae9339f10..4aeae788114 100644 --- a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb @@ -7,26 +7,6 @@ RSpec.shared_examples 'a repo type' do it { is_expected.to eq(expected_identifier) } end - describe '#fetch_id' do - it 'finds an id match in the identifier' do - expect(described_class.fetch_id(expected_identifier)).to eq(expected_id) - end - - it 'does not break on other identifiers' do - expect(described_class.fetch_id('wiki-noid')).to eq(nil) - end - end - - describe '#fetch_container!' do - it 'returns the container' do - expect(described_class.fetch_container!(expected_identifier)).to eq expected_container - end - - it 'raises an exception if the identifier is invalid' do - expect { described_class.fetch_container!('project-noid') }.to raise_error ArgumentError - end - end - describe '#path_suffix' do subject { described_class.path_suffix } diff --git a/spec/support/shared_examples/models/application_setting_shared_examples.rb b/spec/support/shared_examples/models/application_setting_shared_examples.rb index aed85a6630a..01513161d24 100644 --- a/spec/support/shared_examples/models/application_setting_shared_examples.rb +++ b/spec/support/shared_examples/models/application_setting_shared_examples.rb @@ -288,11 +288,37 @@ RSpec.shared_examples 'application settings examples' do end describe '#pick_repository_storage' do - it 'uses Array#sample to pick a random storage' do - array = double('array', sample: 'random') - expect(setting).to receive(:repository_storages).and_return(array) + before do + allow(setting).to receive(:repository_storages_weighted).and_return({ 'default' => 20, 'backup' => 80 }) + end + + it 'chooses repository based on weight' do + picked_storages = { 'default' => 0.0, 'backup' => 0.0 } + 10_000.times { picked_storages[setting.pick_repository_storage] += 1 } + + expect(((picked_storages['default'] / 10_000) * 100).round.to_i).to be_between(19, 21) + expect(((picked_storages['backup'] / 10_000) * 100).round.to_i).to be_between(79, 81) + end + end + + describe '#normalized_repository_storage_weights' do + using RSpec::Parameterized::TableSyntax - expect(setting.pick_repository_storage).to eq('random') + where(:storages, :normalized) do + { 'default' => 0, 'backup' => 100 } | { 'default' => 0.0, 'backup' => 1.0 } + { 'default' => 100, 'backup' => 100 } | { 'default' => 0.5, 'backup' => 0.5 } + { 'default' => 20, 'backup' => 80 } | { 'default' => 0.2, 'backup' => 0.8 } + { 'default' => 0, 'backup' => 0 } | { 'default' => 0.0, 'backup' => 0.0 } + end + + with_them do + before do + allow(setting).to receive(:repository_storages_weighted).and_return(storages) + end + + it 'normalizes storage weights' do + expect(setting.normalized_repository_storage_weights).to eq(normalized) + end end end diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb index fa6b0c3afdd..239588d3b2f 100644 --- a/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb @@ -54,7 +54,7 @@ RSpec.shared_examples 'cluster application helm specs' do |application_name| context 'managed_apps_local_tiller feature flag is enabled' do before do - stub_feature_flags(managed_apps_local_tiller: true) + stub_feature_flags(managed_apps_local_tiller: application.cluster.clusterable) end it 'does not include cert files' do diff --git a/spec/support/shared_examples/models/cluster_application_initial_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_initial_status_shared_examples.rb index 0b21e9a3aa7..7f0c60d4204 100644 --- a/spec/support/shared_examples/models/cluster_application_initial_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_initial_status_shared_examples.rb @@ -18,7 +18,7 @@ RSpec.shared_examples 'cluster application initial status specs' do context 'local tiller feature flag is enabled' do before do - stub_feature_flags(managed_apps_local_tiller: true) + stub_feature_flags(managed_apps_local_tiller: cluster.clusterable) end it 'sets a default status' do diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb index c2fd04d648b..0efa5e56199 100644 --- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -66,7 +66,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| context 'managed_apps_local_tiller feature flag enabled' do before do - stub_feature_flags(managed_apps_local_tiller: true) + stub_feature_flags(managed_apps_local_tiller: subject.cluster.clusterable) end it 'does not update the helm version' do @@ -197,12 +197,73 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| describe '#make_externally_installed' do subject { create(application_name, :installing) } + let(:old_helm) { create(:clusters_applications_helm, version: '1.2.3') } + it 'is installed' do subject.make_externally_installed expect(subject).to be_installed end + context 'local tiller flag enabled' do + before do + stub_feature_flags(managed_apps_local_tiller: true) + end + + context 'helm record does not exist' do + subject { build(application_name, :installing, :no_helm_installed) } + + it 'does not create a helm record' do + subject.make_externally_installed! + + subject.cluster.reload + expect(subject.cluster.application_helm).to be_nil + end + end + + context 'helm record exists' do + subject { build(application_name, :installing, cluster: old_helm.cluster) } + + it 'does not update helm version' do + subject.make_externally_installed! + + subject.cluster.application_helm.reload + + expect(subject.cluster.application_helm.version).to eq('1.2.3') + end + end + end + + context 'local tiller flag disabled' do + before do + stub_feature_flags(managed_apps_local_tiller: false) + end + + context 'helm record does not exist' do + subject { build(application_name, :installing, :no_helm_installed) } + + it 'creates a helm record' do + subject.make_externally_installed! + + subject.cluster.reload + expect(subject.cluster.application_helm).to be_present + expect(subject.cluster.application_helm).to be_persisted + end + end + + context 'helm record exists' do + subject { build(application_name, :installing, cluster: old_helm.cluster) } + + it 'does not update helm version' do + subject.make_externally_installed! + + subject.cluster.application_helm.reload + + expect(subject.cluster.application_helm.version).to eq('1.2.3') + end + end + end + context 'application is updated' do subject { create(application_name, :updated) } diff --git a/spec/support/shared_examples/models/concerns/blob_replicator_strategy_shared_examples.rb b/spec/support/shared_examples/models/concerns/blob_replicator_strategy_shared_examples.rb deleted file mode 100644 index 76339837351..00000000000 --- a/spec/support/shared_examples/models/concerns/blob_replicator_strategy_shared_examples.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -# Include these shared examples in specs of Replicators that include -# BlobReplicatorStrategy. -# -# A let variable called model_record should be defined in the spec. It should be -# a valid, unpersisted instance of the model class. -# -RSpec.shared_examples 'a blob replicator' do - include EE::GeoHelpers - - let_it_be(:primary) { create(:geo_node, :primary) } - let_it_be(:secondary) { create(:geo_node) } - - subject(:replicator) { model_record.replicator } - - before do - stub_current_geo_node(primary) - end - - describe '#handle_after_create_commit' do - it 'creates a Geo::Event' do - expect do - replicator.handle_after_create_commit - end.to change { ::Geo::Event.count }.by(1) - - expect(::Geo::Event.last.attributes).to include( - "replicable_name" => replicator.replicable_name, "event_name" => "created", "payload" => { "model_record_id" => replicator.model_record.id }) - end - - it 'schedules the checksum calculation if needed' do - expect(Geo::BlobVerificationPrimaryWorker).to receive(:perform_async) - expect(replicator).to receive(:needs_checksum?).and_return(true) - - replicator.handle_after_create_commit - end - - it 'does not schedule the checksum calculation if feature flag is disabled' do - stub_feature_flags(geo_self_service_framework: false) - - expect(Geo::BlobVerificationPrimaryWorker).not_to receive(:perform_async) - allow(replicator).to receive(:needs_checksum?).and_return(true) - - replicator.handle_after_create_commit - end - end - - describe '#calculate_checksum!' do - it 'calculates the checksum' do - model_record.save! - - replicator.calculate_checksum! - - expect(model_record.reload.verification_checksum).not_to be_nil - expect(model_record.reload.verified_at).not_to be_nil - end - - it 'saves the error message and increments retry counter' do - model_record.save! - - allow(model_record).to receive(:calculate_checksum!) do - raise StandardError.new('Failure to calculate checksum') - end - - replicator.calculate_checksum! - - expect(model_record.reload.verification_failure).to eq 'Failure to calculate checksum' - expect(model_record.verification_retry_count).to be 1 - end - end - - describe '#consume_created_event' do - it 'invokes Geo::BlobDownloadService' do - service = double(:service) - - expect(service).to receive(:execute) - expect(::Geo::BlobDownloadService).to receive(:new).with(replicator: replicator).and_return(service) - - replicator.consume_event_created - end - end - - describe '#carrierwave_uploader' do - it 'is implemented' do - expect do - replicator.carrierwave_uploader - end.not_to raise_error - end - end - - describe '#model' do - let(:invoke_model) { replicator.class.model } - - it 'is implemented' do - expect do - invoke_model - end.not_to raise_error - end - - it 'is a Class' do - expect(invoke_model).to be_a(Class) - end - - # For convenience (and reliability), instead of asking developers to include shared examples on each model spec as well - context 'replicable model' do - it 'defines #replicator' do - expect(model_record).to respond_to(:replicator) - end - - it 'invokes replicator.handle_after_create_commit on create' do - expect(replicator).to receive(:handle_after_create_commit) - - model_record.save! - end - end - end -end diff --git a/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb b/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb index 30c8c7d0fe5..f37ef3533c3 100644 --- a/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb @@ -148,18 +148,18 @@ RSpec.shared_examples 'model with repository' do expect(subject).to eq('picked') end - it 'picks from the latest available storage', :request_store do + it 'picks from the available storages based on weight', :request_store do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') Gitlab::CurrentSettings.expire_current_application_settings Gitlab::CurrentSettings.current_application_settings settings = ApplicationSetting.last - settings.repository_storages = %w(picked) + settings.repository_storages_weighted = { 'picked' => 100, 'default' => 0 } settings.save! - expect(Gitlab::CurrentSettings.repository_storages).to eq(%w(default)) + expect(Gitlab::CurrentSettings.repository_storages_weighted).to eq({ 'default' => 100 }) expect(subject).to eq('picked') - expect(Gitlab::CurrentSettings.repository_storages).to eq(%w(picked)) + expect(Gitlab::CurrentSettings.repository_storages_weighted).to eq({ 'default' => 0, 'picked' => 100 }) end end end diff --git a/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb b/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb index 4bcea36fd42..d21823661f8 100644 --- a/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/limitable_shared_examples.rb @@ -26,7 +26,7 @@ RSpec.shared_examples 'includes Limitable concern' do subject.dup.save end - it 'cannot create new models exceding the plan limits' do + it 'cannot create new models exceeding the plan limits' do expect { subject.save }.not_to change { described_class.count } expect(subject.errors[:base]).to contain_exactly("Maximum number of #{subject.class.limit_name.humanize(capitalize: false)} (1) exceeded") end diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb index 84569e95e11..a881d5f036c 100644 --- a/spec/support/shared_examples/models/wiki_shared_examples.rb +++ b/spec/support/shared_examples/models/wiki_shared_examples.rb @@ -32,6 +32,13 @@ RSpec.shared_examples 'wiki model' do it 'returns the wiki base path' do expect(subject.wiki_base_path).to eq("#{wiki_container.web_url(only_path: true)}/-/wikis") end + + it 'includes the relative URL root' do + allow(Rails.application.routes).to receive(:default_url_options).and_return(script_name: '/root') + + expect(subject.wiki_base_path).to start_with('/root/') + expect(subject.wiki_base_path).not_to start_with('/root/root') + end end describe '#wiki' do diff --git a/spec/support/shared_examples/path_extraction_shared_examples.rb b/spec/support/shared_examples/path_extraction_shared_examples.rb new file mode 100644 index 00000000000..19c6f2404e5 --- /dev/null +++ b/spec/support/shared_examples/path_extraction_shared_examples.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'assigns ref vars' do + it 'assigns the repository var' do + assign_ref_vars + + expect(@repo).to eq container.repository + end + + context 'ref contains %20' do + let(:ref) { 'foo%20bar' } + + it 'is not converted to a space in @id' do + container.repository.add_branch(owner, 'foo%20bar', 'master') + + assign_ref_vars + + expect(@id).to start_with('foo%20bar/') + end + end + + context 'ref contains trailing space' do + let(:ref) { 'master ' } + + it 'strips surrounding space' do + assign_ref_vars + + expect(@ref).to eq('master') + end + end + + context 'ref contains leading space' do + let(:ref) { ' master ' } + + it 'strips surrounding space' do + assign_ref_vars + + expect(@ref).to eq('master') + end + end + + context 'path contains space' do + let(:params) { { path: 'with space', ref: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } } + + it 'is not converted to %20 in @path' do + assign_ref_vars + + expect(@path).to eq(params[:path]) + end + end + + context 'subclass overrides get_id' do + it 'uses ref returned by get_id' do + allow_next_instance_of(self.class) do |instance| + allow(instance).to receive(:get_id) { '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } + end + + assign_ref_vars + + expect(@id).to eq(get_id) + end + end +end + +RSpec.shared_examples 'extracts refs' do + describe '#extract_ref' do + it 'returns an empty pair when no repository_container is set' do + allow_any_instance_of(described_class).to receive(:repository_container).and_return(nil) + expect(extract_ref('master/CHANGELOG')).to eq(['', '']) + end + + context 'without a path' do + it 'extracts a valid branch' do + expect(extract_ref('master')).to eq(['master', '']) + end + + it 'extracts a valid tag' do + expect(extract_ref('v2.0.0')).to eq(['v2.0.0', '']) + end + + it 'extracts a valid commit ref without a path' do + expect(extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062')).to eq( + ['f4b14494ef6abf3d144c28e4af0c20143383e062', ''] + ) + end + + it 'falls back to a primitive split for an invalid ref' do + expect(extract_ref('stable')).to eq(['stable', '']) + end + + it 'extracts the longest matching ref' do + expect(extract_ref('release/app/v1.0.0/README.md')).to eq( + ['release/app/v1.0.0', 'README.md']) + end + end + + context 'with a path' do + it 'extracts a valid branch' do + expect(extract_ref('foo/bar/baz/CHANGELOG')).to eq( + ['foo/bar/baz', 'CHANGELOG']) + end + + it 'extracts a valid tag' do + expect(extract_ref('v2.0.0/CHANGELOG')).to eq(['v2.0.0', 'CHANGELOG']) + end + + it 'extracts a valid commit SHA' do + expect(extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG')).to eq( + %w(f4b14494ef6abf3d144c28e4af0c20143383e062 CHANGELOG) + ) + end + + it 'falls back to a primitive split for an invalid ref' do + expect(extract_ref('stable/CHANGELOG')).to eq(%w(stable CHANGELOG)) + end + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb index 37a504cd56a..37ee2548dfe 100644 --- a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb @@ -86,7 +86,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| page.within '.time-tracking-component-wrap' do find('.help-button').click - expect(find_link('Learn more')[:href]).to have_content('/help/workflow/time_tracking.md') + expect(find_link('Learn more')[:href]).to have_content('/help/user/project/time_tracking.md') end end end diff --git a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb index 0f277c11913..3e058838773 100644 --- a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb @@ -54,6 +54,29 @@ RSpec.shared_examples 'returns repositories for allowed users' do |user_type, sc expect(response).to match_response_schema('registry/repositories') end end + + context 'with tags_count param' do + let(:url) { "/#{scope}s/#{object.id}/registry/repositories?tags_count=true" } + + before do + stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest), with_manifest: true) + stub_container_registry_tags(repository: test_repository.path, tags: %w(rootA latest), with_manifest: true) + end + + it 'returns a list of repositories and their tags_count' do + subject + + expect(response.body).to include('tags_count') + expect(json_response[0]['tags_count']).to eq(2) + end + + it 'returns a matching schema' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + end + end end end diff --git a/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb index 3d25b9076ad..518c5b8dc28 100644 --- a/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb @@ -30,7 +30,9 @@ RSpec.shared_examples 'diff discussions API' do |parent_type, noteable_type, id_ it "creates a new diff note" do line_range = { "start_line_code" => Gitlab::Git.diff_line_code(diff_note.position.file_path, 1, 1), - "end_line_code" => Gitlab::Git.diff_line_code(diff_note.position.file_path, 2, 2) + "end_line_code" => Gitlab::Git.diff_line_code(diff_note.position.file_path, 2, 2), + "start_line_type" => diff_note.position.type, + "end_line_type" => diff_note.position.type } position = diff_note.position.to_h.merge({ line_range: line_range }) 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 feb3ba46353..f26af6cb766 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 @@ -45,44 +45,37 @@ RSpec.shared_examples 'group and project boards query' do end describe 'sorting and pagination' do + let(:data_path) { [board_parent_type, :boards] } + + def pagination_query(params, page_info) + graphql_query_for( + board_parent_type, + { 'fullPath' => board_parent.full_path }, + query_graphql_field('boards', params, "#{page_info} edges { node { id } }") + ) + end + + def pagination_results_data(data) + data.map { |board| board.dig('node', 'id') } + end + context 'when using default sorting' do let!(:board_B) { create(:board, resource_parent: board_parent, name: 'B') } let!(:board_C) { create(:board, resource_parent: board_parent, name: 'C') } let!(:board_a) { create(:board, resource_parent: board_parent, name: 'a') } let!(:board_A) { create(:board, resource_parent: board_parent, name: 'A') } - - before do - post_graphql(query, current_user: current_user) - end - - it_behaves_like 'a working graphql query' + let(:boards) { [board_a, board_A, board_B, board_C] } context 'when ascending' do - let(:boards) { [board_a, board_A, board_B, board_C] } - let(:expected_boards) do - if board_parent.multiple_issue_boards_available? - boards - else - [boards.first] - end - end - - it 'sorts boards' do - expect(grab_names).to eq expected_boards.map(&:name) - end - - context 'when paginating' do - let(:params) { 'first: 2' } - - it 'sorts boards' do - expect(grab_names).to eq expected_boards.first(2).map(&:name) - - cursored_query = query("after: \"#{end_cursor}\"") - post_graphql(cursored_query, current_user: current_user) - - response_data = Gitlab::Json.parse(response.body)['data'][board_parent_type]['boards']['edges'] - - expect(grab_names(response_data)).to eq expected_boards.drop(2).first(2).map(&:name) + it_behaves_like 'sorted paginated query' do + let(:sort_param) { } + let(:first_param) { 2 } + let(:expected_results) do + if board_parent.multiple_issue_boards_available? + boards.map { |board| board.to_global_id.to_s } + else + [boards.first.to_global_id.to_s] + end end end end diff --git a/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb b/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb new file mode 100644 index 00000000000..ded381fd402 --- /dev/null +++ b/spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'issuable update endpoint' do + let(:area) { entity.class.name.underscore.pluralize } + + describe 'PUT /projects/:id/issues/:issue_id' do + let(:url) { "/projects/#{project.id}/#{area}/#{entity.iid}" } + + it 'clears labels when labels param is nil' do + put api(url, user), params: { labels: 'label1' } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['labels']).to contain_exactly('label1') + + put api(url, user), params: { labels: nil } + + expect(response).to have_gitlab_http_status(:ok) + json_response = Gitlab::Json.parse(response.body) + expect(json_response['labels']).to be_empty + end + + it 'updates the issuable with labels param as array' do + stub_const("Gitlab::QueryLimiting::Transaction::THRESHOLD", 110) + + params = { labels: ['label1', 'label2', 'foo, bar', '&,?'] } + + put api(url, user), params: params + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['labels']).to include 'label1' + expect(json_response['labels']).to include 'label2' + expect(json_response['labels']).to include 'foo' + expect(json_response['labels']).to include 'bar' + expect(json_response['labels']).to include '&' + expect(json_response['labels']).to include '?' + end + end +end diff --git a/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb b/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb index f49f944f38d..675b6c5cef6 100644 --- a/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb @@ -93,6 +93,20 @@ RSpec.shared_examples 'resource_label_events API' do |parent_type, eventable_typ end end + describe 'pagination' do + let!(:event1) { create_event(label) } + let!(:event2) { create_event(label) } + + # https://gitlab.com/gitlab-org/gitlab/-/issues/220192 + it "returns the second page" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events?page=2&per_page=1", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.count).to eq(1) + expect(json_response.first['id']).to eq(event2.id) + end + end + def create_event(label) create(:resource_label_event, eventable.class.name.underscore => eventable, label: label) end diff --git a/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb b/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb new file mode 100644 index 00000000000..bca51dab353 --- /dev/null +++ b/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'resource_milestone_events API' do |parent_type, eventable_type, id_name| + describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_milestone_events" do + let!(:event) { create_event(milestone) } + + it "returns an array of resource milestone events" do + url = "/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events" + get api(url, user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(event.id) + expect(json_response.first['milestone']['id']).to eq(event.milestone.id) + expect(json_response.first['action']).to eq(event.action) + end + + it "returns a 404 error when eventable id not found" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{non_existing_record_id}/resource_milestone_events", user) + + expect(response).to have_gitlab_http_status(:not_found) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + private_user = create(:user) + + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events", private_user) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_milestone_events/:event_id" do + let!(:event) { create_event(milestone) } + + it "returns a resource milestone event by id" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events/#{event.id}", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['id']).to eq(event.id) + expect(json_response['milestone']['id']).to eq(event.milestone.id) + expect(json_response['action']).to eq(event.action) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + private_user = create(:user) + + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events/#{event.id}", private_user) + + expect(response).to have_gitlab_http_status(:not_found) + end + + it "returns a 404 error if resource milestone event not found" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events/#{non_existing_record_id}", user) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + def create_event(milestone, action: :add) + create(:resource_milestone_event, eventable.class.name.underscore => eventable, milestone: milestone, action: action) + end +end diff --git a/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb b/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb index db5c4b45b70..1ef08de31a9 100644 --- a/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb +++ b/spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb @@ -8,7 +8,7 @@ RSpec.shared_examples 'diff file base entity' do :file_hash, :file_path, :old_path, :new_path, :viewer, :diff_refs, :stored_externally, :external_storage, :renamed_file, :deleted_file, - :a_mode, :b_mode, :new_file) + :a_mode, :b_mode, :new_file, :file_identifier_hash) end # Converted diff files from GitHub import does not contain blob file diff --git a/spec/support/shared_examples/serializers/import/import_entity_shared_examples.rb b/spec/support/shared_examples/serializers/import/import_entity_shared_examples.rb new file mode 100644 index 00000000000..6422c4beb1d --- /dev/null +++ b/spec/support/shared_examples/serializers/import/import_entity_shared_examples.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'exposes required fields for import entity' do + describe 'exposes required fields' do + it 'correctly exposes id' do + expect(subject[:id]).to eql(expected_values[:id]) + end + + it 'correctly exposes full name' do + expect(subject[:full_name]).to eql(expected_values[:full_name]) + end + + it 'correctly exposes sanitized name' do + expect(subject[:sanitized_name]).to eql(expected_values[:sanitized_name]) + end + + it 'correctly exposes provider link' do + expect(subject[:provider_link]).to eql(expected_values[:provider_link]) + end + end +end diff --git a/spec/support/shared_examples/services/container_expiration_policy_shared_examples.rb b/spec/support/shared_examples/services/container_expiration_policy_shared_examples.rb new file mode 100644 index 00000000000..28bf46a57d5 --- /dev/null +++ b/spec/support/shared_examples/services/container_expiration_policy_shared_examples.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'updating the container expiration policy attributes' do |mode:, from: {}, to:| + if mode == :create + it 'creates a new container expiration policy' do + expect { subject } + .to change { project.reload.container_expiration_policy.present? }.from(false).to(true) + .and change { ContainerExpirationPolicy.count }.by(1) + end + else + it_behaves_like 'not creating the container expiration policy' + end + + it 'updates the container expiration policy' do + if from.empty? + subject + + expect(container_expiration_policy.reload.cadence).to eq(to[:cadence]) + expect(container_expiration_policy.keep_n).to eq(to[:keep_n]) + expect(container_expiration_policy.older_than).to eq(to[:older_than]) + else + expect { subject } + .to change { container_expiration_policy.reload.cadence }.from(from[:cadence]).to(to[:cadence]) + .and change { container_expiration_policy.reload.keep_n }.from(from[:keep_n]).to(to[:keep_n]) + .and change { container_expiration_policy.reload.older_than }.from(from[:older_than]).to(to[:older_than]) + end + end +end + +RSpec.shared_examples 'not creating the container expiration policy' do + it "doesn't create the container expiration policy" do + expect { subject }.not_to change { ContainerExpirationPolicy.count } + end +end + +RSpec.shared_examples 'creating the container expiration policy' do + it_behaves_like 'updating the container expiration policy attributes', mode: :create, to: { cadence: '3month', keep_n: 100, older_than: '14d' } + + it_behaves_like 'returning a success' +end diff --git a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb index 71bdd46572f..efcb83a34af 100644 --- a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb +++ b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb @@ -45,7 +45,7 @@ RSpec.shared_examples 'WikiPages::CreateService#execute' do |container_type| expect { service.execute }.to change { Event.count }.by 1 expect(Event.recent.first).to have_attributes( - action: Event::CREATED, + action: 'created', target: have_attributes(canonical_slug: page_title) ) end diff --git a/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb index 62541eb3da9..1231c012c31 100644 --- a/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb +++ b/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb @@ -27,7 +27,7 @@ RSpec.shared_examples 'WikiPages::DestroyService#execute' do |container_type| expect { service.execute(page) }.to change { Event.count }.by 1 expect(Event.recent.first).to have_attributes( - action: Event::DESTROYED, + action: 'destroyed', target: have_attributes(canonical_slug: page.slug) ) end diff --git a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb index 0dfc99d043b..77354fec069 100644 --- a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb +++ b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb @@ -48,7 +48,7 @@ RSpec.shared_examples 'WikiPages::UpdateService#execute' do |container_type| expect { service.execute(page) }.to change { Event.count }.by 1 expect(Event.recent.first).to have_attributes( - action: Event::UPDATED, + action: 'updated', wiki_page: page, target_title: page.title ) diff --git a/spec/support/shared_examples/uncached_response_shared_examples.rb b/spec/support/shared_examples/uncached_response_shared_examples.rb new file mode 100644 index 00000000000..3997017ff35 --- /dev/null +++ b/spec/support/shared_examples/uncached_response_shared_examples.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true +# +# Pairs with lib/gitlab/no_cache_headers.rb +# + +RSpec.shared_examples 'uncached response' do + it 'defines an uncached header response' do + expect(response.headers["Cache-Control"]).to include("no-store", "no-cache") + expect(response.headers["Pragma"]).to eq("no-cache") + expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") + end +end |