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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support/shared_examples')
-rw-r--r--spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb31
-rw-r--r--spec/support/shared_examples/controllers/concerns/integrations/integrations_actions_shared_examples.rb (renamed from spec/support/shared_examples/controllers/concerns/integrations_actions_shared_examples.rb)2
-rw-r--r--spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb58
-rw-r--r--spec/support/shared_examples/features/2fa_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/features/dependency_proxy_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/manage_applications_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/packages_shared_examples.rb30
-rw-r--r--spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb38
-rw-r--r--spec/support/shared_examples/features/sidebar_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/graphql/notes_creation_shared_examples.rb26
-rw-r--r--spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb67
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb52
-rw-r--r--spec/support/shared_examples/metrics/transaction_metrics_with_labels_shared_examples.rb219
-rw-r--r--spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb117
-rw-r--r--spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb31
-rw-r--r--spec/support/shared_examples/models/reviewer_state_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/namespaces/traversal_examples.rb22
-rw-r--r--spec/support/shared_examples/namespaces/traversal_scope_examples.rb81
-rw-r--r--spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/requests/api/debian_common_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb192
-rw-r--r--spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb369
-rw-r--r--spec/support/shared_examples/requests/api/graphql/mutations/destroy_list_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/status_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/requests/applications_controller_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/requests/self_monitoring_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/snippet_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/service_desk_issue_templates_examples.rb8
-rw-r--r--spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/services/jira/requests/base_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb25
-rw-r--r--spec/support/shared_examples/workers/self_monitoring_shared_examples.rb2
42 files changed, 1232 insertions, 475 deletions
diff --git a/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
new file mode 100644
index 00000000000..e8cc666605b
--- /dev/null
+++ b/spec/support/shared_examples/bulk_imports/common/pipelines/wiki_pipeline_examples.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
+ describe '#run' do
+ let_it_be(:bulk_import_configuration) { create(:bulk_import_configuration, bulk_import: bulk_import) }
+
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
+
+ let(:extracted_data) { BulkImports::Pipeline::ExtractedData.new(data: {}) }
+
+ context 'successfully imports wiki for an entity' do
+ subject { described_class.new(context) }
+
+ before do
+ allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
+ allow(extractor).to receive(:extract).and_return(extracted_data)
+ end
+ end
+
+ it 'imports new wiki into destination project' do
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ url = "https://oauth2:token@gitlab.example/#{entity.source_full_path}.wiki.git"
+ expect(repository_service).to receive(:fetch_remote).with(url, any_args).and_return 0
+ end
+
+ subject.run
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/concerns/integrations_actions_shared_examples.rb b/spec/support/shared_examples/controllers/concerns/integrations/integrations_actions_shared_examples.rb
index 748a3acf17b..a8aed0c1f0b 100644
--- a/spec/support/shared_examples/controllers/concerns/integrations_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/concerns/integrations/integrations_actions_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples IntegrationsActions do
+RSpec.shared_examples Integrations::Actions do
let(:integration) do
create(:datadog_integration,
integration_attributes.merge(
diff --git a/spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb b/spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb
index 74a98c20383..8affe4ac8f5 100644
--- a/spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb
@@ -6,39 +6,41 @@
# - request_full_path
RSpec.shared_examples 'request exceeding rate limit' do
- before do
- stub_application_setting(notes_create_limit: 2)
- 2.times { post :create, params: params }
- end
+ context 'with rate limiter', :freeze_time, :clean_gitlab_redis_rate_limiting do
+ before do
+ stub_application_setting(notes_create_limit: 2)
+ 2.times { post :create, params: params }
+ end
- it 'prevents from creating more notes', :request_store do
- expect { post :create, params: params }
- .to change { Note.count }.by(0)
+ it 'prevents from creating more notes' do
+ expect { post :create, params: params }
+ .to change { Note.count }.by(0)
- expect(response).to have_gitlab_http_status(:too_many_requests)
- expect(response.body).to eq(_('This endpoint has been requested too many times. Try again later.'))
- end
+ expect(response).to have_gitlab_http_status(:too_many_requests)
+ expect(response.body).to eq(_('This endpoint has been requested too many times. Try again later.'))
+ end
- it 'logs the event in auth.log' do
- attributes = {
- message: 'Application_Rate_Limiter_Request',
- env: :notes_create_request_limit,
- remote_ip: '0.0.0.0',
- request_method: 'POST',
- path: request_full_path,
- user_id: user.id,
- username: user.username
- }
+ it 'logs the event in auth.log' do
+ attributes = {
+ message: 'Application_Rate_Limiter_Request',
+ env: :notes_create_request_limit,
+ remote_ip: '0.0.0.0',
+ request_method: 'POST',
+ path: request_full_path,
+ user_id: user.id,
+ username: user.username
+ }
- expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
- post :create, params: params
- end
+ expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
+ post :create, params: params
+ end
- it 'allows user in allow-list to create notes, even if the case is different' do
- user.update_attribute(:username, user.username.titleize)
- stub_application_setting(notes_create_limit_allowlist: ["#{user.username.downcase}"])
+ it 'allows user in allow-list to create notes, even if the case is different' do
+ user.update_attribute(:username, user.username.titleize)
+ stub_application_setting(notes_create_limit_allowlist: ["#{user.username.downcase}"])
- post :create, params: params
- expect(response).to have_gitlab_http_status(:found)
+ post :create, params: params
+ expect(response).to have_gitlab_http_status(:found)
+ end
end
end
diff --git a/spec/support/shared_examples/features/2fa_shared_examples.rb b/spec/support/shared_examples/features/2fa_shared_examples.rb
index ddc03e178ba..94c91556ea7 100644
--- a/spec/support/shared_examples/features/2fa_shared_examples.rb
+++ b/spec/support/shared_examples/features/2fa_shared_examples.rb
@@ -18,6 +18,7 @@ RSpec.shared_examples 'hardware device for 2fa' do |device_type|
let(:user) { create(:user) }
before do
+ stub_feature_flags(bootstrap_confirmation_modals: false)
gitlab_sign_in(user)
user.update_attribute(:otp_required_for_login, true)
end
diff --git a/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb b/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb
index d29c677a962..5d1488502d2 100644
--- a/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb
+++ b/spec/support/shared_examples/features/dependency_proxy_shared_examples.rb
@@ -26,7 +26,7 @@ RSpec.shared_examples 'a successful manifest pull' do
subject
expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers['Docker-Content-Digest']).to eq(manifest.digest)
+ expect(response.headers[DependencyProxy::Manifest::DIGEST_HEADER]).to eq(manifest.digest)
expect(response.headers['Content-Length']).to eq(manifest.size)
expect(response.headers['Docker-Distribution-Api-Version']).to eq(DependencyProxy::DISTRIBUTION_API_VERSION)
expect(response.headers['Etag']).to eq("\"#{manifest.digest}\"")
diff --git a/spec/support/shared_examples/features/manage_applications_shared_examples.rb b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
index 0161899cb76..27d50c67f24 100644
--- a/spec/support/shared_examples/features/manage_applications_shared_examples.rb
+++ b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
@@ -18,6 +18,7 @@ RSpec.shared_examples 'manage applications' do
click_on 'Save application'
validate_application(application_name, 'Yes')
+ expect(page).to have_link('Continue', href: index_path)
application = Doorkeeper::Application.find_by(name: application_name)
expect(page).to have_css("button[title=\"Copy secret\"][data-clipboard-text=\"#{application.secret}\"]", text: 'Copy')
@@ -33,6 +34,7 @@ RSpec.shared_examples 'manage applications' do
click_on 'Save application'
validate_application(application_name_changed, 'No')
+ expect(page).not_to have_link('Continue')
visit_applications_path
diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb
index 96be30b9f1f..d14b4638ca5 100644
--- a/spec/support/shared_examples/features/packages_shared_examples.rb
+++ b/spec/support/shared_examples/features/packages_shared_examples.rb
@@ -21,10 +21,6 @@ end
RSpec.shared_examples 'package details link' do |property|
let(:package) { packages.first }
- before do
- stub_feature_flags(packages_details_one_column: false)
- end
-
it 'navigates to the correct url' do
page.within(packages_table_selector) do
click_link package.name
@@ -32,7 +28,7 @@ RSpec.shared_examples 'package details link' do |property|
expect(page).to have_current_path(project_package_path(package.project, package))
- expect(page).to have_css('.packages-app h1[data-testid="title"]', text: package.name)
+ expect(page).to have_css('.packages-app h2[data-testid="title"]', text: package.name)
expect(page).to have_content('Installation')
expect(page).to have_content('Registry setup')
@@ -94,16 +90,24 @@ def packages_table_selector
end
def click_sort_option(option, ascending)
- page.within('.gl-sorting') do
- # Reset the sort direction
- click_button 'Sort direction' if page.has_selector?('svg[aria-label="Sorting Direction: Ascending"]', wait: 0)
+ wait_for_requests
- find('button.gl-dropdown-toggle').click
+ # Reset the sort direction
+ if page.has_selector?('button[aria-label="Sorting Direction: Ascending"]', wait: 0) && !ascending
+ click_button 'Sort direction'
- page.within('.dropdown-menu') do
- click_button option
- end
+ wait_for_requests
+ end
+
+ find('button.gl-dropdown-toggle').click
+
+ page.within('.dropdown-menu') do
+ click_button option
+ end
+
+ if ascending
+ wait_for_requests
- click_button 'Sort direction' if ascending
+ click_button 'Sort direction'
end
end
diff --git a/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb b/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb
index 6d44a6fde85..337b3f3cbd0 100644
--- a/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb
+++ b/spec/support/shared_examples/features/resolving_discussions_in_issues_shared_examples.rb
@@ -1,43 +1,29 @@
# frozen_string_literal: true
RSpec.shared_examples 'creating an issue for a thread' do
- it 'shows an issue with the title filled in' do
+ it 'shows an issue creation form' do
+ # Title field is filled in
title_field = page.find_field('issue[title]')
-
expect(title_field.value).to include(merge_request.title)
- end
- it 'has a mention of the discussion in the description' do
- description_field = page.find_field('issue[description]')
+ # Has a hidden field for the merge request
+ merge_request_field = find('#merge_request_to_resolve_discussions_of', visible: false)
+ expect(merge_request_field.value).to eq(merge_request.iid.to_s)
+ # Has a mention of the discussion in the description
+ description_field = page.find_field('issue[description]')
expect(description_field.value).to include(discussion.first_note.note)
end
- it 'can create a new issue for the project' do
+ it 'creates a new issue for the project' do
+ # Actually creates an issue for the project
expect { click_button 'Create issue' }.to change { project.issues.reload.size }.by(1)
- end
-
- it 'resolves the discussion in the merge request' do
- click_button 'Create issue'
+ # Resolves the discussion in the merge request
discussion.first_note.reload
-
expect(discussion.resolved?).to eq(true)
- end
-
- it 'shows a flash messaage after resolving a discussion' do
- click_button 'Create issue'
-
- page.within '.flash-notice' do
- # Only check for the word 'Resolved' since the spec might have resolved
- # multiple discussions
- expect(page).to have_content('Resolved')
- end
- end
-
- it 'has a hidden field for the merge request' do
- merge_request_field = find('#merge_request_to_resolve_discussions_of', visible: false)
- expect(merge_request_field.value).to eq(merge_request.iid.to_s)
+ # Issue title inludes MR title
+ expect(page).to have_content(%Q(Follow-up from "#{merge_request.title}"))
end
end
diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb
index 5bfe929e957..d509d124de0 100644
--- a/spec/support/shared_examples/features/sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb
@@ -52,16 +52,17 @@ RSpec.shared_examples 'issue boards sidebar' do
it 'shows toggle as on then as off as user toggles to subscribe and unsubscribe', :aggregate_failures do
wait_for_requests
+ subscription_button = find('[data-testid="subscription-toggle"]')
- click_button 'Notifications'
+ subscription_button.click
- expect(page).to have_button('Notifications', class: 'is-checked')
+ expect(subscription_button).to have_css("button.is-checked")
- click_button 'Notifications'
+ subscription_button.click
wait_for_requests
- expect(page).not_to have_button('Notifications', class: 'is-checked')
+ expect(subscription_button).to have_css("button:not(.is-checked)")
end
context 'when notifications have been disabled' do
@@ -73,7 +74,7 @@ RSpec.shared_examples 'issue boards sidebar' do
it 'displays a message that notifications have been disabled' do
page.within('[data-testid="sidebar-notifications"]') do
- expect(page).to have_button('Notifications', class: 'is-disabled')
+ expect(page).to have_selector('[data-testid="subscription-toggle"]', class: 'is-disabled')
expect(page).to have_content('Disabled by project owner')
end
end
diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
index fb598b978f6..56b6dc682eb 100644
--- a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
@@ -66,20 +66,22 @@ RSpec.shared_examples 'a Note mutation when the given resource id is not for a N
end
RSpec.shared_examples 'a Note mutation when there are rate limit validation errors' do
- before do
- stub_application_setting(notes_create_limit: 3)
- 3.times { post_graphql_mutation(mutation, current_user: current_user) }
- end
-
- it_behaves_like 'a Note mutation that does not create a Note'
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['This endpoint has been requested too many times. Try again later.']
-
- context 'when the user is in the allowlist' do
+ context 'with rate limiter', :freeze_time, :clean_gitlab_redis_rate_limiting do
before do
- stub_application_setting(notes_create_limit_allowlist: ["#{current_user.username}"])
+ stub_application_setting(notes_create_limit: 3)
+ 3.times { post_graphql_mutation(mutation, current_user: current_user) }
end
- it_behaves_like 'a Note mutation that creates a Note'
+ it_behaves_like 'a Note mutation that does not create a Note'
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['This endpoint has been requested too many times. Try again later.']
+
+ context 'when the user is in the allowlist' do
+ before do
+ stub_application_setting(notes_create_limit_allowlist: ["#{current_user.username}"])
+ end
+
+ it_behaves_like 'a Note mutation that creates a Note'
+ end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
index 8b4ecd7d5ae..a3c67210a4a 100644
--- a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb
@@ -35,8 +35,8 @@ RSpec.shared_examples 'common trace features' do
stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: trace.job.project)
end
- it 'calls ::ApplicationRecord.sticking.unstick_or_continue_sticking' do
- expect(::ApplicationRecord.sticking).to receive(:unstick_or_continue_sticking)
+ it 'calls ::Ci::Build.sticking.unstick_or_continue_sticking' do
+ expect(::Ci::Build.sticking).to receive(:unstick_or_continue_sticking)
.with(described_class::LOAD_BALANCING_STICKING_NAMESPACE, trace.job.id)
.and_call_original
@@ -49,8 +49,8 @@ RSpec.shared_examples 'common trace features' do
stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: false)
end
- it 'does not call ::ApplicationRecord.sticking.unstick_or_continue_sticking' do
- expect(::ApplicationRecord.sticking).not_to receive(:unstick_or_continue_sticking)
+ it 'does not call ::Ci::Build.sticking.unstick_or_continue_sticking' do
+ expect(::Ci::Build.sticking).not_to receive(:unstick_or_continue_sticking)
trace.read { |stream| stream }
end
@@ -305,8 +305,8 @@ RSpec.shared_examples 'common trace features' do
stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: trace.job.project)
end
- it 'calls ::ApplicationRecord.sticking.stick' do
- expect(::ApplicationRecord.sticking).to receive(:stick)
+ it 'calls ::Ci::Build.sticking.stick' do
+ expect(::Ci::Build.sticking).to receive(:stick)
.with(described_class::LOAD_BALANCING_STICKING_NAMESPACE, trace.job.id)
.and_call_original
@@ -319,8 +319,8 @@ RSpec.shared_examples 'common trace features' do
stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: false)
end
- it 'does not call ::ApplicationRecord.sticking.stick' do
- expect(::ApplicationRecord.sticking).not_to receive(:stick)
+ it 'does not call ::Ci::Build.sticking.stick' do
+ expect(::Ci::Build.sticking).not_to receive(:stick)
subject
end
@@ -808,7 +808,19 @@ RSpec.shared_examples 'trace with enabled live trace feature' do
create(:ci_job_artifact, :trace, job: build)
end
- it { is_expected.to be_truthy }
+ it 'is truthy' do
+ is_expected.to be_truthy
+ end
+ end
+
+ context 'when archived trace record exists but file is not stored' do
+ before do
+ create(:ci_job_artifact, :unarchived_trace_artifact, job: build)
+ end
+
+ it 'is falsy' do
+ is_expected.to be_falsy
+ end
end
context 'when live trace exists' do
@@ -872,13 +884,35 @@ RSpec.shared_examples 'trace with enabled live trace feature' do
build.reload
expect(build.trace.exist?).to be_truthy
- expect(build.job_artifacts_trace).to be_nil
Gitlab::Ci::Trace::ChunkedIO.new(build) do |stream|
expect(stream.read).to eq(trace_raw)
end
end
end
+ shared_examples 'a pre-commit error' do |error:|
+ it_behaves_like 'source trace in ChunkedIO stays intact', error: error
+
+ it 'does not save the trace artifact' do
+ expect { subject }.to raise_error(error)
+
+ build.reload
+ expect(build.job_artifacts_trace).to be_nil
+ end
+ end
+
+ shared_examples 'a post-commit error' do |error:|
+ it_behaves_like 'source trace in ChunkedIO stays intact', error: error
+
+ it 'saves the trace artifact but not the file' do
+ expect { subject }.to raise_error(error)
+
+ build.reload
+ expect(build.job_artifacts_trace).to be_present
+ expect(build.job_artifacts_trace.file.exists?).to be_falsy
+ end
+ end
+
context 'when job does not have trace artifact' do
context 'when trace is stored in ChunkedIO' do
let!(:build) { create(:ci_build, :success, :trace_live) }
@@ -892,7 +926,7 @@ RSpec.shared_examples 'trace with enabled live trace feature' do
allow(IO).to receive(:copy_stream).and_return(0)
end
- it_behaves_like 'source trace in ChunkedIO stays intact', error: Gitlab::Ci::Trace::ArchiveError
+ it_behaves_like 'a pre-commit error', error: Gitlab::Ci::Trace::ArchiveError
end
context 'when failed to create job artifact record' do
@@ -902,7 +936,16 @@ RSpec.shared_examples 'trace with enabled live trace feature' do
.and_return(%w[Error Error])
end
- it_behaves_like 'source trace in ChunkedIO stays intact', error: ActiveRecord::RecordInvalid
+ it_behaves_like 'a pre-commit error', error: ActiveRecord::RecordInvalid
+ end
+
+ context 'when storing the file raises an error' do
+ before do
+ stub_artifacts_object_storage(direct_upload: true)
+ allow_any_instance_of(Ci::JobArtifact).to receive(:store_file!).and_raise(Excon::Error::BadGateway, 'S3 is down lol')
+ end
+
+ it_behaves_like 'a post-commit error', error: Excon::Error::BadGateway
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb
index 6342064beb8..bea7cca2744 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb
@@ -6,7 +6,7 @@ shared_examples 'deployment metrics examples' do
environment = project.environments.production.first || create(:environment, :production, project: project)
create(:deployment, :success, args.merge(environment: environment))
- # this is needed for the dora_deployment_frequency_in_vsa feature flag so we have aggregated data
+ # this is needed for the DORA API so we have aggregated data
::Dora::DailyMetrics::RefreshWorker.new.perform(environment.id, Time.current.to_date.to_s) if Gitlab.ee?
end
diff --git a/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
index a617342ff8c..df795723874 100644
--- a/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb
@@ -11,7 +11,7 @@ RSpec.shared_examples 'CTE with MATERIALIZED keyword examples' do
context 'when PG version is <12' do
it 'does not add MATERIALIZE keyword' do
- allow(Gitlab::Database.main).to receive(:version).and_return('11.1')
+ allow(ApplicationRecord.database).to receive(:version).and_return('11.1')
expect(query).to include(expected_query_block_without_materialized)
end
@@ -19,14 +19,14 @@ RSpec.shared_examples 'CTE with MATERIALIZED keyword examples' do
context 'when PG version is >=12' do
it 'adds MATERIALIZE keyword' do
- allow(Gitlab::Database.main).to receive(:version).and_return('12.1')
+ allow(ApplicationRecord.database).to receive(:version).and_return('12.1')
expect(query).to include(expected_query_block_with_materialized)
end
context 'when version is higher than 12' do
it 'adds MATERIALIZE keyword' do
- allow(Gitlab::Database.main).to receive(:version).and_return('15.1')
+ allow(ApplicationRecord.database).to receive(:version).and_return('15.1')
expect(query).to include(expected_query_block_with_materialized)
end
diff --git a/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb
index 5ce698c4701..41d3d76b66b 100644
--- a/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attributes|
+RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attributes, additional_attributes = []|
let(:prohibited_attributes) { %i[remote_url my_attributes my_ids token my_id test] }
let(:import_export_config) { Gitlab::ImportExport::Config.new.to_h }
@@ -26,7 +26,7 @@ RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attrib
end
it 'does not contain attributes that would be cleaned with AttributeCleaner' do
- expect(cleaned_hash.keys).to include(*permitted_hash.keys)
+ expect(cleaned_hash.keys + additional_attributes.to_a).to include(*permitted_hash.keys)
end
it 'does not contain prohibited attributes that are not related to given relation' do
diff --git a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
index 708bc71ae96..ff03051ed37 100644
--- a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
let(:fake_duplicate_job) do
- instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob)
+ instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, duplicate_key_ttl: Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob::DEFAULT_DUPLICATE_KEY_TTL)
end
let(:expected_message) { "dropped #{strategy_name.to_s.humanize.downcase}" }
@@ -11,14 +11,14 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
describe '#schedule' do
before do
- allow(Gitlab::SidekiqLogging::DeduplicationLogger.instance).to receive(:log)
+ allow(Gitlab::SidekiqLogging::DeduplicationLogger.instance).to receive(:deduplicated_log)
end
it 'checks for duplicates before yielding' do
expect(fake_duplicate_job).to receive(:scheduled?).twice.ordered.and_return(false)
expect(fake_duplicate_job).to(
receive(:check!)
- .with(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob::DUPLICATE_KEY_TTL)
+ .with(fake_duplicate_job.duplicate_key_ttl)
.ordered
.and_return('a jid'))
expect(fake_duplicate_job).to receive(:duplicate?).ordered.and_return(false)
@@ -40,6 +40,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
allow(fake_duplicate_job).to receive(:update_latest_wal_location!)
+ allow(fake_duplicate_job).to receive(:set_deduplicated_flag!)
allow(fake_duplicate_job).to receive(:options).and_return({})
job_hash = {}
@@ -61,10 +62,11 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
allow(fake_duplicate_job).to receive(:options).and_return({ including_scheduled: true })
allow(fake_duplicate_job).to(
receive(:check!)
- .with(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob::DUPLICATE_KEY_TTL)
+ .with(fake_duplicate_job.duplicate_key_ttl)
.and_return('the jid'))
allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
allow(fake_duplicate_job).to receive(:update_latest_wal_location!)
+ allow(fake_duplicate_job).to receive(:set_deduplicated_flag!)
job_hash = {}
expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
@@ -83,9 +85,10 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
allow(fake_duplicate_job).to receive(:scheduled_at).and_return(Time.now + time_diff)
allow(fake_duplicate_job).to receive(:options).and_return({ including_scheduled: true })
allow(fake_duplicate_job).to(
- receive(:check!).with(time_diff.to_i).and_return('the jid'))
+ receive(:check!).with(time_diff.to_i + fake_duplicate_job.duplicate_key_ttl).and_return('the jid'))
allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
allow(fake_duplicate_job).to receive(:update_latest_wal_location!)
+ allow(fake_duplicate_job).to receive(:set_deduplicated_flag!)
job_hash = {}
expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
@@ -100,6 +103,26 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
end
end
+ context "when the job is not duplicate" do
+ before do
+ allow(fake_duplicate_job).to receive(:scheduled?).and_return(false)
+ allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
+ allow(fake_duplicate_job).to receive(:duplicate?).and_return(false)
+ allow(fake_duplicate_job).to receive(:options).and_return({})
+ allow(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
+ end
+
+ it 'does not return false nor drop the job' do
+ schedule_result = nil
+
+ expect(fake_duplicate_job).not_to receive(:set_deduplicated_flag!)
+
+ expect { |b| schedule_result = strategy.schedule({}, &b) }.to yield_control
+
+ expect(schedule_result).to be_nil
+ end
+ end
+
context "when the job is droppable" do
before do
allow(fake_duplicate_job).to receive(:scheduled?).and_return(false)
@@ -109,6 +132,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
allow(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
allow(fake_duplicate_job).to receive(:idempotent?).and_return(true)
allow(fake_duplicate_job).to receive(:update_latest_wal_location!)
+ allow(fake_duplicate_job).to receive(:set_deduplicated_flag!)
end
it 'updates latest wal location' do
@@ -117,10 +141,11 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
strategy.schedule({ 'jid' => 'new jid' }) {}
end
- it 'drops the job' do
+ it 'returns false to drop the job' do
schedule_result = nil
expect(fake_duplicate_job).to receive(:idempotent?).and_return(true)
+ expect(fake_duplicate_job).to receive(:set_deduplicated_flag!).once
expect { |b| schedule_result = strategy.schedule({}, &b) }.not_to yield_control
expect(schedule_result).to be(false)
@@ -130,7 +155,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger)
expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
- expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), expected_message, {})
+ expect(fake_logger).to receive(:deduplicated_log).with(a_hash_including({ 'jid' => 'new jid' }), expected_message, {})
strategy.schedule({ 'jid' => 'new jid' }) {}
end
@@ -140,7 +165,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
allow(fake_duplicate_job).to receive(:options).and_return({ foo: :bar })
- expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), expected_message, { foo: :bar })
+ expect(fake_logger).to receive(:deduplicated_log).with(a_hash_including({ 'jid' => 'new jid' }), expected_message, { foo: :bar })
strategy.schedule({ 'jid' => 'new jid' }) {}
end
@@ -159,6 +184,9 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name|
before do
allow(fake_duplicate_job).to receive(:delete!)
+ allow(fake_duplicate_job).to receive(:scheduled?) { false }
+ allow(fake_duplicate_job).to receive(:options) { {} }
+ allow(fake_duplicate_job).to receive(:should_reschedule?) { false }
allow(fake_duplicate_job).to receive(:latest_wal_locations).and_return( wal_locations )
end
diff --git a/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb
new file mode 100644
index 00000000000..d3fd28727b5
--- /dev/null
+++ b/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples 'ZenTao menu with CE version' do
+ let(:project) { create(:project, has_external_issue_tracker: true) }
+ let(:user) { project.owner }
+ let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) }
+ let(:zentao_integration) { create(:zentao_integration, project: project) }
+
+ subject { described_class.new(context) }
+
+ describe '#render?' do
+ context 'when issues integration is disabled' do
+ before do
+ zentao_integration.update!(active: false)
+ end
+
+ it 'returns false' do
+ expect(subject.render?).to eq false
+ end
+ end
+
+ context 'when issues integration is enabled' do
+ before do
+ zentao_integration.update!(active: true)
+ end
+
+ it 'returns true' do
+ expect(subject.render?).to eq true
+ end
+
+ it 'renders menu link' do
+ expect(subject.link).to eq zentao_integration.url
+ end
+
+ it 'contains only open ZenTao item' do
+ expect(subject.renderable_items.map(&:item_id)).to match_array [:open_zentao]
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb b/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
new file mode 100644
index 00000000000..7ccd9533811
--- /dev/null
+++ b/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'it has loose foreign keys' do
+ let(:factory_name) { nil }
+ let(:table_name) { described_class.table_name }
+ let(:connection) { described_class.connection }
+
+ it 'includes the LooseForeignKey module' do
+ expect(described_class.ancestors).to include(LooseForeignKey)
+ end
+
+ it 'responds to #loose_foreign_key_definitions' do
+ expect(described_class).to respond_to(:loose_foreign_key_definitions)
+ end
+
+ it 'has at least one loose foreign key definition' do
+ expect(described_class.loose_foreign_key_definitions.size).to be > 0
+ end
+
+ it 'has the deletion trigger present' do
+ sql = <<-SQL
+ SELECT trigger_name
+ FROM information_schema.triggers
+ WHERE event_object_table = '#{table_name}'
+ SQL
+
+ triggers = connection.execute(sql)
+
+ expected_trigger_name = "#{table_name}_loose_fk_trigger"
+ expect(triggers.pluck('trigger_name')).to include(expected_trigger_name)
+ end
+
+ it 'records record deletions' do
+ model = create(factory_name) # rubocop: disable Rails/SaveBang
+ model.destroy!
+
+ deleted_record = LooseForeignKeys::DeletedRecord.find_by(fully_qualified_table_name: "#{connection.current_schema}.#{table_name}", primary_key_value: model.id)
+
+ expect(deleted_record).not_to be_nil
+ end
+
+ it 'cleans up record deletions' do
+ model = create(factory_name) # rubocop: disable Rails/SaveBang
+
+ expect { model.destroy! }.to change { LooseForeignKeys::DeletedRecord.count }.by(1)
+
+ LooseForeignKeys::ProcessDeletedRecordsService.new(connection: connection).execute
+
+ expect(LooseForeignKeys::DeletedRecord.status_pending.count).to be(0)
+ expect(LooseForeignKeys::DeletedRecord.status_processed.count).to be(1)
+ end
+end
diff --git a/spec/support/shared_examples/metrics/transaction_metrics_with_labels_shared_examples.rb b/spec/support/shared_examples/metrics/transaction_metrics_with_labels_shared_examples.rb
new file mode 100644
index 00000000000..286c60f1f4f
--- /dev/null
+++ b/spec/support/shared_examples/metrics/transaction_metrics_with_labels_shared_examples.rb
@@ -0,0 +1,219 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'transaction metrics with labels' do
+ let(:sensitive_tags) do
+ {
+ path: 'private',
+ branch: 'sensitive'
+ }
+ end
+
+ around do |example|
+ described_class.reload_metric!
+ example.run
+ described_class.reload_metric!
+ end
+
+ describe '.prometheus_metric' do
+ let(:prometheus_metric) { instance_double(Prometheus::Client::Histogram, observe: nil, base_labels: {}) }
+
+ it 'adds a metric' do
+ expect(::Gitlab::Metrics).to receive(:histogram).with(
+ :meow_observe, 'Meow observe histogram', hash_including(*described_class::BASE_LABEL_KEYS), be_a(Array)
+ ).and_return(prometheus_metric)
+
+ expect do |block|
+ metric = described_class.prometheus_metric(:meow_observe, :histogram, &block)
+ expect(metric).to be(prometheus_metric)
+ end.to yield_control
+ end
+ end
+
+ describe '#method_call_for' do
+ it 'returns a MethodCall' do
+ method = transaction_obj.method_call_for('Foo#bar', :Foo, '#bar')
+
+ expect(method).to be_an_instance_of(Gitlab::Metrics::MethodCall)
+ end
+ end
+
+ describe '#add_event' do
+ let(:prometheus_metric) { instance_double(Prometheus::Client::Counter, increment: nil, base_labels: {}) }
+
+ it 'adds a metric' do
+ expect(prometheus_metric).to receive(:increment).with(labels)
+ expect(described_class).to receive(:fetch_metric).with(:counter, :gitlab_transaction_event_meow_total).and_return(prometheus_metric)
+
+ transaction_obj.add_event(:meow)
+ end
+
+ it 'allows tracking of custom tags' do
+ expect(prometheus_metric).to receive(:increment).with(labels.merge(animal: "dog"))
+ expect(described_class).to receive(:fetch_metric).with(:counter, :gitlab_transaction_event_bau_total).and_return(prometheus_metric)
+
+ transaction_obj.add_event(:bau, animal: 'dog')
+ end
+
+ context 'with sensitive tags' do
+ it 'filters tags' do
+ expect(described_class).to receive(:fetch_metric).with(:counter, :gitlab_transaction_event_bau_total).and_return(prometheus_metric)
+ expect(prometheus_metric).not_to receive(:increment).with(hash_including(sensitive_tags))
+
+ transaction_obj.add_event(:bau, **sensitive_tags.merge(sane: 'yes'))
+ end
+ end
+ end
+
+ describe '#increment' do
+ let(:prometheus_metric) { instance_double(Prometheus::Client::Counter, increment: nil, base_labels: {}) }
+
+ it 'adds a metric' do
+ expect(::Gitlab::Metrics).to receive(:counter).with(
+ :meow, 'Meow counter', hash_including(*described_class::BASE_LABEL_KEYS)
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:increment).with(labels, 1)
+
+ transaction_obj.increment(:meow, 1)
+ end
+
+ context 'with block' do
+ it 'overrides docstring' do
+ expect(::Gitlab::Metrics).to receive(:counter).with(
+ :block_docstring, 'test', hash_including(*described_class::BASE_LABEL_KEYS)
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:increment).with(labels, 1)
+
+ transaction_obj.increment(:block_docstring, 1) do
+ docstring 'test'
+ end
+ end
+
+ it 'overrides labels' do
+ expect(::Gitlab::Metrics).to receive(:counter).with(
+ :block_labels, 'Block labels counter', hash_including(*described_class::BASE_LABEL_KEYS)
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:increment).with(labels.merge(sane: 'yes'), 1)
+
+ transaction_obj.increment(:block_labels, 1, sane: 'yes') do
+ label_keys %i(sane)
+ end
+ end
+
+ it 'filters sensitive tags' do
+ labels_keys = sensitive_tags.keys
+
+ expect(::Gitlab::Metrics).to receive(:counter).with(
+ :metric_with_sensitive_block, 'Metric with sensitive block counter', hash_excluding(labels_keys)
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:increment).with(labels, 1)
+
+ transaction_obj.increment(:metric_with_sensitive_block, 1, sensitive_tags) do
+ label_keys labels_keys
+ end
+ end
+ end
+ end
+
+ describe '#set' do
+ let(:prometheus_metric) { instance_double(Prometheus::Client::Gauge, set: nil, base_labels: {}) }
+
+ it 'adds a metric' do
+ expect(::Gitlab::Metrics).to receive(:gauge).with(
+ :meow_set, 'Meow set gauge', hash_including(*described_class::BASE_LABEL_KEYS), :all
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:set).with(labels, 99)
+
+ transaction_obj.set(:meow_set, 99)
+ end
+
+ context 'with block' do
+ it 'overrides docstring' do
+ expect(::Gitlab::Metrics).to receive(:gauge).with(
+ :block_docstring_set, 'test', hash_including(*described_class::BASE_LABEL_KEYS), :all
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:set).with(labels, 99)
+
+ transaction_obj.set(:block_docstring_set, 99) do
+ docstring 'test'
+ end
+ end
+
+ it 'overrides labels' do
+ expect(::Gitlab::Metrics).to receive(:gauge).with(
+ :block_labels_set, 'Block labels set gauge', hash_including(*described_class::BASE_LABEL_KEYS), :all
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:set).with(labels.merge(sane: 'yes'), 99)
+
+ transaction_obj.set(:block_labels_set, 99, sane: 'yes') do
+ label_keys %i(sane)
+ end
+ end
+
+ it 'filters sensitive tags' do
+ labels_keys = sensitive_tags.keys
+
+ expect(::Gitlab::Metrics).to receive(:gauge).with(
+ :metric_set_with_sensitive_block, 'Metric set with sensitive block gauge', hash_excluding(*labels_keys), :all
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:set).with(labels, 99)
+
+ transaction_obj.set(:metric_set_with_sensitive_block, 99, sensitive_tags) do
+ label_keys label_keys
+ end
+ end
+ end
+ end
+
+ describe '#observe' do
+ let(:prometheus_metric) { instance_double(Prometheus::Client::Histogram, observe: nil, base_labels: {}) }
+
+ it 'adds a metric' do
+ expect(::Gitlab::Metrics).to receive(:histogram).with(
+ :meow_observe, 'Meow observe histogram', hash_including(*described_class::BASE_LABEL_KEYS), kind_of(Array)
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:observe).with(labels, 2.0)
+
+ transaction_obj.observe(:meow_observe, 2.0)
+ end
+
+ context 'with block' do
+ it 'overrides docstring' do
+ expect(::Gitlab::Metrics).to receive(:histogram).with(
+ :block_docstring_observe, 'test', hash_including(*described_class::BASE_LABEL_KEYS), kind_of(Array)
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:observe).with(labels, 2.0)
+
+ transaction_obj.observe(:block_docstring_observe, 2.0) do
+ docstring 'test'
+ end
+ end
+
+ it 'overrides labels' do
+ expect(::Gitlab::Metrics).to receive(:histogram).with(
+ :block_labels_observe, 'Block labels observe histogram', hash_including(*described_class::BASE_LABEL_KEYS), kind_of(Array)
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:observe).with(labels.merge(sane: 'yes'), 2.0)
+
+ transaction_obj.observe(:block_labels_observe, 2.0, sane: 'yes') do
+ label_keys %i(sane)
+ end
+ end
+
+ it 'filters sensitive tags' do
+ labels_keys = sensitive_tags.keys
+
+ expect(::Gitlab::Metrics).to receive(:histogram).with(
+ :metric_observe_with_sensitive_block,
+ 'Metric observe with sensitive block histogram',
+ hash_excluding(labels_keys),
+ kind_of(Array)
+ ).and_return(prometheus_metric)
+ expect(prometheus_metric).to receive(:observe).with(labels, 2.0)
+
+ transaction_obj.observe(:metric_observe_with_sensitive_block, 2.0, sensitive_tags) do
+ label_keys label_keys
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb b/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb
index f928fb1eb43..d823e7ac221 100644
--- a/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb
+++ b/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb
@@ -12,6 +12,7 @@ RSpec.shared_examples 'StageEventModel' do
project_id: 4,
author_id: 5,
milestone_id: 6,
+ state_id: 1,
start_event_timestamp: time,
end_event_timestamp: time
},
@@ -22,6 +23,7 @@ RSpec.shared_examples 'StageEventModel' do
project_id: 11,
author_id: 12,
milestone_id: 13,
+ state_id: 1,
start_event_timestamp: time,
end_event_timestamp: time
}
@@ -34,8 +36,9 @@ RSpec.shared_examples 'StageEventModel' do
described_class.issuable_id_column,
:group_id,
:project_id,
- :milestone_id,
:author_id,
+ :milestone_id,
+ :state_id,
:start_event_timestamp,
:end_event_timestamp
]
@@ -59,10 +62,120 @@ RSpec.shared_examples 'StageEventModel' do
upsert_data
output_data = described_class.all.map do |record|
- column_order.map { |column| record[column] }
+ column_order.map do |column|
+ if column == :state_id
+ described_class.states[record[column]]
+ else
+ record[column]
+ end
+ end
end.sort
expect(input_data.map(&:values).sort).to eq(output_data)
end
end
+
+ describe 'scopes' do
+ def attributes(array)
+ array.map(&:attributes)
+ end
+
+ RSpec::Matchers.define :match_attributes do |expected|
+ match do |actual|
+ actual.map(&:attributes) == expected.map(&:attributes)
+ end
+ end
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:user) }
+ let_it_be(:milestone) { create(:milestone) }
+ let_it_be(:issuable_with_assignee) { create(issuable_factory, assignees: [user])}
+
+ let_it_be(:record) { create(stage_event_factory, start_event_timestamp: 3.years.ago.to_date, end_event_timestamp: 2.years.ago.to_date) }
+ let_it_be(:record_with_author) { create(stage_event_factory, author_id: user.id) }
+ let_it_be(:record_with_project) { create(stage_event_factory, project_id: project.id) }
+ let_it_be(:record_with_group) { create(stage_event_factory, group_id: project.namespace_id) }
+ let_it_be(:record_with_assigned_issuable) { create(stage_event_factory, described_class.issuable_id_column => issuable_with_assignee.id) }
+ let_it_be(:record_with_milestone) { create(stage_event_factory, milestone_id: milestone.id) }
+
+ it 'filters by stage_event_hash_id' do
+ records = described_class.by_stage_event_hash_id(record.stage_event_hash_id)
+
+ expect(records).to match_attributes([record])
+ end
+
+ it 'filters by project_id' do
+ records = described_class.by_project_id(project.id)
+
+ expect(records).to match_attributes([record_with_project])
+ end
+
+ it 'filters by group_id' do
+ records = described_class.by_group_id(project.namespace_id)
+
+ expect(records).to match_attributes([record_with_group])
+ end
+
+ it 'filters by author_id' do
+ records = described_class.authored(user)
+
+ expect(records).to match_attributes([record_with_author])
+ end
+
+ it 'filters by assignee' do
+ records = described_class.assigned_to(user)
+
+ expect(records).to match_attributes([record_with_assigned_issuable])
+ end
+
+ it 'filters by milestone_id' do
+ records = described_class.with_milestone_id(milestone.id)
+
+ expect(records).to match_attributes([record_with_milestone])
+ end
+
+ describe 'start_event_timestamp filtering' do
+ it 'when range is given' do
+ records = described_class
+ .start_event_timestamp_after(4.years.ago)
+ .start_event_timestamp_before(2.years.ago)
+
+ expect(records).to match_attributes([record])
+ end
+
+ it 'when specifying upper bound' do
+ records = described_class.start_event_timestamp_before(2.years.ago)
+
+ expect(attributes(records)).to include(attributes([record]).first)
+ end
+
+ it 'when specifying the lower bound' do
+ records = described_class.start_event_timestamp_after(4.years.ago)
+
+ expect(attributes(records)).to include(attributes([record]).first)
+ end
+ end
+
+ describe 'end_event_timestamp filtering' do
+ it 'when range is given' do
+ records = described_class
+ .end_event_timestamp_after(3.years.ago)
+ .end_event_timestamp_before(1.year.ago)
+
+ expect(records).to match_attributes([record])
+ end
+
+ it 'when specifying upper bound' do
+ records = described_class.end_event_timestamp_before(1.year.ago)
+
+ expect(attributes(records)).to include(attributes([record]).first)
+ end
+
+ it 'when specifying the lower bound' do
+ records = described_class.end_event_timestamp_after(3.years.ago)
+
+ expect(attributes(records)).to include(attributes([record]).first)
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb b/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
index a4e0d6c871e..2d08de297a3 100644
--- a/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
@@ -11,18 +11,18 @@ RSpec.shared_examples 'ttl_expirable' do
it { is_expected.to validate_presence_of(:status) }
end
- describe '.updated_before' do
+ describe '.read_before' do
# rubocop:disable Rails/SaveBang
let_it_be_with_reload(:item1) { create(class_symbol) }
let_it_be(:item2) { create(class_symbol) }
# rubocop:enable Rails/SaveBang
before do
- item1.update_column(:updated_at, 1.month.ago)
+ item1.update_column(:read_at, 1.month.ago)
end
it 'returns items with created at older than the supplied number of days' do
- expect(described_class.updated_before(10)).to contain_exactly(item1)
+ expect(described_class.read_before(10)).to contain_exactly(item1)
end
end
@@ -48,4 +48,13 @@ RSpec.shared_examples 'ttl_expirable' do
expect(described_class.lock_next_by(:created_at)).to contain_exactly(item3)
end
end
+
+ describe '#read', :freeze_time do
+ let_it_be(:old_read_at) { 1.day.ago }
+ let_it_be(:item1) { create(class_symbol, read_at: old_read_at) }
+
+ it 'updates read_at' do
+ expect { item1.read! }.to change { item1.reload.read_at }
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index 56c202cb228..a2909c66e22 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -299,6 +299,22 @@ RSpec.shared_examples_for "member creation" do
end
end
end
+
+ context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
+ before do
+ stub_experiments(invite_members_for_task: true)
+ end
+
+ it 'creates a member_task with the correct attributes', :aggregate_failures do
+ task_project = source.is_a?(Group) ? create(:project, group: source) : source
+ described_class.new(source, user, :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id).execute
+
+ member = source.members.last
+
+ expect(member.tasks_to_be_done).to match_array([:ci, :code])
+ expect(member.member_task.project).to eq(task_project)
+ end
+ end
end
end
@@ -379,5 +395,20 @@ RSpec.shared_examples_for "bulk member creation" do
expect(members).to all(be_persisted)
end
end
+
+ context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
+ before do
+ stub_experiments(invite_members_for_task: true)
+ end
+
+ it 'creates a member_task with the correct attributes', :aggregate_failures do
+ task_project = source.is_a?(Group) ? create(:project, group: source) : source
+ members = described_class.add_users(source, [user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: task_project.id)
+ member = members.last
+
+ expect(member.tasks_to_be_done).to match_array([:ci, :code])
+ expect(member.member_task.project).to eq(task_project)
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/models/reviewer_state_shared_examples.rb b/spec/support/shared_examples/models/reviewer_state_shared_examples.rb
new file mode 100644
index 00000000000..f1392768b06
--- /dev/null
+++ b/spec/support/shared_examples/models/reviewer_state_shared_examples.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'having reviewer state' do
+ describe 'mr_attention_requests feature flag is disabled' do
+ before do
+ stub_feature_flags(mr_attention_requests: false)
+ end
+
+ it { is_expected.to have_attributes(state: 'unreviewed') }
+ end
+
+ describe 'mr_attention_requests feature flag is enabled' do
+ it { is_expected.to have_attributes(state: 'attention_requested') }
+ end
+end
diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb
index d126b242fb0..ac6a843663f 100644
--- a/spec/support/shared_examples/namespaces/traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_examples.rb
@@ -22,6 +22,8 @@ RSpec.shared_examples 'namespace traversal' do
let_it_be(:deep_nested_group) { create(:group, parent: nested_group) }
let_it_be(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
let_it_be(:groups) { [group, nested_group, deep_nested_group, very_deep_nested_group] }
+ let_it_be(:project) { create(:project, group: nested_group) }
+ let_it_be(:project_namespace) { project.project_namespace }
describe '#root_ancestor' do
it 'returns the correct root ancestor' do
@@ -65,6 +67,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.ancestors).to contain_exactly(group, nested_group)
expect(nested_group.ancestors).to contain_exactly(group)
expect(group.ancestors).to eq([])
+ expect(project_namespace.ancestors).to be_empty
end
context 'with asc hierarchy_order' do
@@ -73,6 +76,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.ancestors(hierarchy_order: :asc)).to eq [nested_group, group]
expect(nested_group.ancestors(hierarchy_order: :asc)).to eq [group]
expect(group.ancestors(hierarchy_order: :asc)).to eq([])
+ expect(project_namespace.ancestors(hierarchy_order: :asc)).to be_empty
end
end
@@ -82,6 +86,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.ancestors(hierarchy_order: :desc)).to eq [group, nested_group]
expect(nested_group.ancestors(hierarchy_order: :desc)).to eq [group]
expect(group.ancestors(hierarchy_order: :desc)).to eq([])
+ expect(project_namespace.ancestors(hierarchy_order: :desc)).to be_empty
end
end
@@ -98,6 +103,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.ancestor_ids).to contain_exactly(group.id, nested_group.id)
expect(nested_group.ancestor_ids).to contain_exactly(group.id)
expect(group.ancestor_ids).to be_empty
+ expect(project_namespace.ancestor_ids).to be_empty
end
context 'with asc hierarchy_order' do
@@ -106,6 +112,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.ancestor_ids(hierarchy_order: :asc)).to eq [nested_group.id, group.id]
expect(nested_group.ancestor_ids(hierarchy_order: :asc)).to eq [group.id]
expect(group.ancestor_ids(hierarchy_order: :asc)).to eq([])
+ expect(project_namespace.ancestor_ids(hierarchy_order: :asc)).to eq([])
end
end
@@ -115,6 +122,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.ancestor_ids(hierarchy_order: :desc)).to eq [group.id, nested_group.id]
expect(nested_group.ancestor_ids(hierarchy_order: :desc)).to eq [group.id]
expect(group.ancestor_ids(hierarchy_order: :desc)).to eq([])
+ expect(project_namespace.ancestor_ids(hierarchy_order: :desc)).to eq([])
end
end
@@ -131,6 +139,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.self_and_ancestors).to contain_exactly(group, nested_group, deep_nested_group)
expect(nested_group.self_and_ancestors).to contain_exactly(group, nested_group)
expect(group.self_and_ancestors).to contain_exactly(group)
+ expect(project_namespace.self_and_ancestors).to contain_exactly(project_namespace)
end
context 'with asc hierarchy_order' do
@@ -139,6 +148,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.self_and_ancestors(hierarchy_order: :asc)).to eq [deep_nested_group, nested_group, group]
expect(nested_group.self_and_ancestors(hierarchy_order: :asc)).to eq [nested_group, group]
expect(group.self_and_ancestors(hierarchy_order: :asc)).to eq([group])
+ expect(project_namespace.self_and_ancestors(hierarchy_order: :asc)).to eq([project_namespace])
end
end
@@ -148,6 +158,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.self_and_ancestors(hierarchy_order: :desc)).to eq [group, nested_group, deep_nested_group]
expect(nested_group.self_and_ancestors(hierarchy_order: :desc)).to eq [group, nested_group]
expect(group.self_and_ancestors(hierarchy_order: :desc)).to eq([group])
+ expect(project_namespace.self_and_ancestors(hierarchy_order: :desc)).to eq([project_namespace])
end
end
@@ -164,6 +175,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.self_and_ancestor_ids).to contain_exactly(group.id, nested_group.id, deep_nested_group.id)
expect(nested_group.self_and_ancestor_ids).to contain_exactly(group.id, nested_group.id)
expect(group.self_and_ancestor_ids).to contain_exactly(group.id)
+ expect(project_namespace.self_and_ancestor_ids).to contain_exactly(project_namespace.id)
end
context 'with asc hierarchy_order' do
@@ -172,6 +184,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.self_and_ancestor_ids(hierarchy_order: :asc)).to eq [deep_nested_group.id, nested_group.id, group.id]
expect(nested_group.self_and_ancestor_ids(hierarchy_order: :asc)).to eq [nested_group.id, group.id]
expect(group.self_and_ancestor_ids(hierarchy_order: :asc)).to eq([group.id])
+ expect(project_namespace.self_and_ancestor_ids(hierarchy_order: :asc)).to eq([project_namespace.id])
end
end
@@ -181,6 +194,7 @@ RSpec.shared_examples 'namespace traversal' do
expect(deep_nested_group.self_and_ancestor_ids(hierarchy_order: :desc)).to eq [group.id, nested_group.id, deep_nested_group.id]
expect(nested_group.self_and_ancestor_ids(hierarchy_order: :desc)).to eq [group.id, nested_group.id]
expect(group.self_and_ancestor_ids(hierarchy_order: :desc)).to eq([group.id])
+ expect(project_namespace.self_and_ancestor_ids(hierarchy_order: :desc)).to eq([project_namespace.id])
end
end
@@ -205,6 +219,10 @@ RSpec.shared_examples 'namespace traversal' do
describe '#recursive_descendants' do
it_behaves_like 'recursive version', :descendants
end
+
+ it 'does not include project namespaces' do
+ expect(group.descendants.to_a).not_to include(project_namespace)
+ end
end
describe '#self_and_descendants' do
@@ -223,6 +241,10 @@ RSpec.shared_examples 'namespace traversal' do
it_behaves_like 'recursive version', :self_and_descendants
end
+
+ it 'does not include project namespaces' do
+ expect(group.self_and_descendants.to_a).not_to include(project_namespace)
+ end
end
describe '#self_and_descendant_ids' do
diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
index 74b1bacc560..4c09c1c2a3b 100644
--- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb
@@ -25,12 +25,6 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { is_expected.to contain_exactly(group_1.id, group_2.id) }
end
- describe '.without_sti_condition' do
- subject { described_class.without_sti_condition }
-
- it { expect(subject.where_values_hash).not_to have_key(:type) }
- end
-
describe '.order_by_depth' do
subject { described_class.where(id: [group_1, nested_group_1, deep_nested_group_1]).order_by_depth(direction) }
@@ -55,6 +49,53 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { is_expected.to eq described_class.column_names }
end
+ shared_examples '.roots' do
+ context 'with only sub-groups' do
+ subject { described_class.where(id: [deep_nested_group_1, nested_group_1, deep_nested_group_2]).roots }
+
+ it { is_expected.to contain_exactly(group_1, group_2) }
+ end
+
+ context 'with only root groups' do
+ subject { described_class.where(id: [group_1, group_2]).roots }
+
+ it { is_expected.to contain_exactly(group_1, group_2) }
+ end
+
+ context 'with all groups' do
+ subject { described_class.where(id: groups).roots }
+
+ it { is_expected.to contain_exactly(group_1, group_2) }
+ end
+ end
+
+ describe '.roots' do
+ context "use_traversal_ids_roots feature flag is true" do
+ before do
+ stub_feature_flags(use_traversal_ids: true)
+ stub_feature_flags(use_traversal_ids_roots: true)
+ end
+
+ it_behaves_like '.roots'
+
+ it 'not make recursive queries' do
+ expect { described_class.where(id: [nested_group_1]).roots.load }.not_to make_queries_matching(/WITH RECURSIVE/)
+ end
+ end
+
+ context "use_traversal_ids_roots feature flag is false" do
+ before do
+ stub_feature_flags(use_traversal_ids_roots: false)
+ end
+
+ it_behaves_like '.roots'
+
+ it 'make recursive queries' do
+ expect { described_class.where(id: [nested_group_1]).roots.load }.to make_queries_matching(/WITH RECURSIVE/)
+ end
+ end
+ end
+
shared_examples '.self_and_ancestors' do
subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_ancestors }
@@ -156,7 +197,7 @@ RSpec.shared_examples 'namespace traversal scopes' do
end
end
- describe '.self_and_descendants' do
+ shared_examples '.self_and_descendants' do
subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_descendants }
it { is_expected.to contain_exactly(nested_group_1, deep_nested_group_1, nested_group_2, deep_nested_group_2) }
@@ -174,7 +215,19 @@ RSpec.shared_examples 'namespace traversal scopes' do
end
end
- describe '.self_and_descendant_ids' do
+ describe '.self_and_descendants' do
+ include_examples '.self_and_descendants'
+
+ context 'with traversal_ids_btree feature flag disabled' do
+ before do
+ stub_feature_flags(traversal_ids_btree: false)
+ end
+
+ include_examples '.self_and_descendants'
+ end
+ end
+
+ shared_examples '.self_and_descendant_ids' do
subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_descendant_ids.pluck(:id) }
it { is_expected.to contain_exactly(nested_group_1.id, deep_nested_group_1.id, nested_group_2.id, deep_nested_group_2.id) }
@@ -190,4 +243,16 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { is_expected.to contain_exactly(deep_nested_group_1.id, deep_nested_group_2.id) }
end
end
+
+ describe '.self_and_descendant_ids' do
+ include_examples '.self_and_descendant_ids'
+
+ context 'with traversal_ids_btree feature flag disabled' do
+ before do
+ stub_feature_flags(traversal_ids_btree: false)
+ end
+
+ include_examples '.self_and_descendant_ids'
+ end
+ end
end
diff --git a/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..5167d27f8b9
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'promote_to_incident quick action' do
+ describe '/promote_to_incident' do
+ context 'when issue can be promoted' do
+ it 'promotes issue to incident' do
+ add_note('/promote_to_incident')
+
+ expect(issue.reload.issue_type).to eq('incident')
+ expect(page).to have_content('Issue has been promoted to incident')
+ end
+ end
+
+ context 'when issue is already an incident' do
+ let(:issue) { create(:incident, project: project) }
+
+ it 'does not promote the issue' do
+ add_note('/promote_to_incident')
+
+ expect(page).to have_content('Could not apply promote_to_incident command')
+ end
+ end
+
+ context 'when user does not have permissions' do
+ let(:guest) { create(:user) }
+
+ before do
+ sign_in(guest)
+ visit project_issue_path(project, issue)
+ wait_for_all_requests
+ end
+
+ it 'does not promote the issue' do
+ add_note('/promote_to_incident')
+
+ expect(page).to have_content('Could not apply promote_to_incident command')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb
new file mode 100644
index 00000000000..e0225070986
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/debian_common_shared_examples.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'rejects Debian access with unknown container id' do |anonymous_status, auth_method|
+ context 'with an unknown container' do
+ let(:container) { double(id: non_existing_record_id) }
+
+ context 'as anonymous' do
+ it_behaves_like 'Debian packages GET request', anonymous_status, nil
+ end
+
+ context 'as authenticated user' do
+ include_context 'Debian repository auth headers', :not_a_member, auth_method do
+ it_behaves_like 'Debian packages GET request', :not_found, nil
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb
new file mode 100644
index 00000000000..5cd63c33936
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/debian_distributions_shared_examples.rb
@@ -0,0 +1,192 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'Debian distributions GET request' do |status, body = nil|
+ and_body = body.nil? ? '' : ' and expected body'
+
+ it "returns #{status}#{and_body}" do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+
+ unless body.nil?
+ expect(response.body).to match(body)
+ end
+ end
+end
+
+RSpec.shared_examples 'Debian distributions PUT request' do |status, body|
+ and_body = body.nil? ? '' : ' and expected body'
+
+ if status == :success
+ it 'updates distribution', :aggregate_failures do
+ expect(::Packages::Debian::UpdateDistributionService).to receive(:new).with(distribution, api_params.except(:codename)).and_call_original
+
+ expect { subject }
+ .to not_change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count }
+ .and not_change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count }
+ .and not_change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count }
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response.media_type).to eq('application/json')
+
+ unless body.nil?
+ expect(response.body).to match(body)
+ end
+ end
+ else
+ it "returns #{status}#{and_body}", :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+
+ unless body.nil?
+ expect(response.body).to match(body)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'Debian distributions DELETE request' do |status, body|
+ and_body = body.nil? ? '' : ' and expected body'
+
+ if status == :success
+ it 'updates distribution', :aggregate_failures do
+ expect { subject }
+ .to change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count }.by(-1)
+ .and change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count }.by(-1)
+ .and change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count }.by(-2)
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response.media_type).to eq('application/json')
+
+ unless body.nil?
+ expect(response.body).to match(body)
+ end
+ end
+ else
+ it "returns #{status}#{and_body}", :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+
+ unless body.nil?
+ expect(response.body).to match(body)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'Debian distributions POST request' do |status, body|
+ and_body = body.nil? ? '' : ' and expected body'
+
+ if status == :created
+ it 'creates distribution', :aggregate_failures do
+ expect(::Packages::Debian::CreateDistributionService).to receive(:new).with(container, user, api_params).and_call_original
+
+ expect { subject }
+ .to change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count }.by(1)
+ .and change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count }.by(1)
+ .and change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count }.by(2)
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response.media_type).to eq('application/json')
+
+ unless body.nil?
+ expect(response.body).to match(body)
+ end
+ end
+ else
+ it "returns #{status}#{and_body}", :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+
+ unless body.nil?
+ expect(response.body).to match(body)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'Debian distributions read endpoint' do |desired_behavior, success_status, success_body|
+ context 'with valid container' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:visibility_level, :user_type, :auth_method, :expected_status, :expected_body) do
+ :public | :guest | :private_token | success_status | success_body
+ :public | :not_a_member | :private_token | success_status | success_body
+ :public | :anonymous | :private_token | success_status | success_body
+ :public | :invalid_token | :private_token | :unauthorized | nil
+ :private | :developer | :private_token | success_status | success_body
+ :private | :developer | :basic | :not_found | nil
+ :private | :guest | :private_token | :forbidden | nil
+ :private | :not_a_member | :private_token | :not_found | nil
+ :private | :anonymous | :private_token | :not_found | nil
+ :private | :invalid_token | :private_token | :unauthorized | nil
+ end
+
+ with_them do
+ include_context 'Debian repository access', params[:visibility_level], params[:user_type], params[:auth_method] do
+ it_behaves_like "Debian distributions #{desired_behavior} request", params[:expected_status], params[:expected_body]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Debian access with unknown container id', :not_found, :private_token
+end
+
+RSpec.shared_examples 'Debian distributions write endpoint' do |desired_behavior, success_status, success_body|
+ context 'with valid container' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:visibility_level, :user_type, :auth_method, :expected_status, :expected_body) do
+ :public | :developer | :private_token | success_status | success_body
+ :public | :developer | :basic | :unauthorized | nil
+ :public | :guest | :private_token | :forbidden | nil
+ :public | :not_a_member | :private_token | :forbidden | nil
+ :public | :anonymous | :private_token | :unauthorized | nil
+ :public | :invalid_token | :private_token | :unauthorized | nil
+ :private | :developer | :private_token | success_status | success_body
+ :private | :guest | :private_token | :forbidden | nil
+ :private | :not_a_member | :private_token | :not_found | nil
+ :private | :anonymous | :private_token | :not_found | nil
+ :private | :invalid_token | :private_token | :unauthorized | nil
+ end
+
+ with_them do
+ include_context 'Debian repository access', params[:visibility_level], params[:user_type], params[:auth_method] do
+ it_behaves_like "Debian distributions #{desired_behavior} request", params[:expected_status], params[:expected_body]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Debian access with unknown container id', :not_found, :private_token
+end
+
+RSpec.shared_examples 'Debian distributions maintainer write endpoint' do |desired_behavior, success_status, success_body|
+ context 'with valid container' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:visibility_level, :user_type, :auth_method, :expected_status, :expected_body) do
+ :public | :maintainer | :private_token | success_status | success_body
+ :public | :maintainer | :basic | :unauthorized | nil
+ :public | :developer | :private_token | :forbidden | nil
+ :public | :not_a_member | :private_token | :forbidden | nil
+ :public | :anonymous | :private_token | :unauthorized | nil
+ :public | :invalid_token | :private_token | :unauthorized | nil
+ :private | :maintainer | :private_token | success_status | success_body
+ :private | :developer | :private_token | :forbidden | nil
+ :private | :not_a_member | :private_token | :not_found | nil
+ :private | :anonymous | :private_token | :not_found | nil
+ :private | :invalid_token | :private_token | :unauthorized | nil
+ end
+
+ with_them do
+ include_context 'Debian repository access', params[:visibility_level], params[:user_type], params[:auth_method] do
+ it_behaves_like "Debian distributions #{desired_behavior} request", params[:expected_status], params[:expected_body]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Debian access with unknown container id', :not_found, :private_token
+end
diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
index a3ed74085fb..2fd5e6a5f91 100644
--- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
@@ -1,127 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_context 'Debian repository shared context' do |container_type, can_freeze|
- include_context 'workhorse headers'
-
- before do
- stub_feature_flags(debian_packages: true, debian_group_packages: true)
- end
-
- let_it_be(:private_container, freeze: can_freeze) { create(container_type, :private) }
- let_it_be(:public_container, freeze: can_freeze) { create(container_type, :public) }
- let_it_be(:user, freeze: true) { create(:user) }
- let_it_be(:personal_access_token, freeze: true) { create(:personal_access_token, user: user) }
-
- let_it_be(:private_distribution, freeze: true) { create("debian_#{container_type}_distribution", :with_file, container: private_container, codename: 'existing-codename') }
- let_it_be(:private_component, freeze: true) { create("debian_#{container_type}_component", distribution: private_distribution, name: 'existing-component') }
- let_it_be(:private_architecture_all, freeze: true) { create("debian_#{container_type}_architecture", distribution: private_distribution, name: 'all') }
- let_it_be(:private_architecture, freeze: true) { create("debian_#{container_type}_architecture", distribution: private_distribution, name: 'existing-arch') }
- let_it_be(:private_component_file) { create("debian_#{container_type}_component_file", component: private_component, architecture: private_architecture) }
-
- let_it_be(:public_distribution, freeze: true) { create("debian_#{container_type}_distribution", :with_file, container: public_container, codename: 'existing-codename') }
- let_it_be(:public_component, freeze: true) { create("debian_#{container_type}_component", distribution: public_distribution, name: 'existing-component') }
- let_it_be(:public_architecture_all, freeze: true) { create("debian_#{container_type}_architecture", distribution: public_distribution, name: 'all') }
- let_it_be(:public_architecture, freeze: true) { create("debian_#{container_type}_architecture", distribution: public_distribution, name: 'existing-arch') }
- let_it_be(:public_component_file) { create("debian_#{container_type}_component_file", component: public_component, architecture: public_architecture) }
-
- if container_type == :group
- let_it_be(:private_project) { create(:project, :private, group: private_container) }
- let_it_be(:public_project) { create(:project, :public, group: public_container) }
- let_it_be(:private_project_distribution) { create(:debian_project_distribution, container: private_project, codename: 'existing-codename') }
- let_it_be(:public_project_distribution) { create(:debian_project_distribution, container: public_project, codename: 'existing-codename') }
-
- let(:project) { { private: private_project, public: public_project }[visibility_level] }
- else
- let_it_be(:private_project) { private_container }
- let_it_be(:public_project) { public_container }
- let_it_be(:private_project_distribution) { private_distribution }
- let_it_be(:public_project_distribution) { public_distribution }
- end
-
- let_it_be(:private_package) { create(:debian_package, project: private_project, published_in: private_project_distribution) }
- let_it_be(:public_package) { create(:debian_package, project: public_project, published_in: public_project_distribution) }
-
- let(:visibility_level) { :public }
-
- let(:distribution) { { private: private_distribution, public: public_distribution }[visibility_level] }
- let(:architecture) { { private: private_architecture, public: public_architecture }[visibility_level] }
- let(:component) { { private: private_component, public: public_component }[visibility_level] }
- let(:component_file) { { private: private_component_file, public: public_component_file }[visibility_level] }
- let(:package) { { private: private_package, public: public_package }[visibility_level] }
- let(:letter) { package.name[0..2] == 'lib' ? package.name[0..3] : package.name[0] }
-
- let(:method) { :get }
-
- let(:workhorse_params) do
- if method == :put
- file_upload = fixture_file_upload("spec/fixtures/packages/debian/#{file_name}")
- { file: file_upload }
- else
- {}
- end
- end
-
- let(:api_params) { workhorse_params }
-
- let(:auth_headers) { {} }
- let(:wh_headers) do
- if method == :put
- workhorse_headers
- else
- {}
- end
- end
-
- let(:headers) { auth_headers.merge(wh_headers) }
-
- let(:send_rewritten_field) { true }
-
- subject do
- if method == :put
- workhorse_finalize(
- api(url),
- method: method,
- file_key: :file,
- params: api_params,
- headers: headers,
- send_rewritten_field: send_rewritten_field
- )
- else
- send method, api(url), headers: headers, params: api_params
- end
- end
-end
-
-RSpec.shared_context 'with file_name' do |file_name|
- let(:file_name) { file_name }
-end
-
-RSpec.shared_context 'Debian repository auth headers' do |user_role, user_token, auth_method = :token|
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
-
- let(:auth_headers) do
- if user_role == :anonymous
- {}
- elsif auth_method == :token
- { 'Private-Token' => token }
- else
- basic_auth_header(user.username, token)
- end
- end
-end
-
-RSpec.shared_context 'Debian repository access' do |visibility_level, user_role, add_member, user_token, auth_method|
- include_context 'Debian repository auth headers', user_role, user_token, auth_method do
- let(:containers) { { private: private_container, public: public_container } }
- let(:container) { containers[visibility_level] }
-
- before do
- container.send("add_#{user_role}", user) if add_member && user_role != :anonymous
- end
- end
-end
-
-RSpec.shared_examples 'Debian repository GET request' do |status, body = nil|
+RSpec.shared_examples 'Debian packages GET request' do |status, body = nil|
and_body = body.nil? ? '' : ' and expected body'
it "returns #{status}#{and_body}" do
@@ -135,7 +14,7 @@ RSpec.shared_examples 'Debian repository GET request' do |status, body = nil|
end
end
-RSpec.shared_examples 'Debian repository upload request' do |status, body = nil|
+RSpec.shared_examples 'Debian packages upload request' do |status, body = nil|
and_body = body.nil? ? '' : ' and expected body'
if status == :created
@@ -175,7 +54,7 @@ RSpec.shared_examples 'Debian repository upload request' do |status, body = nil|
end
end
-RSpec.shared_examples 'Debian repository upload authorize request' do |status, body = nil|
+RSpec.shared_examples 'Debian packages upload authorize request' do |status, body = nil|
and_body = body.nil? ? '' : ' and expected body'
if status == :created
@@ -221,237 +100,57 @@ RSpec.shared_examples 'Debian repository upload authorize request' do |status, b
end
end
-RSpec.shared_examples 'Debian repository POST distribution request' do |status, body|
- and_body = body.nil? ? '' : ' and expected body'
-
- if status == :created
- it 'creates distribution', :aggregate_failures do
- expect(::Packages::Debian::CreateDistributionService).to receive(:new).with(container, user, api_params).and_call_original
-
- expect { subject }
- .to change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count }.by(1)
- .and change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count }.by(1)
- .and change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count }.by(2)
-
- expect(response).to have_gitlab_http_status(status)
- expect(response.media_type).to eq('application/json')
-
- unless body.nil?
- expect(response.body).to match(body)
- end
- end
- else
- it "returns #{status}#{and_body}", :aggregate_failures do
- subject
-
- expect(response).to have_gitlab_http_status(status)
-
- unless body.nil?
- expect(response.body).to match(body)
- end
- end
- end
-end
-
-RSpec.shared_examples 'Debian repository PUT distribution request' do |status, body|
- and_body = body.nil? ? '' : ' and expected body'
-
- if status == :success
- it 'updates distribution', :aggregate_failures do
- expect(::Packages::Debian::UpdateDistributionService).to receive(:new).with(distribution, api_params.except(:codename)).and_call_original
-
- expect { subject }
- .to not_change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count }
- .and not_change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count }
- .and not_change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count }
-
- expect(response).to have_gitlab_http_status(status)
- expect(response.media_type).to eq('application/json')
-
- unless body.nil?
- expect(response.body).to match(body)
- end
- end
- else
- it "returns #{status}#{and_body}", :aggregate_failures do
- subject
-
- expect(response).to have_gitlab_http_status(status)
-
- unless body.nil?
- expect(response.body).to match(body)
- end
- end
- end
-end
-
-RSpec.shared_examples 'Debian repository DELETE distribution request' do |status, body|
- and_body = body.nil? ? '' : ' and expected body'
-
- if status == :success
- it 'updates distribution', :aggregate_failures do
- expect { subject }
- .to change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count }.by(-1)
- .and change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count }.by(-1)
- .and change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count }.by(-2)
-
- expect(response).to have_gitlab_http_status(status)
- expect(response.media_type).to eq('application/json')
-
- unless body.nil?
- expect(response.body).to match(body)
- end
- end
- else
- it "returns #{status}#{and_body}", :aggregate_failures do
- subject
-
- expect(response).to have_gitlab_http_status(status)
-
- unless body.nil?
- expect(response.body).to match(body)
- end
- end
- end
-end
-
-RSpec.shared_examples 'rejects Debian access with unknown container id' do |hidden_status|
- context 'with an unknown container' do
- let(:container) { double(id: non_existing_record_id) }
-
- context 'as anonymous' do
- it_behaves_like 'Debian repository GET request', hidden_status, nil
- end
-
- context 'as authenticated user' do
- subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
-
- it_behaves_like 'Debian repository GET request', :not_found, nil
- end
- end
-end
-
-RSpec.shared_examples 'Debian repository read endpoint' do |desired_behavior, success_status, success_body, authenticate_non_public: true|
- hidden_status = if authenticate_non_public
- :unauthorized
- else
- :not_found
- end
-
- context 'with valid container' do
- using RSpec::Parameterized::TableSyntax
-
- where(:visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do
- :public | :developer | true | true | success_status | success_body
- :public | :guest | true | true | success_status | success_body
- :public | :developer | true | false | :unauthorized | nil
- :public | :guest | true | false | :unauthorized | nil
- :public | :developer | false | true | success_status | success_body
- :public | :guest | false | true | success_status | success_body
- :public | :developer | false | false | :unauthorized | nil
- :public | :guest | false | false | :unauthorized | nil
- :public | :anonymous | false | true | success_status | success_body
- :private | :developer | true | true | success_status | success_body
- :private | :guest | true | true | :forbidden | nil
- :private | :developer | true | false | :unauthorized | nil
- :private | :guest | true | false | :unauthorized | nil
- :private | :developer | false | true | :not_found | nil
- :private | :guest | false | true | :not_found | nil
- :private | :developer | false | false | :unauthorized | nil
- :private | :guest | false | false | :unauthorized | nil
- :private | :anonymous | false | true | hidden_status | nil
- end
-
- with_them do
- include_context 'Debian repository access', params[:visibility_level], params[:user_role], params[:member], params[:user_token], :basic do
- it_behaves_like "Debian repository #{desired_behavior}", params[:expected_status], params[:expected_body]
- end
- end
- end
-
- it_behaves_like 'rejects Debian access with unknown container id', hidden_status
-end
-
-RSpec.shared_examples 'Debian repository write endpoint' do |desired_behavior, success_status, success_body, authenticate_non_public: true|
- hidden_status = if authenticate_non_public
- :unauthorized
- else
- :not_found
- end
-
+RSpec.shared_examples 'Debian packages read endpoint' do |desired_behavior, success_status, success_body|
context 'with valid container' do
using RSpec::Parameterized::TableSyntax
- where(:visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do
- :public | :developer | true | true | success_status | success_body
- :public | :guest | true | true | :forbidden | nil
- :public | :developer | true | false | :unauthorized | nil
- :public | :guest | true | false | :unauthorized | nil
- :public | :developer | false | true | :forbidden | nil
- :public | :guest | false | true | :forbidden | nil
- :public | :developer | false | false | :unauthorized | nil
- :public | :guest | false | false | :unauthorized | nil
- :public | :anonymous | false | true | :unauthorized | nil
- :private | :developer | true | true | success_status | success_body
- :private | :guest | true | true | :forbidden | nil
- :private | :developer | true | false | :unauthorized | nil
- :private | :guest | true | false | :unauthorized | nil
- :private | :developer | false | true | :not_found | nil
- :private | :guest | false | true | :not_found | nil
- :private | :developer | false | false | :unauthorized | nil
- :private | :guest | false | false | :unauthorized | nil
- :private | :anonymous | false | true | hidden_status | nil
+ where(:visibility_level, :user_type, :auth_method, :expected_status, :expected_body) do
+ :public | :guest | :basic | success_status | success_body
+ :public | :not_a_member | :basic | success_status | success_body
+ :public | :anonymous | :basic | success_status | success_body
+ :public | :invalid_token | :basic | :unauthorized | nil
+ :private | :developer | :basic | success_status | success_body
+ :private | :developer | :private_token | :unauthorized | nil
+ :private | :guest | :basic | :forbidden | nil
+ :private | :not_a_member | :basic | :not_found | nil
+ :private | :anonymous | :basic | :unauthorized | nil
+ :private | :invalid_token | :basic | :unauthorized | nil
end
with_them do
- include_context 'Debian repository access', params[:visibility_level], params[:user_role], params[:member], params[:user_token], :basic do
- it_behaves_like "Debian repository #{desired_behavior}", params[:expected_status], params[:expected_body]
+ include_context 'Debian repository access', params[:visibility_level], params[:user_type], params[:auth_method] do
+ it_behaves_like "Debian packages #{desired_behavior} request", params[:expected_status], params[:expected_body]
end
end
end
- it_behaves_like 'rejects Debian access with unknown container id', hidden_status
+ it_behaves_like 'rejects Debian access with unknown container id', :unauthorized, :basic
end
-RSpec.shared_examples 'Debian repository maintainer write endpoint' do |desired_behavior, success_status, success_body, authenticate_non_public: true|
- hidden_status = if authenticate_non_public
- :unauthorized
- else
- :not_found
- end
-
+RSpec.shared_examples 'Debian packages write endpoint' do |desired_behavior, success_status, success_body|
context 'with valid container' do
using RSpec::Parameterized::TableSyntax
- where(:visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do
- :public | :maintainer | true | true | success_status | success_body
- :public | :developer | true | true | :forbidden | nil
- :public | :guest | true | true | :forbidden | nil
- :public | :maintainer | true | false | :unauthorized | nil
- :public | :guest | true | false | :unauthorized | nil
- :public | :maintainer | false | true | :forbidden | nil
- :public | :guest | false | true | :forbidden | nil
- :public | :maintainer | false | false | :unauthorized | nil
- :public | :guest | false | false | :unauthorized | nil
- :public | :anonymous | false | true | :unauthorized | nil
- :private | :maintainer | true | true | success_status | success_body
- :private | :developer | true | true | :forbidden | nil
- :private | :guest | true | true | :forbidden | nil
- :private | :maintainer | true | false | :unauthorized | nil
- :private | :guest | true | false | :unauthorized | nil
- :private | :maintainer | false | true | :not_found | nil
- :private | :guest | false | true | :not_found | nil
- :private | :maintainer | false | false | :unauthorized | nil
- :private | :guest | false | false | :unauthorized | nil
- :private | :anonymous | false | true | hidden_status | nil
+ where(:visibility_level, :user_type, :auth_method, :expected_status, :expected_body) do
+ :public | :developer | :basic | success_status | success_body
+ :public | :developer | :private_token | :unauthorized | nil
+ :public | :guest | :basic | :forbidden | nil
+ :public | :not_a_member | :basic | :forbidden | nil
+ :public | :anonymous | :basic | :unauthorized | nil
+ :public | :invalid_token | :basic | :unauthorized | nil
+ :private | :developer | :basic | success_status | success_body
+ :private | :guest | :basic | :forbidden | nil
+ :private | :not_a_member | :basic | :not_found | nil
+ :private | :anonymous | :basic | :unauthorized | nil
+ :private | :invalid_token | :basic | :unauthorized | nil
end
with_them do
- include_context 'Debian repository access', params[:visibility_level], params[:user_role], params[:member], params[:user_token], :basic do
- it_behaves_like "Debian repository #{desired_behavior}", params[:expected_status], params[:expected_body]
+ include_context 'Debian repository access', params[:visibility_level], params[:user_type], params[:auth_method] do
+ it_behaves_like "Debian packages #{desired_behavior} request", params[:expected_status], params[:expected_body]
end
end
end
- it_behaves_like 'rejects Debian access with unknown container id', hidden_status
+ it_behaves_like 'rejects Debian access with unknown container id', :unauthorized, :basic
end
diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/destroy_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/destroy_list_shared_examples.rb
index 0cec67ff541..dca152223fb 100644
--- a/spec/support/shared_examples/requests/api/graphql/mutations/destroy_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/mutations/destroy_list_shared_examples.rb
@@ -28,7 +28,7 @@ RSpec.shared_examples 'board lists destroy request' do
it 'returns an error' do
subject
- expect(graphql_errors.first['message']).to include("The resource that you are attempting to access does not exist or you don't have permission to perform this action")
+ expect(graphql_errors.first['message']).to include(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb
index 41a61ba5fd7..d576a5874fd 100644
--- a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb
@@ -2,12 +2,26 @@
RSpec.shared_examples 'a package detail' do
it_behaves_like 'a working graphql query' do
- it 'matches the JSON schema' do
- expect(package_details).to match_schema('graphql/packages/package_details')
+ it_behaves_like 'matching the package details schema'
+ end
+
+ context 'with pipelines' do
+ let_it_be(:build_info1) { create(:package_build_info, :with_pipeline, package: package) }
+ let_it_be(:build_info2) { create(:package_build_info, :with_pipeline, package: package) }
+ let_it_be(:build_info3) { create(:package_build_info, :with_pipeline, package: package) }
+
+ it_behaves_like 'a working graphql query' do
+ it_behaves_like 'matching the package details schema'
end
end
end
+RSpec.shared_examples 'matching the package details schema' do
+ it 'matches the JSON schema' do
+ expect(package_details).to match_schema('graphql/packages/package_details')
+ end
+end
+
RSpec.shared_examples 'a package with files' do
it 'has the right amount of files' do
expect(package_files_response.length).to be(package.package_files.length)
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index 40799688144..0434d0beb7e 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -281,7 +281,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end
end
- context 'when request exceeds the rate limit' do
+ context 'when request exceeds the rate limit', :freeze_time, :clean_gitlab_redis_rate_limiting do
before do
stub_application_setting(notes_create_limit: 1)
allow(::Gitlab::ApplicationRateLimiter).to receive(:increment).and_return(2)
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index 2af7b616659..19677e92001 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -8,6 +8,8 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
let_it_be(:package_dependency_link3) { create(:packages_dependency_link, package: package, dependency_type: :bundleDependencies) }
let_it_be(:package_dependency_link4) { create(:packages_dependency_link, package: package, dependency_type: :peerDependencies) }
+ let_it_be(:package_metadatum) { create(:npm_metadatum, package: package) }
+
let(:headers) { {} }
subject { get(url, headers: headers) }
@@ -39,6 +41,19 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
# query count can slightly change between the examples so we're using a custom threshold
expect { get(url, headers: headers) }.not_to exceed_query_limit(control).with_threshold(4)
end
+
+ context 'with packages_npm_abbreviated_metadata disabled' do
+ before do
+ stub_feature_flags(packages_npm_abbreviated_metadata: false)
+ end
+
+ it 'calls the presenter without including metadata' do
+ expect(::Packages::Npm::PackagePresenter)
+ .to receive(:new).with(anything, anything, include_metadata: false).and_call_original
+
+ subject
+ end
+ end
end
shared_examples 'reject metadata request' do |status:|
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
index ed6d9ed43c8..06c51add438 100644
--- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -167,7 +167,7 @@ end
RSpec.shared_examples 'rejects PyPI access with unknown project id' do
context 'with an unknown project' do
- let(:project) { OpenStruct.new(id: 1234567890) }
+ let(:project) { double('access', id: 1234567890) }
it_behaves_like 'unknown PyPI scope id'
end
@@ -175,7 +175,7 @@ end
RSpec.shared_examples 'rejects PyPI access with unknown group id' do
context 'with an unknown project' do
- let(:group) { OpenStruct.new(id: 1234567890) }
+ let(:group) { double('access', id: 1234567890) }
it_behaves_like 'unknown PyPI scope id'
end
diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb
index 8207190b1dc..40843ccbd15 100644
--- a/spec/support/shared_examples/requests/api/status_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb
@@ -76,3 +76,32 @@ RSpec.shared_examples '412 response' do
end
end
end
+
+RSpec.shared_examples '422 response' do
+ let(:message) { nil }
+
+ before do
+ # Fires the request
+ request
+ end
+
+ it 'returns 422' do
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response).to be_an Object
+
+ if message.present?
+ expect(json_response['message']).to eq(message)
+ end
+ end
+end
+
+RSpec.shared_examples '503 response' do
+ before do
+ # Fires the request
+ request
+ end
+
+ it 'returns 503' do
+ expect(response).to have_gitlab_http_status(:service_unavailable)
+ end
+end
diff --git a/spec/support/shared_examples/requests/applications_controller_shared_examples.rb b/spec/support/shared_examples/requests/applications_controller_shared_examples.rb
new file mode 100644
index 00000000000..8f852d42c2c
--- /dev/null
+++ b/spec/support/shared_examples/requests/applications_controller_shared_examples.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'applications controller - GET #show' do
+ describe 'GET #show' do
+ it 'renders template' do
+ get show_path
+
+ expect(response).to render_template :show
+ end
+
+ context 'when application is viewed after being created' do
+ before do
+ create_application
+ end
+
+ it 'sets `@created` instance variable to `true`' do
+ get show_path
+
+ expect(assigns[:created]).to eq(true)
+ end
+ end
+
+ context 'when application is reviewed' do
+ it 'sets `@created` instance variable to `false`' do
+ get show_path
+
+ expect(assigns[:created]).to eq(false)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'applications controller - POST #create' do
+ it "sets `#{OauthApplications::CREATED_SESSION_KEY}` session key to `true`" do
+ create_application
+
+ expect(session[OauthApplications::CREATED_SESSION_KEY]).to eq(true)
+ end
+end
+
+def create_application
+ create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api'])
+ post create_path, params: { doorkeeper_application: create_params }
+end
diff --git a/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb b/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb
index ff87fc5d8df..f8a752a5673 100644
--- a/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb
+++ b/spec/support/shared_examples/requests/self_monitoring_shared_examples.rb
@@ -39,6 +39,10 @@ end
# let(:status_api) { status_create_self_monitoring_project_admin_application_settings_path }
# subject { post create_self_monitoring_project_admin_application_settings_path }
RSpec.shared_examples 'triggers async worker, returns sidekiq job_id with response accepted' do
+ before do
+ allow(worker_class).to receive(:with_status).and_return(worker_class)
+ end
+
it 'returns sidekiq job_id of expected length' do
subject
diff --git a/spec/support/shared_examples/requests/snippet_shared_examples.rb b/spec/support/shared_examples/requests/snippet_shared_examples.rb
index dae3a3e74be..b13c4da0bed 100644
--- a/spec/support/shared_examples/requests/snippet_shared_examples.rb
+++ b/spec/support/shared_examples/requests/snippet_shared_examples.rb
@@ -86,6 +86,7 @@ RSpec.shared_examples 'snippet blob content' do
expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ expect(response.parsed_body).to be_empty
end
context 'when snippet repository is empty' do
diff --git a/spec/support/shared_examples/service_desk_issue_templates_examples.rb b/spec/support/shared_examples/service_desk_issue_templates_examples.rb
index fd9645df7a3..ed6c5199936 100644
--- a/spec/support/shared_examples/service_desk_issue_templates_examples.rb
+++ b/spec/support/shared_examples/service_desk_issue_templates_examples.rb
@@ -3,10 +3,10 @@
RSpec.shared_examples 'issue description templates from current project only' do
it 'loads issue description templates from the project only' do
within('#service-desk-template-select') do
- expect(page).to have_content('project-issue-bar')
- expect(page).to have_content('project-issue-foo')
- expect(page).not_to have_content('group-issue-bar')
- expect(page).not_to have_content('group-issue-foo')
+ expect(page).to have_content(:all, 'project-issue-bar')
+ expect(page).to have_content(:all, 'project-issue-foo')
+ expect(page).not_to have_content(:all, 'group-issue-bar')
+ expect(page).not_to have_content(:all, 'group-issue-foo')
end
end
end
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
index 92a7d7ab3a3..ca86cb082a7 100644
--- a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
+++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb
@@ -3,7 +3,10 @@
# This shared_example requires the following variables:
# - `service`, the service which includes AlertManagement::AlertProcessing
RSpec.shared_examples 'creates an alert management alert or errors' do
- it { is_expected.to be_success }
+ specify do
+ expect(subject).to be_success
+ expect(subject.payload).to match(alerts: all(a_kind_of(AlertManagement::Alert)))
+ end
it 'creates AlertManagement::Alert' do
expect(Gitlab::AppLogger).not_to receive(:warn)
@@ -89,6 +92,7 @@ RSpec.shared_examples 'adds an alert management alert event' do
expect { subject }.to change { alert.reload.events }.by(1)
expect(subject).to be_success
+ expect(subject.payload).to match(alerts: all(a_kind_of(AlertManagement::Alert)))
end
it_behaves_like 'does not create an alert management alert'
diff --git a/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb b/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb
index 56a6d24d557..c4f6273b46c 100644
--- a/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb
+++ b/spec/support/shared_examples/services/jira/requests/base_shared_examples.rb
@@ -26,11 +26,14 @@ RSpec.shared_examples 'a service that handles Jira API errors' do
expect(subject).to be_a(ServiceResponse)
expect(subject).to be_error
- expect(subject.message).to include(expected_message)
+ expect(subject.message).to start_with(expected_message)
end
end
context 'when the JSON in JIRA::HTTPError is unsafe' do
+ config_docs_link_url = Rails.application.routes.url_helpers.help_page_path('integration/jira/configure')
+ let(:docs_link_start) { '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: config_docs_link_url } }
+
before do
stub_client_and_raise(JIRA::HTTPError, error)
end
@@ -39,7 +42,8 @@ RSpec.shared_examples 'a service that handles Jira API errors' do
let(:error) { '{"errorMessages":' }
it 'returns the default error message' do
- expect(subject.message).to eq('An error occurred while requesting data from Jira. Check your Jira integration configuration and try again.')
+ error_message = 'An error occurred while requesting data from Jira. Check your %{docs_link_start}Jira integration configuration</a> and try again.' % { docs_link_start: docs_link_start }
+ expect(subject.message).to eq(error_message)
end
end
@@ -47,7 +51,8 @@ RSpec.shared_examples 'a service that handles Jira API errors' do
let(:error) { '{"errorMessages":["<script>alert(true)</script>foo"]}' }
it 'sanitizes it' do
- expect(subject.message).to eq('An error occurred while requesting data from Jira: foo. Check your Jira integration configuration and try again.')
+ error_message = 'An error occurred while requesting data from Jira: foo. Check your %{docs_link_start}Jira integration configuration</a> and try again.' % { docs_link_start: docs_link_start }
+ expect(subject.message).to eq(error_message)
end
end
end
diff --git a/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb b/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb
new file mode 100644
index 00000000000..716bee39fca
--- /dev/null
+++ b/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'filters by paginated notes' do |event_type|
+ let(:event) { create(event_type) } # rubocop:disable Rails/SaveBang
+
+ before do
+ create(event_type, issue: event.issue)
+ end
+
+ it 'only returns given notes' do
+ paginated_notes = { event_type.to_s.pluralize => [double(id: event.id)] }
+ notes = described_class.new(event.issue, user, paginated_notes: paginated_notes).execute
+
+ expect(notes.size).to eq(1)
+ expect(notes.first.event).to eq(event)
+ end
+
+ context 'when paginated notes is empty' do
+ it 'does not return any notes' do
+ notes = described_class.new(event.issue, user, paginated_notes: {}).execute
+
+ expect(notes.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb b/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb
index 89c0841fbd6..e6da96e12ec 100644
--- a/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb
+++ b/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb
@@ -17,7 +17,7 @@ end
RSpec.shared_examples 'returns in_progress based on Sidekiq::Status' do
it 'returns true when job is enqueued' do
- jid = described_class.perform_async
+ jid = described_class.with_status.perform_async
expect(described_class.in_progress?(jid)).to eq(true)
end