summaryrefslogtreecommitdiff
path: root/spec/support/shared_examples
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 11:18:50 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 11:18:50 +0000
commit8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch)
treea77e7fe7a93de11213032ed4ab1f33a3db51b738 /spec/support/shared_examples
parent00b35af3db1abfe813a778f643dad221aad51fca (diff)
downloadgitlab-ce-8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781.tar.gz
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'spec/support/shared_examples')
-rw-r--r--spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb36
-rw-r--r--spec/support/shared_examples/controllers/issuables_list_metadata_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/milestone_tabs_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb302
-rw-r--r--spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb6
-rw-r--r--spec/support/shared_examples/graphql/container_expiration_policy_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/graphql/label_fields.rb124
-rw-r--r--spec/support/shared_examples/graphql/members_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/integrations/test_examples.rb11
-rw-r--r--spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/models/application_setting_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_application_initial_status_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb63
-rw-r--r--spec/support/shared_examples/models/concerns/blob_replicator_strategy_shared_examples.rb117
-rw-r--r--spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/models/concerns/limitable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/path_extraction_shared_examples.rb118
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb55
-rw-r--r--spec/support/shared_examples/requests/api/issuable_update_shared_examples.rb38
-rw-r--r--spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb66
-rw-r--r--spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/serializers/import/import_entity_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/services/container_expiration_policy_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/uncached_response_shared_examples.rb12
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